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

}