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 columnSheduleDriller = 3;
	private const int columnSheduleShiftStart = 7;
	private const int columnSheduleShiftEnd = 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 = await GenerateFileAsync(dailyReport, cancellationToken);

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

		return (fileName, stream);
	}

	private static async Task<Stream> GenerateFileAsync(DailyReportDto dailyReport, CancellationToken cancellationToken)
	{
		using var excelTemplateStream = await Assembly
			.GetExecutingAssembly()
			.GetTemplateCopyStreamAsync("DailyReportTemplate.xlsx", cancellationToken);

		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 rowСurrent = rowStartSubsystemBlock;

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

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

			rowСurrent++;
		}

		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, columnSheduleDriller).SetCellValue($"{schedule.Surname} {schedule.Name} {schedule.Patronymic}");
			sheet.Cell(rowCurrent, columnSheduleShiftStart).SetCellValue(schedule.ShiftStart);
			sheet.Cell(rowCurrent, columnSheduleShiftEnd).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());
	}
}