using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.DailyReport;
using AsbCloudApp.Data.DailyReport.Blocks;
using AsbCloudApp.Data.DailyReport.Blocks.Sign;
using AsbCloudApp.Data.DailyReport.Blocks.Subsystems;
using AsbCloudApp.Data.DailyReport.Blocks.TimeBalance;
using AsbCloudApp.Data.DailyReport.Blocks.WellOperation;
using AsbCloudApp.Services.DailyReport;
using ClosedXML.Excel;

namespace AsbCloudInfrastructure.Services.DailyReport;

public class DailyReportExportService : IDailyReportExportService
{
   private const int rowStartScheduleBlock = 20;
   private const int rowStartSubsystemBlock = 26;
   private const int rowStartFactWellOperationBlock = 39;
   private const int rowStartTimeBalanceBlock = 62;
   private const int rowStartProcessMapWellDrillingBlock = 68;

   private const int columnTimeBalanceDurationPlan = 3;
   private const int columnTimeBalanceDurationFact = 6;
   private const int columnTimeBalanceReasonDeviation = 8;
   private const int columnTimeBalanceDrillingDeviationPerSection = 10;
   private const int columnTimeBalanceDrillingDeviationPerDay = 11;

	private const int columnScheduleDriller = 3;
	private const int columnScheduleShiftStart = 7;
	private const int columnScheduleShiftEnd = 8;

   private const int columnSubsystemName = 2;
   private const int columnUseSubsystemPerDayUsedTimeHours = 3;
   private const int columnUseSubsystemPerDaySumDepthInterval = 4;
   private const int columnUseSubsystemPerDayKUsage = 5;

   private const int columnUseSubsystemPerWellUsedTimeHours = 6;
   private const int columnUseSubsystemPerWellSumDepthInterval = 7;
   private const int columnUseSubsystemPerWellKUsage = 8;

   private const int columnProcessMapWellDrillingBlockDrillingMode = 2;
   private const int columnProcessMapWellDrillingBlockWellBoreDepth = 3;
   private const int columnProcessMapWellDrillingBlockMechDrillingHours = 4;
   private const int columnProcessMapWellDrillingBlockRopPlan = 5;
   private const int columnProcessMapWellDrillingBlockRopFact = 6;

   private const int columnWellOperationCategory = 2;
   private const int columnWellOperationDurationHours = 4;

   private const string cellCustomer = "C2";
   private const string cellContractor = "C3";
   private const string cellDeposit = "C5";
   private const string cellCluster = "C6";
   private const string cellWellCaption = "C7";
   private const string cellWellType = "C8";
   private const string cellDate = "C12";
   private const string cellDepthStart = "C13";
   private const string cellDepthEnd = "D13";

   private const string cellTrajectoryBlockWellboreDepth = "B17";
   private const string cellTrajectoryBlockVerticalDepth = "C17";
   private const string cellTrajectoryBlockZenithAngle = "D17";
   private const string cellTrajectoryBlockAzimuthGeo = "E17";

   private const string cellTimeBalanceBlockSection = "C60";
   private const string cellTimeBalanceBlockWellDepthPlan = "C61";
   private const string cellSectionDrillingHours = "F77";
   private const string cellTimeBalanceBlockWellDepthFact = "F78";
   private const string cellTimeBalanceBlockWellOperationSlipsTimeCount = "F79";

   private const string cellSubsystemComment = "D35";
   private const string cellSubsystemTvgLagDays = "F80";
   private const string cellSubsystemMeasurementsPerDay = "F81";
   private const string cellSubsystemWellbore = "C9";
   private const string cellSubsystemTotalRopPlan = "E70";

   private const string cellSignDrillingMaster = "C85";
   private const string cellSignSupervisor = "C86";

   private readonly IDailyReportService dailyReportService;

   public DailyReportExportService(IDailyReportService dailyReportService)
   {
      this.dailyReportService = dailyReportService;
   }

   public async Task<(string FileName, Stream File)> ExportAsync(int idWell, DateOnly dailyReportDate, CancellationToken cancellationToken)
   {
      var dailyReport = await dailyReportService.GetAsync(idWell, dailyReportDate, cancellationToken);

		var stream = GenerateFile(dailyReport);

      var fileName = $"Суточный_рапорт_по_скважине_{dailyReport.WellCaption}_куст_{dailyReport.Cluster}_от_{dailyReport.Date:yy-MM-dd}.xlsx";

      return (fileName, stream);
   }

	private static MemoryStream GenerateFile(DailyReportDto dailyReport)
	{
		using var excelTemplateStream = Assembly
			.GetExecutingAssembly()
			.GetTemplateCopyStream("DailyReportTemplate.xlsx");

      using var workbook = new XLWorkbook(excelTemplateStream);

      AddDailyReportToWorkBook(workbook, dailyReport);

      var memoryStream = new MemoryStream();
      workbook.SaveAs(memoryStream, new SaveOptions { });
      memoryStream.Seek(0, SeekOrigin.Begin);
      return memoryStream;
   }

   private static void AddDailyReportToWorkBook(IXLWorkbook workbook, DailyReportDto dailyReport)
   {
      const string sheetName = "Суточный отчёт";

      var sheet = workbook.GetWorksheet(sheetName);;

      sheet.Cell(cellCustomer).SetCellValue(dailyReport.Customer);
      sheet.Cell(cellContractor).SetCellValue(dailyReport.Contractor);
      sheet.Cell(cellDeposit).SetCellValue(dailyReport.Deposit);
      sheet.Cell(cellCluster).SetCellValue(dailyReport.Cluster);
      sheet.Cell(cellWellCaption).SetCellValue(dailyReport.WellCaption);
      sheet.Cell(cellWellType).SetCellValue(dailyReport.WellType);
      sheet.Cell(cellDate).SetCellValue(dailyReport.Date);
      sheet.Cell(cellDepthStart).SetCellValue(dailyReport.DepthStart);
      sheet.Cell(cellDepthEnd).SetCellValue(dailyReport.DepthEnd);

      if (dailyReport.TimeBalanceBlock is not null)
         AddTimeBalanceBlockToSheet(sheet, dailyReport.TimeBalanceBlock);

      if (dailyReport.SubsystemBlock is not null)
         AddSubsystemBlockToSheet(sheet, dailyReport.SubsystemBlock);

      if (dailyReport.SignBlock is not null)
         AddSignBlockToSheet(sheet, dailyReport.SignBlock);

      AddTrajectoryBlockToSheet(sheet, dailyReport.TrajectoryBlock);
      AddScheduleBlockToSheet(sheet, dailyReport.ScheduleBlock);
      AddProcessMapWellDrillingBlockToSheet(sheet, dailyReport.ProcessMapWellDrillingBlock);
      AddFactWellOperationBlockToSheet(sheet, dailyReport.FactWellOperationBlock);
   }

   private static void AddTrajectoryBlockToSheet(IXLWorksheet sheet, TrajectoryBlockDto trajectoryBlock)
   {
      sheet.Cell(cellTrajectoryBlockWellboreDepth).SetCellValue(trajectoryBlock.WellboreDepth);
      sheet.Cell(cellTrajectoryBlockVerticalDepth).SetCellValue(trajectoryBlock.VerticalDepth);
      sheet.Cell(cellTrajectoryBlockZenithAngle).SetCellValue(trajectoryBlock.ZenithAngle);
      sheet.Cell(cellTrajectoryBlockAzimuthGeo).SetCellValue(trajectoryBlock.AzimuthGeo);
   }

   private static void AddTimeBalanceBlockToSheet(IXLWorksheet sheet, TimeBalanceBlockDto timeBalanceBlock)
   {
      var rowCurrent = rowStartTimeBalanceBlock;

      foreach (var wellOperation in timeBalanceBlock.WellOperations.OrderBy(w => w.IdWellOperation))
      {
         sheet.Cell(rowCurrent, columnTimeBalanceDurationPlan).SetCellValue(wellOperation.DurationHours.Plan);
         sheet.Cell(rowCurrent, columnTimeBalanceDurationFact).SetCellValue(wellOperation.DurationHours.Fact);
         sheet.Cell(rowCurrent, columnTimeBalanceReasonDeviation).SetCellValue(wellOperation.ReasonDeviation);
         sheet.Cell(rowCurrent, columnTimeBalanceDrillingDeviationPerSection).SetCellValue(wellOperation.DrillingDeviationPerSection);
         sheet.Cell(rowCurrent, columnTimeBalanceDrillingDeviationPerDay).SetCellValue(wellOperation.DrillingDeviationPerDay);

         rowCurrent++;
      }

      sheet.Cell(cellTimeBalanceBlockSection).SetCellValue(timeBalanceBlock.SectionName);
      sheet.Cell(cellTimeBalanceBlockWellDepthPlan).SetCellValue(timeBalanceBlock.WellDepth.Plan);
      sheet.Cell(cellTimeBalanceBlockWellDepthFact).SetCellValue(timeBalanceBlock.WellDepth.Fact);
      sheet.Cell(cellTimeBalanceBlockWellOperationSlipsTimeCount).SetCellValue(timeBalanceBlock.WellOperationSlipsTimeCount);
   }

	private static void AddSubsystemBlockToSheet(IXLWorksheet sheet, SubsystemBlockDto subsystemBlock)
	{
		var rowCurrent = rowStartSubsystemBlock;

		foreach (var subsystem in subsystemBlock.Subsystems)
		{
			sheet.Cell(rowCurrent, columnSubsystemName).SetCellValue(subsystem.Name);
			sheet.Cell(rowCurrent, columnUseSubsystemPerDayUsedTimeHours).SetCellValue(subsystem.UsagePerDay?.UsedTimeHours);
			sheet.Cell(rowCurrent, columnUseSubsystemPerDaySumDepthInterval).SetCellValue(subsystem.UsagePerDay?.SumDepthInterval);
			sheet.Cell(rowCurrent, columnUseSubsystemPerDayKUsage).SetCellValue(subsystem.UsagePerDay?.KUsage * 100);

			sheet.Cell(rowCurrent, columnUseSubsystemPerWellUsedTimeHours).SetCellValue(subsystem.UsagePerWell?.UsedTimeHours);
			sheet.Cell(rowCurrent, columnUseSubsystemPerWellSumDepthInterval).SetCellValue(subsystem.UsagePerWell?.SumDepthInterval);
			sheet.Cell(rowCurrent, columnUseSubsystemPerWellKUsage).SetCellValue(subsystem.UsagePerWell?.KUsage * 100);

			rowCurrent++;
		}

      sheet.Cell(cellSubsystemComment).SetCellValue(subsystemBlock.Comment);
      sheet.Cell(cellSubsystemTvgLagDays).SetCellValue(subsystemBlock.TvgLagDays);
      sheet.Cell(cellSubsystemMeasurementsPerDay).SetCellValue(subsystemBlock.MeasurementsPerDay);
      sheet.Cell(cellSubsystemWellbore).SetCellValue(subsystemBlock.Wellbore);
      sheet.Cell(cellSubsystemTotalRopPlan).SetCellValue(subsystemBlock.TotalRopPlan);
   }

   private static void AddScheduleBlockToSheet(IXLWorksheet sheet, IEnumerable<ScheduleRecordDto> scheduleBlock)
   {
      var rowCurrent = rowStartScheduleBlock;

		foreach (var schedule in scheduleBlock.OrderBy(s => s.ShiftStart))
		{
			sheet.Cell(rowCurrent, columnScheduleDriller).SetCellValue($"{schedule.Surname} {schedule.Name} {schedule.Patronymic}");
			sheet.Cell(rowCurrent, columnScheduleShiftStart).SetCellValue(schedule.ShiftStart);
			sheet.Cell(rowCurrent, columnScheduleShiftEnd).SetCellValue(schedule.ShiftEnd);

         rowCurrent++;
      }
   }

   private static void AddProcessMapWellDrillingBlockToSheet(IXLWorksheet sheet,
      IEnumerable<ProcessMapWellDrillingRecordDto> processMapWellDrillingBlock)
   {
      var rowCurrent = rowStartProcessMapWellDrillingBlock;

      foreach (var processMapWellDrilling in processMapWellDrillingBlock.OrderBy(p => p.DrillingMode))
      {
         sheet.Cell(rowCurrent, columnProcessMapWellDrillingBlockDrillingMode).SetCellValue(processMapWellDrilling.DrillingMode);
         sheet.Cell(rowCurrent, columnProcessMapWellDrillingBlockWellBoreDepth).SetCellValue(processMapWellDrilling.WellBoreDepth);
         sheet.Cell(rowCurrent, columnProcessMapWellDrillingBlockMechDrillingHours).SetCellValue(processMapWellDrilling.MechDrillingHours);
         sheet.Cell(rowCurrent, columnProcessMapWellDrillingBlockRopPlan).SetCellValue(processMapWellDrilling.Rop.Plan);
         sheet.Cell(rowCurrent, columnProcessMapWellDrillingBlockRopFact).SetCellValue(processMapWellDrilling.Rop.Fact);

         rowCurrent++;
      }
   }

   private static void AddFactWellOperationBlockToSheet(IXLWorksheet sheet, WellOperationBlockDto factWellOperationBlock)
   {
      var rowCurrent = rowStartFactWellOperationBlock;
      
      sheet.Cell(cellSectionDrillingHours).SetCellValue(factWellOperationBlock.SectionDrillingHours);
      
      foreach (var factOperation in factWellOperationBlock.WellOperations.OrderBy(w => w.CategoryName))
      {
         sheet.Cell(rowCurrent, columnWellOperationCategory).SetCellValue(factOperation.CategoryName);
         sheet.Cell(rowCurrent, columnWellOperationDurationHours).SetCellValue(factOperation.DurationHours);

         rowCurrent++;
      }
   }

   private static void AddSignBlockToSheet(IXLWorksheet sheet, SignBlockDto signBlock)
   {
      sheet.Cell(cellSignDrillingMaster).SetCellValue(signBlock.DrillingMaster?.ToString());
      sheet.Cell(cellSignSupervisor).SetCellValue(signBlock.Supervisor?.ToString());
   }
}