using AsbCloudApp.Data;
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;
using System.Text.Csv;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services.SAUB
{

    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<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token)
        {
            var timezone = telemetryService.GetTimezone(idTelemetry);
            var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
            int[] modes = new int[] { 0, 1, 3 };

            var query = db.Set<TelemetryDataSaub>()
                .Where(t => t.IdTelemetry == idTelemetry)
                .Where(t => t.BlockPosition > 0.0001)
                .Where(t => t.WellDepth > 0.0001)
                .Where(t => t.Mode != null)
                .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);
        }

        public 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;
        }

        public 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 GetAsync(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;
        }
    }

}