using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.Csv;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services.SAUB;

class TelemetryNewDataSaubDto : TelemetryDataSaubDto
{
    public new DateTimeOffset DateTime { get; set; }
}

public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSaubDto, TelemetryDataSaub>, ITelemetryDataSaubService
{
    private readonly ITelemetryUserService telemetryUserService;

    public TelemetryDataSaubService(
        IAsbCloudDbContext db,
        ITelemetryService telemetryService,
        ITelemetryUserService telemetryUserService,
        ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
        : base(db, telemetryService, telemetryDataCache)
    {
        this.telemetryUserService = telemetryUserService;
    }

    public async Task<IEnumerable<TelemetryDataSaubDto>> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token)
    {
        var offset = telemetryService.GetTimezone(idTelemetry).Offset;
        var geDateUtc = geDate.ToUniversalTime();
        var leDateUtc = leDate.ToUniversalTime();

        var query = db.Set<TelemetryDataSaub>()
            .Where(t => t.IdTelemetry == idTelemetry)
            .Where(t => t.DateTime >= geDateUtc)
            .Where(t => t.DateTime <= leDateUtc);

        if (isBitOnBottom)
            query = query.Where(t => Math.Abs(t.BitDepth - t.WellDepth) < 0.0001);

        query = query
            .OrderBy(t => t.DateTime)
            .Take(take);

        var entities = await query.ToArrayAsync(token);
        var dtos = entities.Select(e => Convert(e, offset.TotalHours));
        return dtos;
    }

    public async Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token)
    {
        var timezone = telemetryService.GetTimezone(idTelemetry);
        var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
        int[] modes = new int[] { 0, 1, 3 };

        db.Database.SetCommandTimeout(TimeSpan.FromMinutes(1.5));

        var query = db.Set<TelemetryDataSaub>()
            .Where(t => t.IdTelemetry == idTelemetry)
            .Where(t => t.BlockPosition > 0.0001)
            .Where(t => t.WellDepth > 0.0001)
            .Where(t => modes.Contains(t.Mode))
            .Where(t => t.WellDepth - t.BitDepth < 0.01)
            .GroupBy(t => new
            {
                t.DateTime.Hour,
                WellDepthX10 = Math.Truncate(t.WellDepth * 10),
                t.Mode,
                t.IdFeedRegulator
            })
            .Select(g => new TelemetryDataSaubStatDto
            {
                Count = g.Count(),
                IdMode = g.Key.Mode,
                IdFeedRegulator = g.Key.IdFeedRegulator,

                DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
                DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),

                WellDepthMin = g.Min(t => t.WellDepth),
                WellDepthMax = g.Max(t => t.WellDepth),

                Pressure = g.Average(t => t.Pressure),
                PressureSp = g.Average(t => t.PressureSp!.Value),
                PressureIdle = g.Average(t => t.PressureIdle!.Value),
                PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value),
                PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value),
                PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value),

                AxialLoad = g.Average(t => t.AxialLoad),
                AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value),
                AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value),

                RotorTorque = g.Average(t => t.RotorTorque),
                RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value),
                RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value),

                BlockSpeed = g.Average(t => t.BlockSpeed!.Value),
                BlockSpeedSp = g.Average(t => t.BlockSpeedSp!.Value),
            })
            .Where(s => s.WellDepthMin != s.WellDepthMax)
            .Where(s => s.Count > 3)
            .OrderBy(t => t.DateMin);

        return await query.ToArrayAsync(token);
    }

    protected override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
    {
        var entity = src.Adapt<TelemetryDataSaub>();
        var telemetryUser = telemetryUserService
            .GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User))
            .FirstOrDefault();
        entity.IdUser = telemetryUser?.Id;
        entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
        return entity;
    }

    protected override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
    {
        var dto = src.Adapt<TelemetryDataSaubDto>();
        var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser ?? int.MinValue);
        dto.User = telemetryUser?.MakeDisplayName();
        dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);
        dto.BitDepth = src.BitDepth <= src.WellDepth
            ? src.BitDepth
            : src.WellDepth;
        return dto;
    }

    public async Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
    {
        double intervalSec = (endDate - beginDate).TotalSeconds;
        if (intervalSec > 60 * 60 * 24 * 3)
            throw new ArgumentInvalidException(nameof(endDate), "Слишком большой диапазон");

        var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
            ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина id:{idWell} не содержит телеметрии");

        var approxPointsCount = intervalSec switch
        {
            < 2048 => 2048,
            < 8_192 => 4_096,
            < 131_072 => 16_384,
            _ => 32_768
        };

        var data = await GetByWellAsync(idWell, beginDate, intervalSec, approxPointsCount, token);

        var fileName = $"DataSaub idWell{idWell}";
        if (telemetry.Info is not null)
            fileName += $" {telemetry.Info?.Cluster}, {telemetry.Info?.Well}";
        fileName += $" {beginDate:yyyy-MM-DDTHH-mm} - {endDate:yyyy-MM-DDTHH-mm}.csv";

        var outStream = new MemoryStream();
        using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
        {
            var entryFile = archive.CreateEntry(fileName, CompressionLevel.Optimal);
            using var entryStream = entryFile.Open();
            var serializer = new CsvSerializer<TelemetryDataSaubDto>();
            serializer.Serialize(data, entryStream);
        }
        outStream.Seek(0, SeekOrigin.Begin);
        return outStream;
    }
}