using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.ProcessMaps.Report;
using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps.WellDrilling;
using ClosedXML.Excel;

namespace AsbCloudInfrastructure.Services.ProcessMaps.Report;

public class ProcessMapReportWellDrillingExportService : IProcessMapReportWellDrillingExportService
{
    const int firstColumn = 2;
    const int lastColumn = 42;

    const int headerRowsCount = 5;

    private readonly IWellService wellService;
    private readonly IProcessMapReportWellDrillingService processMapReportWellDrillingService;

    public ProcessMapReportWellDrillingExportService(IWellService wellService,
        IProcessMapReportWellDrillingService processMapReportWellDrillingService)
    {
        this.wellService = wellService;
        this.processMapReportWellDrillingService = processMapReportWellDrillingService;
    }


    public async Task<(string Name, Stream File)?> ExportAsync(int idWell, CancellationToken cancellationToken)
    {
        var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken);

        if (well is null)
            return null;

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

        var data = await processMapReportWellDrillingService.GetAsync(idWell, cancellationToken);

        FillProcessMapToWorkbook(workbook, data);

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

        var name = $"РТК бурение. Отчёт по скважине {well.Caption} куст {well.Cluster}.xlsx";

        return (name, memoryStream);
    }

    private static void FillProcessMapToWorkbook(XLWorkbook workbook,
        IEnumerable<ProcessMapReportWellDrillingDto> data)
    {
        var sheet = workbook.Worksheets.FirstOrDefault();
        if (sheet is null)
            return;
        var dataBySections = data.GroupBy(p => p.IdWellSectionType);
        FillSheet(sheet, dataBySections);
    }

    private static void FillSheet(IXLWorksheet sheet,
        IEnumerable<IGrouping<int, ProcessMapReportWellDrillingDto>> dataBySections)
    {
        var startRow = headerRowsCount + 1;
        foreach (var sectionData in dataBySections)
        {
            if (sectionData.Any())
                startRow = FillSection(sheet, sectionData, startRow);
        }
    }

    private static int FillSection(IXLWorksheet sheet, IGrouping<int, ProcessMapReportWellDrillingDto> sectionData,
        int row)
    {
        var rowStart = row;
        var sectionName = sectionData.FirstOrDefault()?.WellSectionTypeName
                          ?? sectionData.Key.ToString();

        sheet.Range(row, firstColumn, row, lastColumn)
            .Merge()
            .FirstCell()
            .SetVal(sectionName)
            .Style
            .Fill.SetBackgroundColor(XLColor.LightGray);
        row++;
        foreach (var interval in sectionData)
            row = FillIntervalData(sheet, interval, row);

        var sectionStyle = sheet.Range(rowStart, firstColumn, row - 1, lastColumn).Style;
        SetBorders(sectionStyle);
        return row;
    }

    private static int FillIntervalData(IXLWorksheet sheet, ProcessMapReportWellDrillingDto interval, int row)
    {
        const int columnDepth = firstColumn + 1;
        const int columnDate = firstColumn + 2;
        const int columnRopTime = firstColumn + 3;
        const int columnMode = firstColumn + 4;

        sheet.Cell(row, firstColumn)
            .SetVal(interval.DepthStart, "0.0");

        sheet.Cell(row, columnDepth)
            .SetVal(interval.DepthEnd, "0.0");

        sheet.Cell(row, columnDate)
            .SetVal(interval.DateStart);

        sheet.Cell(row, columnRopTime)
            .SetVal(interval.MechDrillingHours);

        row = FillIntervalModeData(sheet, interval, columnMode, row);

        return row;
    }

    private static int FillIntervalModeData(IXLWorksheet sheet, ProcessMapReportWellDrillingDto modeData,
        int column, int row)
    {
        int columnDeltaDepth = column + 1;
        int columnPressure = columnDeltaDepth + 1;
        int columnLoad = columnPressure + 5;
        int columnTorque = columnLoad + 5;
        int columnSpeed = columnTorque + 5;
        int columnUsagePlan = columnSpeed + 5;
        int columnUsageFact = columnUsagePlan + 1;
        int columnRop = columnUsageFact + 12;

        sheet.Cell(row, column)
            .SetVal(modeData.DrillingMode);

        sheet.Cell(row, columnDeltaDepth)
            .SetVal(modeData.DeltaDepth);

        FillIntervalModeDataParam(sheet, modeData.PressureDiff, columnPressure, row);
        FillIntervalModeDataParam(sheet, modeData.AxialLoad, columnLoad, row);
        FillIntervalModeDataParam(sheet, modeData.TopDriveTorque, columnTorque, row);
        FillIntervalModeDataSpeed(sheet, modeData.SpeedLimit, columnSpeed, row);

        sheet.Cell(row, columnUsagePlan)
            .SetVal(modeData.UsagePlan);

        sheet.Cell(row, columnUsageFact)
            .SetVal(modeData.UsageFact);

        sheet.Cell(row, columnRop)
            .SetVal(modeData.Rop.Fact);

        return row + 1;
    }

    private static void FillIntervalModeDataParam(IXLWorksheet sheet,
        ProcessMapReportWellDrillingParamsDto dataParam, int column, int row)
    {
        const int columnOffsetSpPlan = 0;
        const int columnOffsetSpFact = 1;
        const int columnOffsetFact = 2;
        const int columnOffsetLimit = 3;
        const int columnOffsetPercent = 4;

        sheet.Cell(row, column + columnOffsetSpPlan)
            .SetVal(dataParam.SetpointPlan);

        sheet.Cell(row, column + columnOffsetSpFact)
            .SetVal(dataParam.SetpointFact);

        sheet.Cell(row, column + columnOffsetFact)
            .SetVal(dataParam.Fact);

        sheet.Cell(row, column + columnOffsetLimit)
            .SetVal(dataParam.Limit);

        sheet.Cell(row, column + columnOffsetPercent)
            .SetVal(dataParam.SetpointUsage, format: "0.0");
    }

    private static void FillIntervalModeDataSpeed(IXLWorksheet sheet,
        ProcessMapReportWellDrillingParamsDto dataParam, int column, int row)
    {
        const int columnOffsetSpPlan = 0;
        const int columnOffsetSpFact = 1;
        const int columnOffsetFact = 2;
        const int columnOffsetLimit = 3;
        const int columnOffsetPercent = 4;

        sheet.Cell(row, column + columnOffsetSpPlan)
            .SetVal(dataParam.SetpointPlan);

        sheet.Cell(row, column + columnOffsetSpFact)
            .SetVal(dataParam.SetpointFact);

        sheet.Cell(row, column + columnOffsetFact)
            .SetVal(dataParam.Fact);

        sheet.Cell(row, column + columnOffsetLimit)
            .SetVal(dataParam.Limit);

        sheet.Cell(row, column + columnOffsetPercent)
            .SetVal(dataParam.SetpointUsage, format: "0.0");
    }

    private static Stream GetExcelTemplateStream()
    {
        var stream = Assembly.GetExecutingAssembly()
            .GetManifestResourceStream(
                "AsbCloudInfrastructure.Services.ProcessMaps.Report.ProcessMapReportTemplate.xlsx");

        return stream!;
    }

    private static void SetBorders(IXLStyle style)
    {
        style.Border.RightBorder = XLBorderStyleValues.Thin;
        style.Border.LeftBorder = XLBorderStyleValues.Thin;
        style.Border.TopBorder = XLBorderStyleValues.Thin;
        style.Border.BottomBorder = XLBorderStyleValues.Thin;
        style.Border.InsideBorder = XLBorderStyleValues.Thin;
    }
}