using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Mapster;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using System.Collections.Generic;
using AsbCloudApp.Data.DailyReport;
using AsbCloudApp.Requests;
using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Exceptions;

namespace AsbCloudInfrastructure.Services.DailyReport
{
#nullable enable
    public class DailyReportService : IDailyReportService
    {
        private readonly IAsbCloudDbContext db;
        private readonly IWellService wellService;
        private readonly IDetectedOperationService detectedOperationService;
        private readonly DailyReportMakerExcel dailyReportMaker = new DailyReportMakerExcel();

        public DailyReportService(IAsbCloudDbContext db, IWellService wellService, IDetectedOperationService detectedOperationService)
        {
            this.db = db;
            this.wellService = wellService;
            this.detectedOperationService = detectedOperationService;
        }

        public async Task<IEnumerable<DailyReportDto>> GetListAsync(int idWell, DateTime? begin, DateTime? end, CancellationToken token)
        {
            var well = wellService.GetOrDefault(idWell);
            if (well is null || well.Timezone is null)
                throw new ArgumentInvalidException("idWell doesn`t exist", nameof(idWell));

            var query = db.DailyReports.Where(r => r.IdWell == idWell);

            DateTimeOffset ExtractDate(DateTime dateTime)
            {
                var dateTimeOffset = dateTime.ToUtcDateTimeOffset(well!.Timezone.Hours);
                var date = new DateTimeOffset(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day, 0,0,0,TimeSpan.Zero);
                return date;
            }

            if (begin is not null)
            {
                var beginUTC = ExtractDate(begin.Value);
                query = query.Where(d => d.StartDate >= beginUTC);
            }

            if (end is not null)
            {
                var endUTC = ExtractDate(end.Value);
                query = query.Where(d => d.StartDate <= endUTC);
            }

            var entities = await query
                .OrderBy(e => e.StartDate)
                .ToListAsync(token);
            return entities.Select(r => Convert(r));
        }

        public async Task<DailyReportDto> GetOrGenerateAsync(int idWell, DateTime date, CancellationToken token)
        {
            var dateOnly = DateTime.SpecifyKind(date.Date, DateTimeKind.Utc);
            var dailyReportDto = await GetOrDefaultAsync(idWell, dateOnly, token);
            dailyReportDto ??= await MakeDefaultDailyReportAsync(idWell, dateOnly, token);            
            return dailyReportDto;
        }

        public async Task<int> AddAsync(int idWell, DailyReportDto dto, CancellationToken token = default)
        {
            var info = Convert(dto);
            var entity = new AsbCloudDb.Model.DailyReport.DailyReport
            {
                IdWell = idWell,
                StartDate = info.Head.ReportDate,
                Info = info
            };
            db.DailyReports.Add(entity);
            var result = await db.SaveChangesAsync(token);
            return result;
        }

        public async Task<int> UpdateAsync(int idWell, DateTime date, DailyReportDto dto, CancellationToken token)
        {
            var dateOffset = date.Date;
            var entity = await db.DailyReports
                .FirstOrDefaultAsync(r => r.IdWell == idWell &&
                    r.StartDate.Year == dateOffset.Year &&
                    r.StartDate.DayOfYear == dateOffset.DayOfYear
                    , token);

            if (entity is null)
                return 0;

            entity.Info = Convert(dto);            
            db.DailyReports.Update(entity);
            var result = await db.SaveChangesAsync(token);
            return result;
        }

        public async Task<Stream?> MakeReportAsync(int idWell, DateTime date, CancellationToken token = default)
        {
            var dailyReportDto = await GetOrDefaultAsync(idWell, date, token);
            if (dailyReportDto is null)
                return null;

            var memoryStream = dailyReportMaker.MakeReportFromBlocks(dailyReportDto);
            return memoryStream;
        }

        private async Task<DailyReportDto?> GetOrDefaultAsync(int idWell, DateTime date, CancellationToken token)
        {
            var dateOffset = date.Date;
            var entity = await db.DailyReports
                .FirstOrDefaultAsync(r => r.IdWell == idWell &&
                    r.StartDate.Year == dateOffset.Year &&
                    r.StartDate.DayOfYear == dateOffset.DayOfYear
                    , token);

            if (entity is null)
                return null;
            var dto = Convert(entity);
            return dto;
        }

        private async Task<DailyReportDto> MakeDefaultDailyReportAsync(int idWell, DateTime date, CancellationToken token)
        {
            var well = await wellService.GetOrDefaultAsync(idWell, token);
            
            var dto = new DailyReportDto()
            {
                Head = new HeadDto()
                {
                    ReportDate = date.Date,
                    WellName = well?.Caption ?? "",
                    ClusterName = well?.Cluster ?? "",
                },
                TimeBalance = await MakeTimeBalanceAsync(idWell, date, token),
                Bha = await GetPrevOrNewBhaAsync(idWell, date, token)
            };
            return dto;
        }

        private async Task<BhaDto> GetPrevOrNewBhaAsync(int idWell, DateTime date, CancellationToken token)
        {
            var dateOffset = date.Date;
            var entity = await db.DailyReports
                .Where(x => x.IdWell == idWell)
                .OrderByDescending(x => x.StartDate)
                .FirstOrDefaultAsync(r => r.StartDate.Year <= dateOffset.Year &&
                    r.StartDate.DayOfYear <= dateOffset.DayOfYear, token);

            if (entity is null)
                return new BhaDto();

            var dto = Convert(entity);
            return dto.Bha;
        }

        private async Task<TimeBalanceDto> MakeTimeBalanceAsync(int idWell, DateTime date, CancellationToken token)
        {
            var stat = await detectedOperationService.GetOperationsStatAsync(new DetectedOperationRequest
            {
                IdWell = idWell,
                GtDate = date.Date,
                LtDate = date.Date.AddDays(1)
            }, token);

            if (stat is null)
                return new TimeBalanceDto();

            var dto = new TimeBalanceDto()
            {
                Drilling = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationRotor, DetectedOperationService.IdOperationSlide):#0.00}",
                Flushing = $"{GetHoursFromStat(stat, DetectedOperationService.idOperationFlushing):#0.00}",
                Building = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationSlipsTime):#0.00}",
                Elaboration = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationElaboration):#0.00}",
                ElaborationBeforeBuilding = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationElaborationBeforeBuilding):#0.00}",
                TemplatingBeforeBuilding = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationTemplatingBeforeBuilding):#0.00}",
                FlushingBeforeBuilding = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationFlushingBeforeBuilding):#0.00}",
                StaticSurveying = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationStaticSurveying):#0.00}",
                Gis = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationGis):#0.00}",
                Ozc = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationOzc):#0.00}",
                Cementing = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationCementing):#0.00}",
                Npv = $"{GetHoursFromStat(stat, DetectedOperationService.IdOperationNpv):#0.00}",
            };
            return dto;
        }

        private static double GetHoursFromStat(IEnumerable<DetectedOperationStatDto> stat, params int[] idCategories)
        {
            var valueMinutes = stat.Where(o => idCategories.Contains(o.IdCategory))
                .Sum(o => o.MinutesTotal);
            return valueMinutes / 60d;
        }

        private static DailyReportDto Convert(AsbCloudDb.Model.DailyReport.DailyReport entity)
        {         
            var dto = entity.Info.Adapt<DailyReportDto>();
            dto.Head.ReportDate = entity.StartDate.Date;
            return dto;
        }

        private static DailyReportInfo Convert(DailyReportDto dto)
        {
            var entity = dto.Adapt<DailyReportInfo>();
            entity.Head.ReportDate = dto.Head.ReportDate.Date.Date;
            return entity;
        }        
    }
#nullable disable
}