using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.ProcessMap;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using ClosedXML.Excel;
using System;
using AsbCloudApp.Data;

namespace AsbCloudInfrastructure.Services.ProcessMap;

/*
* password for ProcessMapImportTemplate.xlsx is ASB2020!
*/
public class ProcessMapPlanImportService : IProcessMapPlanImportService
{
	private readonly IProcessMapPlanRepository processMapPlanRepository;
	private readonly ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository;

	private const string sheetNamePlan = "План";

	private const int headerRowsCount = 2;

	private const int columnWellSectionType = 1;
	private const int columnMode = 2;
	private const int columnDepthStart = 3;
	private const int columnDepthEnd = 4;
	private const int columnPressurePlan = 5;
	private const int columnPressureLimitMax = 6;
	private const int columnAxialLoadPlan = 7;
	private const int columnAxialLoadLimitMax = 8;
	private const int columnTopDriveTorquePlan = 9;
	private const int columnTopDriveTorqueLimitMax = 10;
	private const int columnTopDriveSpeedPlan = 11;
	private const int columnTopDriveSpeedLimitMax = 12;
	private const int columnFlowPlan = 13;
	private const int columnFlowLimitMax = 14;
	private const int columnRopPlan = 15;
	private const int columnUsageSaub = 16;
	private const int columnUsageSpin = 17;

	private WellSectionTypeDto[] sections = null!;

	public ProcessMapPlanImportService(IProcessMapPlanRepository processMapPlanRepository,
		ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository)
	{
		this.processMapPlanRepository = processMapPlanRepository;
		this.wellSectionTypeRepository = wellSectionTypeRepository;
	}

	public async Task ImportAsync(int idWell, int idUser, bool deleteProcessMapPlansBeforeImport, Stream stream, 
		CancellationToken cancellationToken)
	{
		sections = (await wellSectionTypeRepository.GetAllAsync(cancellationToken)).ToArray();

		using var workBook = new XLWorkbook(stream);

		var processPlanMaps = ParseWorkBook(workBook);

		if (deleteProcessMapPlansBeforeImport)
			await processMapPlanRepository.RemoveByWellAsync(idWell, cancellationToken);
		
		foreach (var processPlanMap in processPlanMaps)
		{
			processPlanMap.IdWell = idWell;
			processPlanMap.IdUser = idUser;
		}

		await processMapPlanRepository.InsertRangeAsync(processPlanMaps, cancellationToken);
	}

	public async Task<Stream> ExportAsync(int idWell, CancellationToken cancellationToken)
	{
		var processMapPlans = (await processMapPlanRepository.GetByIdWellAsync(idWell,
			cancellationToken)).ToArray();

		return await GenerateExcelFileStreamAsync(processMapPlans,
			cancellationToken);
	}

	public async Task<Stream> GetExcelTemplateStreamAsync(CancellationToken cancellationToken)
	{
		var resourceName = Assembly.GetExecutingAssembly()
			.GetManifestResourceNames()
			.FirstOrDefault(n => n.EndsWith("ProcessMapPlanTemplate.xlsx"))!;

		using var stream = Assembly.GetExecutingAssembly()
			.GetManifestResourceStream(resourceName)!;

		var memoryStream = new MemoryStream();
		await stream.CopyToAsync(memoryStream, cancellationToken);
		memoryStream.Position = 0;

		return memoryStream;
	}

	private void AddToWorkbook(XLWorkbook workbook, ProcessMapPlanDto[] processMapPlans)
	{
		if (!processMapPlans.Any()) 
			return;
		
		var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan)
					?? throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}.");

		AddToSheet(sheet, processMapPlans);
	}

	private void AddToSheet(IXLWorksheet sheet, ProcessMapPlanDto[] processMapPlans)
	{
		for (int i = 0; i < processMapPlans.Length; i++)
		{
			var row = sheet.Row(1 + i + headerRowsCount);
			AddToRow(row, processMapPlans[i]);
		}
	}

	private void AddToRow(IXLRow row, ProcessMapPlanDto processMap)
	{
		row.Cell(columnWellSectionType).Value = processMap.WellSectionType.Caption;
		row.Cell(columnMode).Value = GetModeCaption(processMap.IdMode);
		row.Cell(columnDepthStart).Value = processMap.DepthStart;
		row.Cell(columnDepthEnd).Value = processMap.DepthEnd;
		row.Cell(columnPressurePlan).Value = processMap.Pressure.Plan;
		row.Cell(columnPressureLimitMax).Value = processMap.Pressure.LimitMax;
		row.Cell(columnAxialLoadPlan).Value = processMap.AxialLoad.Plan;
		row.Cell(columnAxialLoadLimitMax).Value = processMap.AxialLoad.LimitMax;
		row.Cell(columnTopDriveTorquePlan).Value = processMap.TopDriveTorque.Plan;
		row.Cell(columnTopDriveTorqueLimitMax).Value = processMap.TopDriveTorque.LimitMax;
		row.Cell(columnTopDriveSpeedPlan).Value = processMap.TopDriveSpeed.Plan;
		row.Cell(columnTopDriveSpeedLimitMax).Value = processMap.TopDriveSpeed.LimitMax;
		row.Cell(columnFlowPlan).Value = processMap.Flow.Plan;
		row.Cell(columnFlowLimitMax).Value = processMap.Flow.LimitMax;
		row.Cell(columnRopPlan).Value = processMap.RopPlan;
		row.Cell(columnUsageSaub).Value = processMap.UsageSaub;
		row.Cell(columnUsageSpin).Value = processMap.UsageSpin;
	}

	private ProcessMapPlanDto[] ParseWorkBook(IXLWorkbook workbook)
	{
		var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan)
					?? throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}.");

		return ParseSheet(sheet);
	}

	private ProcessMapPlanDto[] ParseSheet(IXLWorksheet sheet)
	{
		const int columnsCount = 17;

		if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnsCount)
			throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов.");

		var rowsCount = sheet.RowsUsed().Count() - headerRowsCount;

		if (rowsCount <= 0)
			return Array.Empty<ProcessMapPlanDto>();

		var processMapPlans = new ProcessMapPlanDto[rowsCount];

		var parseErrors = new List<string>();

		for (int i = 0; i < processMapPlans.Length; i++)
		{
			var row = sheet.Row(1 + i + headerRowsCount);

			try
			{
				processMapPlans[i] = ParseRow(row);
			}
			catch (FileFormatException ex)
			{
				parseErrors.Add(ex.Message);
			}
		}

		if (parseErrors.Any())
			throw new FileFormatException(string.Join("\r\n", parseErrors));

		return processMapPlans;
	}

	private ProcessMapPlanDto ParseRow(IXLRow row)
	{
		var wellSectionTypeCaption = GetCellValue<string>(row, columnWellSectionType).Trim().ToLower();
		var modeName = GetCellValue<string>(row, columnMode).Trim().ToLower();
		var depthStart = GetCellValue<double>(row, columnDepthStart);
		var depthEnd = GetCellValue<double>(row, columnDepthEnd);
		var pressurePlan = GetCellValue<double>(row, columnPressurePlan);
		var pressureLimitMax = GetCellValue<double>(row, columnPressureLimitMax);
		var axialLoadPlan = GetCellValue<double>(row, columnAxialLoadPlan);
		var axialLoadLimitMax = GetCellValue<double>(row, columnAxialLoadLimitMax);
		var topDriveTorquePlan = GetCellValue<double>(row, columnTopDriveTorquePlan);
		var topDriveTorqueLimitMax = GetCellValue<double>(row, columnTopDriveTorqueLimitMax);
		var topDriveSpeedPlan = GetCellValue<double>(row, columnTopDriveSpeedPlan);
		var topDriveSpeedLimitMax = GetCellValue<double>(row, columnTopDriveSpeedLimitMax);
		var flowPlan = GetCellValue<double>(row, columnFlowPlan);
		var flowLimitMax = GetCellValue<double>(row, columnFlowLimitMax);
		var ropPlan = GetCellValue<double>(row, columnRopPlan);
		var usageSaub = GetCellValue<double>(row, columnUsageSaub);
		var usageSpin = GetCellValue<double>(row, columnUsageSpin);

		var wellSection = sections.FirstOrDefault(s => s.Caption.Trim().ToLower() == wellSectionTypeCaption)
						?? throw new FileFormatException(
							$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указана некорректная секция");

		var idMode = GetIdMode(modeName)
					?? throw new FileFormatException(
						$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указан некорректный режим");
		
		if (depthStart is < 0 or > 50000) 
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указана некорректная стартовая глубина");

		if (depthEnd is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указана некорректная конечная глубина");

		if (pressurePlan is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение перепада давления");

		if (pressureLimitMax is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение перепада давления");

		if (axialLoadPlan is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение нагрузки");

		if (axialLoadLimitMax is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение нагрузки");

		if (topDriveTorquePlan is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение момента на ВСП");

		if (topDriveTorqueLimitMax is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение момента на ВСП");

		if (topDriveSpeedPlan is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение оборотов на ВСП");

		if (topDriveSpeedLimitMax is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничения оборота на ВСП");

		if (flowPlan is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение расхода");

		if (flowLimitMax is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение расхода");

		if (ropPlan is < 0 or > 50000)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение механической скорости");

		if (usageSaub is < 0 or > 100)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указан некорректный плановый процент использования АКБ");

		if (usageSpin is < 0 or > 100)
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указан некорректные плановый процент использования spin master");
		
		return new()
		{
			IdWellSectionType = wellSection.Id,
			IdMode = idMode,
			DepthStart = depthStart,
			LastUpdate = DateTime.UtcNow,
			DepthEnd = depthEnd,
			Pressure = new()
			{
				Plan = pressurePlan,
				LimitMax = pressureLimitMax
			},
			AxialLoad = new()
			{
				Plan = axialLoadPlan,
				LimitMax = axialLoadLimitMax
			},
			TopDriveTorque = new()
			{
				Plan = topDriveTorquePlan,
				LimitMax = topDriveTorqueLimitMax
			},
			TopDriveSpeed = new()
			{
				Plan = topDriveSpeedPlan,
				LimitMax = topDriveSpeedLimitMax
			},
			Flow = new()
			{
				Plan = flowPlan,
				LimitMax = flowLimitMax
			},
			RopPlan = ropPlan,
			UsageSaub = usageSaub,
			UsageSpin = usageSpin
		};
	}

	private async Task<Stream> GenerateExcelFileStreamAsync(ProcessMapPlanDto[] processMapPlans,
		CancellationToken cancellationToken)
	{
		using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);

		using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);

		AddToWorkbook(workbook, processMapPlans);

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

	private static int? GetIdMode(string modeName) =>
		modeName switch
		{
			"ручной" => 0,
			"ротор" => 1,
			"слайд" => 2,
			_ => null
		};

	private static string GetModeCaption(int idMode)
		=> idMode switch
		{
			1 => "Ротор",
			2 => "Слайд",
			_ => "Ручной",
		};

	private static T GetCellValue<T>(IXLRow row, int columnNumber)
	{
		try
		{
			var cell = row.Cell(columnNumber);
			return (T)Convert.ChangeType(cell.Value, typeof(T));
		}
		catch
		{
			throw new FileFormatException(
				$"Лист {row.Worksheet.Name}. Ячейка: ({row.RowNumber()},{columnNumber}) содержит некорректное значение");
		}
	}
}