using AsbCloudApp.Exceptions;
using AsbCloudDb.Model;
using AsbSaubReport.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;

namespace AsbCloudInfrastructure;

public class ReportDataSourcePgCloud : IReportDataSource
{
    private const string DefaultTimezoneId = "Asia/Yekaterinburg";
    private readonly IAsbCloudDbContext context;

    private readonly int? idTelemetry;
    private readonly WellInfoReport info;

    private readonly Dictionary<int, TelemetryEvent> events;
    private readonly Dictionary<int, TelemetryUser> users;
    private readonly double timezoneOffset;
    private readonly Dictionary<int, string> categories = new Dictionary<int, string>
    {
        {1, "Авария"},
        {2, "Предупреждение"},
        {3, "Информация"},
    };

    public ReportDataSourcePgCloud(IAsbCloudDbContext context, int idWell)
    {
        this.context = context;

        var well = context.Wells
            .Include(w => w.Cluster)
            .ThenInclude(c => c.Deposit)
            .Include(w => w.RelationCompaniesWells)
            .ThenInclude(r => r.Company)
            .Include(w => w.Telemetry)
            .FirstOrDefault(w => w.Id == idWell)
            ?? throw new ArgumentInvalidException(nameof(idWell), "idWell doesn`t exist");

        idTelemetry = well?.IdTelemetry 
            ?? throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} doesn't contain telemetry");

        events = context.TelemetryEvents
         .Where(e => e.IdTelemetry == idTelemetry)
         .ToDictionary(e => e.IdEvent, e => e);

        users = context.TelemetryUsers
            .Where(u => u.IdTelemetry == idTelemetry)
            .ToDictionary(u => u.IdUser, u => u);

        timezoneOffset = well!.Telemetry?.Info?.TimeZoneOffsetTotalHours ?? well.Timezone?.Hours ?? 5.0;

        info = new WellInfoReport
        {
            Deposit = well.Cluster?.Deposit?.Caption,
            Cluster = well.Cluster?.Caption,
            Well = well.Caption,
            Customer = well.RelationCompaniesWells.FirstOrDefault(c => c.Company.IdCompanyType == 1)?.Company.Caption,
            DrillingStartDate = well.Telemetry?.Info?.DrillingStartDate.ToRemoteDateTime(timezoneOffset) ?? default,
            TimeZoneId = well.Telemetry?.Info?.TimeZoneId ?? well.Timezone?.TimezoneId ?? DefaultTimezoneId,
            TimeZoneOffsetTotalHours = timezoneOffset,
        };
    }

    public AnalyzeResult Analyze()
    {
        // TODO: Replace by linq methods.
        var messagesStat = (from item in context.TelemetryMessages
                            where item.IdTelemetry == idTelemetry
                            group item.DateTime by item.IdTelemetry into g
                            select new { min = g.Min(), max = g.Max(), count = g.Count() })
                .FirstOrDefault();

        var dataStat = (from item in context.TelemetryDataSaub
                        where item.IdTelemetry == idTelemetry
                        group item.DateTime by item.IdTelemetry into g
                        select new { min = g.Min(), max = g.Max(), count = g.Count() })
                .FirstOrDefault();

        var result = new AnalyzeResult
        {
            MinDate = dataStat?.min.UtcDateTime ?? messagesStat?.min.UtcDateTime ?? default,
            MaxDate = dataStat?.max.UtcDateTime ?? messagesStat?.max.UtcDateTime ?? default,
            MessagesCount = messagesStat?.count ?? 0,
        };

        return result;
    }

    public IQueryable<DataSaubReport> GetDataSaubItems(DateTime begin, DateTime end)
    {
        var beginUtc = begin.ToUtcDateTimeOffset(timezoneOffset);
        var endUtc = end.ToUtcDateTimeOffset(timezoneOffset);

        var query = context.TelemetryDataSaub
            .Where(d => d.IdTelemetry == idTelemetry
                && d.DateTime >= beginUtc
                && d.DateTime <= endUtc)
            .OrderBy(d => d.DateTime)
            .Select(d => new DataSaubReport
            {
                Date = d.DateTime.DateTime.AddHours(timezoneOffset),
                Mode = d.Mode,
                WellDepth = d.WellDepth,
                BitDepth = d.BitDepth,
                BlockPosition = d.BlockPosition,
                BlockSpeed = d.BlockSpeed,
                BlockSpeedSp = d.BlockSpeedSp,
                BlockSpeedSpDevelop = d.BlockSpeedSpDevelop,
                Pressure = d.Pressure,
                PressureSp = d.PressureSp,
                AxialLoad = d.AxialLoad,
                AxialLoadSp = d.AxialLoadSp,
                AxialLoadLimitMax = d.AxialLoadLimitMax,
                HookWeight = d.HookWeight,
                RotorTorque = d.RotorTorque,
                RotorTorqueSp = d.RotorTorqueSp,
                RotorSpeed = d.RotorSpeed,
                Flow = d.Flow,
                PressureSpDevelop = d.PressureSpDevelop,
                IdFeedRegulator = d.IdFeedRegulator,
                Pump0Flow = d.Pump0Flow,
                Pump1Flow = d.Pump1Flow,
                Pump2Flow = d.Pump2Flow,

            });
        return query;
    }

    public IQueryable<DataSpinReport> GetDataSpinItems(DateTime begin, DateTime end)
    {
        var beginUtc = begin.ToUtcDateTimeOffset(timezoneOffset);
        var endUtc = end.ToUtcDateTimeOffset(timezoneOffset);

        var query = context.TelemetryDataSpin
            .Where(d => d.IdTelemetry == idTelemetry
                && d.DateTime >= beginUtc
                && d.DateTime <= endUtc)
            .OrderBy(d => d.DateTime)
            .Select(d => new DataSpinReport
            {
                Date = d.DateTime.DateTime.AddHours(timezoneOffset),
                Mode = d.Mode,
                IsWorkingSpinMaster = (d.State != 0 && d.State != 5 && d.State != 6 && d.State != 7),
                IsWorkingTorqueMaster = (d.State == 7 && (d.Mode & 2) > 0),
            });
        return query;
    }

    public IQueryable<MessageReport> GetMessages(DateTime begin, DateTime end)
    {
        var beginUtc = begin.ToUtcDateTimeOffset(timezoneOffset);
        var endUtc = end.ToUtcDateTimeOffset(timezoneOffset);

        var query = from item in context.TelemetryMessages
                    where item.IdTelemetry == idTelemetry
                        && item.DateTime >= beginUtc
                        && item.DateTime <= endUtc
                    orderby item.DateTime
                    select new MessageReport
                    {
                        Id = item.Id,
                        Date = item.DateTime.DateTime,
                        Category = events.GetValueOrDefault(item.IdEvent) == null
                             ? $""
                             : categories[events[item.IdEvent].IdCategory],
                        User = item.IdTelemetryUser == null
                             ? ""
                             : users.GetValueOrDefault((int)item.IdTelemetryUser) == null
                                 ? $"User id{item.IdTelemetryUser}"
                                 : users[(int)item.IdTelemetryUser].MakeDisplayName(),
                        Text = events.GetValueOrDefault(item.IdEvent) == null
                            ? $"Событие {item.IdEvent} {item.Arg0} {item.Arg1} {item.Arg2} {item.Arg3}"
                            : events[item.IdEvent].MakeMessageText(item)
                    };
        return query;
    }

    public WellInfoReport GetWellInfo()
    => info;
}