using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbSaubReport;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services
{
    public class ReportService : IReportService
    {
        private readonly IAsbCloudDbContext db;
        private readonly IConfiguration configuration;
        private readonly ITelemetryService telemetryService;
        private readonly IFileService fileService;
        private readonly IReportsBackgroundQueue queue;

        public ReportService(IAsbCloudDbContext db, IConfiguration configuration,
            ITelemetryService telemetryService, IFileService fileService,
            IReportsBackgroundQueue queue)
        {
            this.db = db;
            this.configuration = configuration;
            this.telemetryService = telemetryService;
            this.fileService = fileService;
            this.queue = queue;
            ReportCategoryId = db.FileCategories.AsNoTracking()
                .FirstOrDefault(c =>
                c.Name.Equals("Рапорт")).Id;
        }

        public int ReportCategoryId { get; private set; }

        public int CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin,
            DateTime end, Action<object, int> progressHandler)
        {
            var newReportId = queue.EnqueueTask((id) =>
            {
                var optionsBuilder = new DbContextOptionsBuilder<AsbCloudDbContext>();
                optionsBuilder.UseNpgsql(configuration.GetConnectionString("DefaultConnection"));
                var tempDir = Path.Combine(Path.GetTempPath(), "report");

                using var context = new AsbCloudDbContext(optionsBuilder.Options);

                var generator = GetReportGenerator(idWell, begin, end, stepSeconds, format, context);
                var reportFileName = Path.Combine(tempDir, generator.GetReportDefaultFileName());
                var totalPages = generator.GetPagesCount();

                generator.OnProgress += (s, e) =>
                {
                    progressHandler.Invoke(e.Adapt<ReportProgressDto>(), id);
                };
                generator.Make(reportFileName);

                var fileService = new FileService(context, new GoogleDriveService());
                var fileInfo = fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName).Result;
                
                progressHandler.Invoke(new
                {
                    Operation = "done",
                    Progress = 100f,
                    TotalPages = totalPages,
                    CurrentPage = totalPages,
                    file = fileInfo,
                }, id);

                var newReportProperties = new ReportProperty
                {
                    IdWell = idWell,
                    IdFile = fileInfo.Id,
                    Begin = begin,
                    End = end,
                    Step = stepSeconds,
                    Format = format
                };
                context.ReportProperties.Add(newReportProperties);
                context.SaveChanges();
            });
            progressHandler.Invoke(new ReportProgressDto
            {
                Operation = "Ожидает начала в очереди.",
                Progress = 0f,
            }, newReportId);
            return newReportId;
        }

        public int GetReportPagesCount(int idWell, DateTime begin, DateTime end, int stepSeconds, int format)
        {
            var generator = GetReportGenerator(idWell, begin, end, stepSeconds, format, (AsbCloudDbContext)db);
            return generator.GetPagesCount();
        }

        public async Task<DatesRangeDto> GetReportsDatesRangeAsync(int idWell,
            CancellationToken token = default)
        {
            var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell);

            if (telemetryId is null)
                return null;

            var datesRange = await (from d in db.TelemetryDataSaub
                                    where d.IdTelemetry == telemetryId
                                    select d.Date).Union(
                                    from m in db.TelemetryMessages
                                    where m.IdTelemetry == telemetryId
                                    select m.Date).DefaultIfEmpty()
                                    .GroupBy(g => true)
                                    .AsNoTracking()
                                    .Select(g => new
                                    {
                                        From = g.Min(),
                                        To = g.Max()
                                    }).OrderBy(gr => gr.From)
                                    .FirstOrDefaultAsync(token)
                                    .ConfigureAwait(false);

            return new DatesRangeDto
            {
                From = datesRange.From,
                To = datesRange.To.Year == 1 ? DateTime.MaxValue : datesRange.To
            };
        }

        public Task<List<ReportPropertiesDto>> GetSuitableReportsAsync(int idWell, DateTime begin, DateTime end, int stepSeconds, int format, CancellationToken token) =>
            (from r in db.ReportProperties.Include(r => r.File)
             where r.IdWell == idWell
             && r.Begin >= begin
             && r.End <= end
             && r.Step <= stepSeconds
             && r.Format == format
             select new ReportPropertiesDto
             {
                 Id = r.Id,
                 Name = r.File.Name,
                 File = new FileInfoDto{
                     Id = r.File.Id,
                     Author = null,
                     IdAuthor = r.File.IdAuthor??0,
                     IdCategory = r.File.IdCategory,
                     IdWell = r.File.IdWell,
                     Name = r.File.Name,
                     Size = r.File.Size,
                     UploadDate = r.File.UploadDate,
                 },
                 IdWell = r.IdWell,
                 Date = r.File.UploadDate,
                 Begin = r.Begin,
                 End = r.End,
                 Step = r.Step,
                 Format = r.Format == 0 ? ".pdf" : ".las"
             })
            .OrderBy(o => o.Date)
            .AsNoTracking()
            .Take(512).ToListAsync(token);

        public Task<List<ReportPropertiesDto>> GetAllReportsByWellAsync(int idWell, CancellationToken token) =>
            (from r in db.ReportProperties.Include(r => r.File)
             where r.IdWell == idWell
             select new ReportPropertiesDto
             {
                 Id = r.Id,
                 Name = r.File.Name,
                 File = new FileInfoDto
                 {
                     Id = r.File.Id,
                     Author = null,
                     IdAuthor = r.File.IdAuthor ?? 0,
                     IdCategory = r.File.IdCategory,
                     IdWell = r.File.IdWell,
                     Name = r.File.Name,
                     Size = r.File.Size,
                     UploadDate = r.File.UploadDate,
                 },
                 IdWell = r.IdWell,
                 Date = r.File.UploadDate,
                 Begin = r.Begin,
                 End = r.End,
                 Step = r.Step,
                 Format = r.Format == 0 ? ".pdf" : ".las"
             })
            .OrderBy(o => o.Date)
            .AsNoTracking()
            .Take(1024).ToListAsync(token);

        private static IReportGenerator GetReportGenerator(int idWell, DateTime begin,
            DateTime end, int stepSeconds, int format, AsbCloudDbContext context)
        {
            var dataSource = new ReportDataSourcePgCloud(context, idWell);
            IReportGenerator generator = format switch
            {
                //LAS
                1 => new AsbSaubReportLas.ReprotGeneratorLas(dataSource),
                //PDF
                _ => new AsbSaubReportPdf.ReprotGeneratorPdf(dataSource),
            };
            generator.Begin = begin;
            generator.End = end;
            generator.Step = TimeSpan.FromSeconds(stepSeconds);
            generator.WithCharts = true;
            generator.WithEvents = true;

            return generator;
        }
    }
}