using AsbCloudApp.Data;
using AsbCloudApp.Data.Progress;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Background;
using AsbSaubReport;
using Mapster;
using Microsoft.EntityFrameworkCore;
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 ITelemetryService telemetryService;
    private readonly FileService fileService;
    private readonly IWellService wellService;
    private readonly BackgroundWorker backgroundWorkerService;

    public ReportService(IAsbCloudDbContext db,
        ITelemetryService telemetryService,
        IWellService wellService,
        FileService fileService,
        BackgroundWorker backgroundWorkerService)
    {
        this.db = db;
        this.wellService = wellService;
        this.backgroundWorkerService = backgroundWorkerService;
        this.telemetryService = telemetryService;
        this.fileService = fileService;
    }

    public string EnqueueCreateReportWork(int idWell, int idUser, ReportParametersRequest request, Action<object, string> progressHandler)
    {
        var work = new WorkToCreateReport(idWell, idUser, request, progressHandler);

        work.OnErrorAsync = (message, exception, token) => Task.Run(() => {
            var state = new ProgressExceptionDto
            {
                Operation = "error",
                Progress = 100f,
                Message = string.IsNullOrEmpty(message)
                    ? exception.Message
                    : message,
                Exception = exception,
            };
            progressHandler.Invoke(state, work.Id);
            }, token);

        progressHandler.Invoke(new ReportProgressDto
        {
            Operation = "Ожидает начала в очереди.",
            Progress = 0f,
        }, work.Id);

        backgroundWorkerService.Enqueue(work);
       
        return work.Id;
    }

    public int GetReportPagesCount(int idWell, DateTimeOffset begin, DateTimeOffset end, int stepSeconds, int format)
    {
        var timezoneOffset = wellService.GetTimezone(idWell).Hours;
        var beginRemote = begin.DateTime.ToTimeZoneOffsetHours(timezoneOffset);
        var endRemote = end.DateTime.ToTimeZoneOffsetHours(timezoneOffset);

        var generator = GetReportGenerator(idWell, beginRemote, endRemote, stepSeconds, format, db);
        var pagesCount = generator.GetPagesCount();
        return pagesCount;
    }

    public DatesRangeDto? GetDatesRangeOrDefault(int idWell)
    {
        var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
        if (telemetry is null)
            return null;
        var range = telemetryService.GetDatesRange(telemetry.Id);
        return range;
    }

    public async Task<IEnumerable<ReportPropertiesDto>> GetAllReportsByWellAsync(int idWell, CancellationToken token)
    {
        var timezoneOffset = wellService.GetTimezone(idWell).Hours;
        var timeSpan = TimeSpan.FromHours(timezoneOffset);
        var propertiesQuery = db.ReportProperties.Include(r => r.File)
         .Where(p => p.IdWell == idWell)
        .OrderBy(o => o.File.UploadDate)
        .AsNoTracking()
        .Take(1024);
        var entities = await propertiesQuery.ToListAsync(token);
        var dtos = entities.Select(p => new ReportPropertiesDto
        {
            Id = p.Id,
            Name = p.File.Name,
            File = new FileInfoDto
            {
                Id = p.File.Id,
                Author = null,
                IdAuthor = p.File.IdAuthor ?? 0,
                IdCategory = p.File.IdCategory,
                IdWell = p.File.IdWell,
                Name = p.File.Name,
                Size = p.File.Size,
                UploadDate = p.File.UploadDate.ToOffset(timeSpan),
            },
            IdWell = p.IdWell,
            Date = p.File.UploadDate.ToOffset(timeSpan),
            Begin = p.Begin.ToOffset(timeSpan),
            End = p.End.ToOffset(timeSpan),
            Step = p.Step,
            Format = p.Format == 0 ? ".pdf" : ".las"
        });
        return dtos;
    }

    public async Task CreateReportAsync(
        string workId,
        int idWell,
        int idUser,
        ReportParametersRequest request,
        Action<ProgressDto, string> progressHandler,
        CancellationToken token)
    {
        var timezoneOffset = wellService.GetTimezone(idWell).Hours;
        var beginRemote = request.Begin.DateTime;
        var endRemote = request.End.DateTime;
        var beginUtc = request.Begin.ToUniversalTime();
        var endUtc = request.End.ToUniversalTime();

        var tempDir = Path.Combine(Path.GetTempPath(), "report");

        var generator = GetReportGenerator(idWell, beginRemote, endRemote, request.StepSeconds, request.Format, db);
        var reportFileName = Path.Combine(tempDir, generator.GetReportDefaultFileName());
        var totalPages = generator.GetPagesCount();

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

        var ReportCategoryId = db.FileCategories
            .AsNoTracking()
            .First(c => c.Name.Equals("Рапорт"))
            .Id;

        var fileInfo = (await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token))!;

        if (fileInfo == null)
        {
            var state = new ProgressExceptionDto
            {
                Operation = "error",
                Progress = 100f,
                Message = "Не удалось сгенерировать файл отчёта",
                Exception = new FileNotFoundException(),
            };
            progressHandler(state, workId);

            return;
        }

        progressHandler(new ReportProgressFinalDto()
        {
            Operation = "done",
            Progress = 100f,
            TotalPages = totalPages,
            CurrentPage = totalPages,
            file = fileInfo,
        }, workId);

        var newReportProperties = new ReportProperty
        {
            IdWell = idWell,
            IdFile = fileInfo.Id,
            Begin = beginUtc,
            End = endUtc,
            Step = request.StepSeconds,
            Format = request.Format
        };
        db.ReportProperties.Add(newReportProperties);
        db.SaveChanges();
    }

    private static IReportGenerator GetReportGenerator(int idWell, DateTime begin,
        DateTime end, int stepSeconds, int format, IAsbCloudDbContext context)
    {
        var dataSource = new ReportDataSourcePgCloud(context, idWell);
        IReportGenerator generator = format switch
        {
            //LAS
            1 => new AsbSaubReportLas.ReprotGeneratorLas(dataSource),
            //PDF
            _ => new AsbSaubReportPdf.ReprotGeneratorPdf(dataSource),
        };

        if (begin == default || end == default)
        {
            var analyzeResult = dataSource.Analyze();
            begin = begin == default ? analyzeResult.MinDate : begin;
            end = end == default ? begin.AddDays(1) : end;
        }

        generator.Begin = begin;
        generator.End = end;
        generator.Step = TimeSpan.FromSeconds(stepSeconds);
        generator.WithCharts = true;
        generator.WithEvents = true;

        return generator;
    }

    public async Task<int> DeleteAllOldReportsAsync(TimeSpan lifetime, CancellationToken token)
    {
        var lifeTimeStartDate = DateTimeOffset.UtcNow.Date - lifetime;
        var fileIds = await db.ReportProperties
            .Where(r => r.File.UploadDate.Date < lifeTimeStartDate)
            .Select(r => r.IdFile)
            .ToArrayAsync(token);

        return await fileService.DeleteAsync(fileIds, token);
    }
}