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

namespace AsbCloudInfrastructure.Services
{
    public class DrillingProgramService : IDrillingProgramService
    {
        private readonly IFileService fileService;
        private readonly IWellService wellService;
        private const int idFileCategoryDrillingProgramItems = 13;
        private const int idFileCategoryDrillingProgram = 14;

        private const int maxAllowedColumns = 256;

        public DrillingProgramService(IFileService fileService, IWellService wellService)
        {
            this.fileService = fileService;
            this.wellService = wellService;
        }

        public async Task<string> GetOrCreateSharedUrlAsync(int idWell, int idUser, IFileShareService fileShareService, CancellationToken token = default)
        {
            var fileInfo = await GetOrCreateAsync(idWell, idUser, token)
                .ConfigureAwait(false);

            if (fileInfo is null)
                return null;

            var sharedUrl = await fileService.GetSharedUrlAsync(fileInfo, idUser, fileShareService, token)
                .ConfigureAwait(false);

            return sharedUrl;
        }

        public async Task<FileInfoDto> GetOrCreateAsync(int idWell, int idUser, CancellationToken token = default)
        {
            var programParts = (await fileService.GetInfosByCategoryAsync(idWell, idFileCategoryDrillingProgramItems, token)
                .ConfigureAwait(false))
                .Where(f => f.FileMarks?.Any(m => m.IdMarkType == 1 && !m.IsDeleted)??false);

            var well = await wellService.GetAsync(idWell, token)
                .ConfigureAwait(false);

            var programs = await fileService.GetInfosByCategoryAsync(idWell, idFileCategoryDrillingProgram, token)
                .ConfigureAwait(false);
            
            if (programs is not null && programs.Any() && programParts.Any())
            {
                programs = programs.OrderByDescending(f => f.UploadDate);
                var matchFilesIterator = programs.GetEnumerator();
                matchFilesIterator.MoveNext();
                var matchFile = matchFilesIterator.Current;

                if (programParts.All(pp => matchFile.UploadDate > pp.UploadDate) &&
                    File.Exists(fileService.GetUrl(matchFile)))
                    return matchFile;
                else
                    await fileService.DeleteAsync(matchFile.Id, token)
                        .ConfigureAwait(false);

                while (matchFilesIterator.MoveNext())
                    await fileService.DeleteAsync(matchFilesIterator.Current.Id, token)
                        .ConfigureAwait(false);
            }

            if (!programParts.Any())
                throw new FileNotFoundException("Нет частей для формирования программы бурения");

            var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx";
            
            var filteredFilePaths = programParts
                .Select(file => fileService.GetUrl(file));

            var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName);

            UniteExcelFiles(filteredFilePaths, tempResultFilePath);

            var fileInfo = await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram,
                resultFileName, tempResultFilePath, token).ConfigureAwait(false);

            return fileInfo;
        }

        public async Task<int> CreateFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token)
        {
            var fileInfo = await fileService.GetInfoAsync(fileMarkDto.IdFile, token)
                .ConfigureAwait(false);

            if (fileInfo.IdCategory != idFileCategoryDrillingProgramItems)
                throw new ArgumentException($"Этот метод допустим только для файлов-частей программы бурения idCategory=={idFileCategoryDrillingProgramItems}.", nameof(fileMarkDto));

            var result = await fileService.CreateFileMarkAsync(fileMarkDto, idUser, token)
                .ConfigureAwait(false);

            var drillingPrograms = await fileService.GetInfosByCategoryAsync(fileInfo.IdWell, idFileCategoryDrillingProgram, token)
                .ConfigureAwait(false);

            foreach (var drillingProgram in drillingPrograms)
                await fileService.DeleteAsync(drillingProgram.Id, token)
                    .ConfigureAwait(false);

            return result;
        }

        public async Task<int> MarkFileMarkAsDeletedAsync(int idMark,
            CancellationToken token)
        {
            var fileInfo = await fileService.GetByMarkId(idMark, token)
                .ConfigureAwait(false);

            if (fileInfo.IdCategory != idFileCategoryDrillingProgramItems)
                throw new ArgumentException($"Этот метод допустим только для файлов-частей программы бурения idCategory=={idFileCategoryDrillingProgramItems}.", nameof(idMark));

            var result = await fileService.MarkFileMarkAsDeletedAsync(idMark, token)
                .ConfigureAwait(false);

            var drillingPrograms = await fileService.GetInfosByCategoryAsync(fileInfo.IdWell, idFileCategoryDrillingProgram, token)
                .ConfigureAwait(false);

            foreach (var drillingProgram in drillingPrograms)
                await fileService.DeleteAsync(drillingProgram.Id, token)
                    .ConfigureAwait(false);

            return result;
        }

        private static void UniteExcelFiles(IEnumerable<string> excelFilesNames, string resultExcelPath)
        {
            var resultExcelFile = new XLWorkbook(XLEventTracking.Disabled);

            var filteredFileNames = excelFilesNames.Distinct();

            foreach (var excelFileName in filteredFileNames)
            {
                using var workbookSrc = new XLWorkbook(excelFileName, XLEventTracking.Disabled);

                foreach (var sheet in workbookSrc.Worksheets)
                {
                    if(sheet.Visibility == XLWorksheetVisibility.Visible)
                        CopySheet(resultExcelFile, sheet);
                }
            }

            resultExcelFile.SaveAs(resultExcelPath, 
                new SaveOptions { EvaluateFormulasBeforeSaving = true});
        }

        private static void CopySheet(XLWorkbook workbookDst, IXLWorksheet sheetSrc)
        {
            var newSheetName = sheetSrc.Name;
            var suffix = "";
            int index = 1;

            while (workbookDst.Worksheets.Contains(newSheetName))
            {
                newSheetName = sheetSrc.Name;
                suffix = $"_{index++}";
                if (newSheetName.Length + suffix.Length >= 31)
                    newSheetName = newSheetName[..(31 - suffix.Length)];
                newSheetName += suffix;
            }

            var imagesInfos = sheetSrc.Pictures.Select(p => new ImageInfo
            {
                Id = p.Id,
                Data = p.ImageStream.GetBuffer(),
                Height = p.Height,
                Width = p.Width,
                TopLeftCellAddress = p.TopLeftCell.Address,
                Left = p.Left,
                Top = p.Top
            }).ToList();

            IXLWorksheet resultSheet;

            if (sheetSrc.Columns().Count() > maxAllowedColumns)
            {
                resultSheet = workbookDst.Worksheets.Add(newSheetName);

                var rngData = GetCellsRange(sheetSrc);

                rngData.CopyTo(resultSheet.Cell(1, 1));

                var lastRowWithData = rngData.LastRowUsed().RangeAddress
                    .LastAddress.RowNumber;

                for (int i = 1; i < lastRowWithData; i++)
                {
                    resultSheet.Row(i).Height = sheetSrc.Row(i).Height;
                    resultSheet.Column(i).Width = sheetSrc.Column(i).Width;
                }
            }
            else
            {
                RemovePicturesFromSheet(sheetSrc);
                resultSheet = sheetSrc.CopyTo(workbookDst, newSheetName);
            }
            CopyImagesToAnotherSheet(imagesInfos, resultSheet);
        }

        private static IXLWorksheet CopyImagesToAnotherSheet(IEnumerable<ImageInfo> imagesInfos,
            IXLWorksheet resultSheet)
        {
            foreach (var image in imagesInfos)
            {
                var stream = new MemoryStream();
                stream.Write(image.Data, 0, image.Data.Length);

                resultSheet.AddPicture(stream)
                           .WithPlacement(XLPicturePlacement.Move)
                           .WithSize(image.Width, image.Height)
                           .MoveTo(resultSheet.Cell(image.TopLeftCellAddress),
                                   image.Left, image.Top);
            }

            return resultSheet;
        }

        private static void RemovePicturesFromSheet(IXLWorksheet sheet)
        {
            var filteredPics = sheet.Pictures.Select(p => p.Name).Distinct().ToList();

            foreach (var n in filteredPics)
                sheet.Pictures.Delete(n);
        }

        private static IXLRange GetCellsRange(IXLWorksheet sheet)
        {
            var firstTableCell = sheet.FirstCellUsed();
            var lastTableCell = sheet.LastCellUsed();
            var rngData = sheet.Range(firstTableCell.Address, lastTableCell.Address);

            return rngData;
        }
    }

    class ImageInfo
    {
        public int Id { get; set; }
        public byte[] Data { get; set; }
        public int Height { get; set; }
        public int Width { get; set; }
        public IXLAddress TopLeftCellAddress { get; set; }
        public int Left { get; set; }
        public int Top { get; set; }
    }
}