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

namespace AsbCloudInfrastructure.Services.Trajectory
{
#nullable enable
    public class PlannedTrajectoryImportService : IPlannedTrajectoryImportService
    {
        /*
        * password for PlannedTrajectoryTemplate.xlsx is Drill2022
        */

        private readonly IWellService wellService;
        private readonly IPlannedTrajectoryRepository plannedTrajectoryService;

        private const string templateFileName = "PlannedTrajectoryTemplate.xlsx";
        private const string usingTemplateFile = "AsbCloudInfrastructure.Services.PlannedTrajectory";
        private const string sheetNamePlannedTrajectory = "Плановая траектория";
        private const int headerRowsCount = 2;
        private const int ColumnWellboreDepth = 1;
        private const int ColumnZenithAngle = 2;
        private const int ColumnAzimuthGeo = 3;
        private const int ColumnAzimuthMagnetic = 4;
        private const int ColumnVerticalDepth = 5;
        private const int ColumnAbsoluteMark = 6;
        private const int ColumnNorthOrifice = 7;
        private const int ColumnEastOrifice = 8;
        private const int ColumnEastCartographic = 9;
        private const int ColumnNorthCartographic = 10;
        private const int ColumnSpatialIntensity = 11;
        private const int ColumnAngleIntensity = 12;
        private const int ColumnAzimuthIntensity = 13;
        private const int ColumnOrificeOffset = 14;
        private const int ColumnComment = 15;

        public PlannedTrajectoryImportService(IWellService wellService, IPlannedTrajectoryRepository plannedTrajectoryService)
        {

            this.wellService = wellService;
            this.plannedTrajectoryService = plannedTrajectoryService;
        }

        public Stream GetTemplateFile()
        {
            var stream = System.Reflection.Assembly.GetExecutingAssembly()
                .GetManifestResourceStream($"{usingTemplateFile}.{templateFileName}");
            if (stream is null)
                throw new Exception($"Область {usingTemplateFile} не содержит файла с названием {templateFileName}");
            return stream;
        }

        public async Task<string> GetFileNameAsync(int idWell, CancellationToken token)
        {
            var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_plannedTrajectory.xlsx";
            return fileName;
        }

        public async Task<Stream> ExportAsync(int idWell, CancellationToken token)
        {
            var plannedTrajectorys = await plannedTrajectoryService.GetAsync(idWell, token);
            return MakeExelFileStream(plannedTrajectorys);
        }

        private Stream MakeExelFileStream(IEnumerable<PlannedTrajectoryDto> plannedTrajectories)
        {
            using Stream ecxelTemplateStream = GetTemplateFile();
            using var workbook = new XLWorkbook(ecxelTemplateStream, XLEventTracking.Disabled);
            AddPlannedTrajecoryToWorkbook(workbook, plannedTrajectories);
            MemoryStream memoryStream = new MemoryStream();
            workbook.SaveAs(memoryStream, new SaveOptions { });
            memoryStream.Seek(0, SeekOrigin.Begin);
            return memoryStream;
        }

        private static void AddPlannedTrajecoryToWorkbook(XLWorkbook workbook, IEnumerable<PlannedTrajectoryDto> plannedTrajectories)
        {
            if (plannedTrajectories.Any())
            {
                var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlannedTrajectory);
                if (sheet is null)
                    throw new FileFormatException($"Лист с именем {sheetNamePlannedTrajectory} отсутствует, либо имеет некорректное название");
                AddPlannedTrajecoryToSheet(sheet, plannedTrajectories);
            }
        }

        private static void AddPlannedTrajecoryToSheet(IXLWorksheet sheet, IEnumerable<PlannedTrajectoryDto> plannedTrajectories)
        {
            var rowList = plannedTrajectories.ToList();
            for (int i = 0; i < rowList.Count; i++)
            {
                var row = sheet.Row(1 + i + headerRowsCount);
                AddCoordinatesToRow(row, rowList[i]);
            }
        }
        private static void AddCoordinatesToRow(IXLRow row, PlannedTrajectoryDto trajectory)
        {
            row.Cell(ColumnWellboreDepth).Value = trajectory.WellboreDepth;
            row.Cell(ColumnZenithAngle).Value = trajectory.ZenithAngle;
            row.Cell(ColumnAzimuthGeo).Value = trajectory.AzimuthGeo;
            row.Cell(ColumnAzimuthMagnetic).Value = trajectory.AzimuthMagnetic;
            row.Cell(ColumnVerticalDepth).Value = trajectory.VerticalDepth;
            row.Cell(ColumnAbsoluteMark).Value = trajectory.AbsoluteMark;
            row.Cell(ColumnNorthOrifice).Value = trajectory.NorthOrifice;
            row.Cell(ColumnEastOrifice).Value = trajectory.EastOrifice;
            row.Cell(ColumnEastCartographic).Value = trajectory.EastCartographic;
            row.Cell(ColumnNorthCartographic).Value = trajectory.NorthCartographic;
            row.Cell(ColumnSpatialIntensity).Value = trajectory.SpatialIntensity;
            row.Cell(ColumnAngleIntensity).Value = trajectory.AngleIntensity;
            row.Cell(ColumnAzimuthIntensity).Value = trajectory.AzimuthIntensity;
            row.Cell(ColumnOrificeOffset).Value = trajectory.OrificeOffset;
            row.Cell(ColumnComment).Value = trajectory.Comment;
        }

        public async Task<int> ImportAsync(int idWell, int idUser, Stream stream, bool deletePrevRows, CancellationToken token)
        {
            using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
            var trajectoryRows = ParseFileStream(stream);
            foreach (var row in trajectoryRows)
            {
                row.IdWell = idWell;
                row.IdUser = idUser;
            }

            var rowsCount = await SavePlannedTrajectoryAsync(idWell, trajectoryRows, deletePrevRows, token);
            return rowsCount;
        }

        private async Task<int> SavePlannedTrajectoryAsync(int idWell, IEnumerable<PlannedTrajectoryDto> newRows, bool deletePrevRow, CancellationToken token)
        {
            if (deletePrevRow)
                await plannedTrajectoryService.DeleteByIdWellAsync(idWell, token);
            var rowsCount = await plannedTrajectoryService.AddRangeAsync(newRows, token);
            return rowsCount;
        }

        private IEnumerable<PlannedTrajectoryDto> ParseFileStream(Stream stream)
        {
            using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
            return ParseWorkbook(workbook);
        }

        private IEnumerable<PlannedTrajectoryDto> ParseWorkbook(IXLWorkbook workbook)
        {
            var sheetPlannedTrajectory = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlannedTrajectory);
            if (sheetPlannedTrajectory is null)
                throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlannedTrajectory}.");
            var plannedTrajectoryRows = ParseSheet(sheetPlannedTrajectory);
            return plannedTrajectoryRows;
        }

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

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

            if (count > 1024)
                throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк.");

            if (count <= 0)
                throw new FileFormatException($"Лист {sheet.Name} некорректного формата либо пустой");

            var trajectoryRows = new List<PlannedTrajectoryDto>(count);
            var parseErrors = new List<string>();
            for (int i = 0; i < count; i++)
            {
                var row = sheet.Row(1 + i + headerRowsCount);
                try
                {
                    var trajectoryRow = ParseRow(row);
                    trajectoryRows.Add(trajectoryRow);
                }
                catch (FileFormatException ex)
                {
                    parseErrors.Add(ex.Message);
                }
            };
            return trajectoryRows;
        }

        private PlannedTrajectoryDto ParseRow(IXLRow row)
        {
            var _wellboreDepth = row.Cell(ColumnWellboreDepth).Value;
            var _zenithAngle = row.Cell(ColumnZenithAngle).Value;
            var _azimuthGeo = row.Cell(ColumnAzimuthGeo).Value;
            var _azimuthMagnetic = row.Cell(ColumnAzimuthMagnetic).Value;
            var _verticalDepth = row.Cell(ColumnVerticalDepth).Value;
            var _absoluteMark = row.Cell(ColumnAbsoluteMark).Value;
            var _northOrifice = row.Cell(ColumnNorthOrifice).Value;
            var _eastOrifice = row.Cell(ColumnEastOrifice).Value;
            var _eastCartographic = row.Cell(ColumnEastCartographic).Value;
            var _northCartographic = row.Cell(ColumnNorthCartographic).Value;
            var _spatialIntensity = row.Cell(ColumnSpatialIntensity).Value;
            var _angleIntensity = row.Cell(ColumnAngleIntensity).Value;
            var _azimuthIntensity = row.Cell(ColumnAzimuthIntensity).Value;
            var _orificeOffset = row.Cell(ColumnOrificeOffset).Value;
            var _comment = row.Cell(ColumnComment).Value;

            var trajectoryRow = new PlannedTrajectoryDto();

            static double getDoubleValue(object value, string nameParam, IXLRow row)
            {
                if (value is double _value)
                    return _value;
                throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} - некорректные данные - {nameParam}");
            }

            trajectoryRow.WellboreDepth = getDoubleValue(_wellboreDepth, "Глубина по стволу", row);
            trajectoryRow.ZenithAngle = getDoubleValue(_zenithAngle, "Зенитный угол", row);
            trajectoryRow.AzimuthGeo = getDoubleValue(_azimuthGeo, "Азимут географический", row);
            trajectoryRow.AzimuthMagnetic = getDoubleValue(_azimuthMagnetic, "Азимут магнитный", row);
            trajectoryRow.VerticalDepth = getDoubleValue(_verticalDepth, "Глубина вертикальная", row);
            trajectoryRow.AbsoluteMark = getDoubleValue(_absoluteMark, "Абсолютная отметка", row);
            trajectoryRow.NorthOrifice = getDoubleValue(_northOrifice, "Север относительно устья", row);
            trajectoryRow.EastOrifice = getDoubleValue(_eastOrifice, "Восток относительно устья", row);
            trajectoryRow.EastCartographic = getDoubleValue(_eastCartographic, "Восток картографический", row);
            trajectoryRow.NorthCartographic = getDoubleValue(_northCartographic, "Север картографический", row);
            trajectoryRow.SpatialIntensity = getDoubleValue(_spatialIntensity, "Простр. интенсивность", row);
            trajectoryRow.AngleIntensity = getDoubleValue(_angleIntensity, "Интенсивность по углу", row);
            trajectoryRow.AzimuthIntensity = getDoubleValue(_azimuthIntensity, "Интенсивность по азимуту", row);
            trajectoryRow.OrificeOffset = getDoubleValue(_orificeOffset, "Смещение от устья", row);
            if (_comment is not null)
                trajectoryRow.Comment = _comment.ToString();
            return trajectoryRow;
        }
    }
#nullable disable
}