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

namespace AsbCloudInfrastructure.Services.ProcessMaps.Report;

public class ProcessMapReportDataSaubStatExportService : IProcessMapReportDrillingExportService
{
    private const int firstColumn = 2;
    private const int lastColumn = 35;
    private const int headerRowsCount = 5;
    private const string TemplateName = "ProcessMapReportDataSaubStatTemplate.xlsx";
    private const string sheetName = "Отчёт";

    private readonly IWellService wellService;
    private readonly IProcessMapReportDrillingService processMapReportDrillingService;

    public ProcessMapReportDataSaubStatExportService(IWellService wellService,
    IProcessMapReportDrillingService processMapReportDrillingService)
    {
        this.wellService = wellService;
        this.processMapReportDrillingService = processMapReportDrillingService;
    }

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

        if (well is null)
            return null;

        var stream = Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateName);
        using var workbook = new XLWorkbook(stream);

        var data = await processMapReportDrillingService.GetAsync(idWell, request, 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<ProcessMapReportDataSaubStatDto> data)
    {
        var sheet = workbook.GetWorksheet(sheetName);

        var startRow = headerRowsCount + 1;
        foreach (var item in data)
        {
            startRow = FillRow(sheet, item, startRow);
        }
    }


    private static int FillRow(IXLWorksheet sheet, ProcessMapReportDataSaubStatDto item, int startRow)
    {
        var endRow = FillIntervalData(sheet, item, startRow);

        var sectionStyle = sheet.Range(startRow, firstColumn, endRow - 1, lastColumn).Style;
        SetStyle(sectionStyle);
        return endRow;
    }

    private static int FillIntervalData(IXLWorksheet sheet, ProcessMapReportDataSaubStatDto interval, int row)
    {
        const int columnSection = firstColumn + 1;
        const int columnDepthStart = firstColumn + 2;
        const int columnDepthEnd = firstColumn + 3;
        const int columnDeltaDepth = firstColumn + 4;
        const int columnDrilledTime = firstColumn + 5;
        const int columnMode = firstColumn + 6;

        double? deltaDepth = interval.DeltaDepth.HasValue
            ? Math.Round(interval.DeltaDepth.Value, 1)
            : null;

        sheet.Cell(row, firstColumn).SetCellValue(interval.DateStart, "DD.MM.YYYY HH:MM");
        sheet.Cell(row, columnSection).SetCellValue(interval.WellSectionTypeName);
        sheet.Cell(row, columnDepthStart).SetCellValue(Math.Round(interval.DepthStart, 2));
        sheet.Cell(row, columnDepthEnd).SetCellValue(Math.Round(interval.DepthEnd, 2));
        sheet.Cell(row, columnDeltaDepth).SetCellValue(deltaDepth);
        sheet.Cell(row, columnDrilledTime).SetCellValue(Math.Round(interval.DrilledTime, 2));
        sheet.Cell(row, columnMode).SetCellValue(interval.DrillingMode);

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

        return row;
    }

    private static int FillIntervalModeData(IXLWorksheet sheet, ProcessMapReportDataSaubStatDto modeData,
    int column, int row)
    {
        int columnPressure = column + 1;
        int columnLoad = columnPressure + 5;
        int columnTorque = columnLoad + 5;
        int columnSpeed = columnTorque + 5;
        int columnTopDriveSpeed = columnSpeed + 4;
        int columnFlow = columnTopDriveSpeed + 2;
        int columnRopPlan = columnFlow + 3;
        int columnRopFact = columnRopPlan + 1;

        FillIntervalModeDataParam(sheet, modeData.PressureDiff, columnPressure, row);
        FillIntervalModeDataParam(sheet, modeData.AxialLoad, columnLoad, row);
        FillIntervalModeDataParam(sheet, modeData.TopDriveTorque, columnTorque, row);
        FillIntervalModeDataSpeed(sheet, modeData.SpeedLimit, columnSpeed, row);
        FillIntervalModeTopDriveSpeed(sheet, modeData.TopDriveSpeed, columnTopDriveSpeed, row);
        FillIntervalModeFlow(sheet, modeData.Flow, columnFlow, row);

        double? ropPlan = modeData.Rop.Plan.HasValue
            ? Math.Round(modeData.Rop.Plan.Value, 1)
            : null;

        double? ropFact = modeData.Rop.Fact.HasValue
            ? Math.Round(modeData.Rop.Fact.Value, 1)
            : null;

        sheet.Cell(row, columnRopPlan).SetCellValue(ropPlan);
        sheet.Cell(row, columnRopFact).SetCellValue(ropFact);

        return row + 1;
    }

    private static void FillIntervalModeDataParam(IXLWorksheet sheet,
    ProcessMapReportDataSaubStatParamsDto 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;

        double? setpointPlan = dataParam.SetpointPlan.HasValue
            ? Math.Round(dataParam.SetpointPlan.Value)
            : null;

        double? setpointFact = dataParam.SetpointFact.HasValue
            ? Math.Round(dataParam.SetpointFact.Value, 1)
            : null;

        double? factWavg = dataParam.FactWavg.HasValue
            ? Math.Round(dataParam.FactWavg.Value, 1)
            : null;

        double? limit = dataParam.Limit.HasValue
           ? Math.Round(dataParam.Limit.Value, 1)
           : null;

        double? setpointUsage = dataParam.SetpointUsage.HasValue
           ? Math.Round(dataParam.SetpointUsage.Value, 1)
           : null;

        sheet.Cell(row, column + columnOffsetSpPlan).SetCellValue(setpointPlan);
        sheet.Cell(row, column + columnOffsetSpFact).SetCellValue(setpointFact);
        sheet.Cell(row, column + columnOffsetFact).SetCellValue(factWavg);
        sheet.Cell(row, column + columnOffsetLimit).SetCellValue(limit);
        sheet.Cell(row, column + columnOffsetPercent).SetCellValue(setpointUsage);
    }

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

        double? setpointPlan = dataParam.SetpointPlan.HasValue
            ? Math.Round(dataParam.SetpointPlan.Value)
            : null;

        double? setpointFact = dataParam.SetpointFact.HasValue
            ? Math.Round(dataParam.SetpointFact.Value, 1)
            : null;

        double? factWavg = dataParam.FactWavg.HasValue
            ? Math.Round(dataParam.FactWavg.Value, 1)
            : null;

        double? setpointUsage = dataParam.SetpointUsage.HasValue
           ? Math.Round(dataParam.SetpointUsage.Value, 1)
           : null;

        sheet.Cell(row, column + columnOffsetSpPlan).SetCellValue(setpointPlan);
        sheet.Cell(row, column + columnOffsetSpFact).SetCellValue(setpointFact);
        sheet.Cell(row, column + columnOffsetFact).SetCellValue(factWavg);
        sheet.Cell(row, column + columnOffsetPercent).SetCellValue(setpointUsage);
    }

    private static void FillIntervalModeTopDriveSpeed(IXLWorksheet sheet,
    ProcessMapReportDataSaubStatParamsDto dataParam, int column, int row)
    {
        const int columnOffsetSpPlan = 0;
        const int columnOffsetFact = 1;

        double? setpointPlan = dataParam.SetpointPlan.HasValue
            ? Math.Round(dataParam.SetpointPlan.Value)
            : null;

        double? factWavg = dataParam.FactWavg.HasValue
            ? Math.Round(dataParam.FactWavg.Value, 1)
            : null;

        sheet.Cell(row, column + columnOffsetSpPlan).SetCellValue(setpointPlan);
        sheet.Cell(row, column + columnOffsetFact).SetCellValue(factWavg);
    }

    private static void FillIntervalModeFlow(IXLWorksheet sheet,
    ProcessMapReportDataSaubStatParamsDto dataParam, int column, int row)
    {
        const int columnOffsetSpPlan = 0;
        const int columnOffsetFact = 1;
        const int columnOffsetLimit = 2;

        double? setpointPlan = dataParam.SetpointPlan.HasValue
            ? Math.Round(dataParam.SetpointPlan.Value)
            : null;

        double? factWavg = dataParam.FactWavg.HasValue
            ? Math.Round(dataParam.FactWavg.Value, 1)
            : null;

        double? limit = dataParam.Limit.HasValue
           ? Math.Round(dataParam.Limit.Value, 1)
           : null;

        sheet.Cell(row, column + columnOffsetSpPlan).SetCellValue(setpointPlan);
        sheet.Cell(row, column + columnOffsetFact).SetCellValue(factWavg);
        sheet.Cell(row, column + columnOffsetLimit).SetCellValue(limit);
    }

    private static void SetStyle(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;

        style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
    }
}