using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.AutogeneratedDailyReport;
using AsbCloudApp.Data.Subsystems;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.AutoGeneratedDailyReports;
using AsbCloudApp.Services.Subsystems;
using AsbCloudDb.Model;

namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;

public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
{
	private const string fileNameTemplate = "Суточный_отчёт_по_скважине_{0}_куст_{1}_от_{2}.xlsx";
	
	private readonly IWellService wellService;
	private readonly IWellOperationRepository wellOperationRepository;
	private readonly ISubsystemOperationTimeService subsystemOperationTimeService;
	private readonly ICrudRepository<SubsystemDto> subsystemRepository;
	private readonly ILimitingParameterService limitingParameterService;
	private readonly IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService;

	public AutoGeneratedDailyReportService(IWellService wellService,
		IWellOperationRepository wellOperationRepository,
		ISubsystemOperationTimeService subsystemOperationTimeService,
		ICrudRepository<SubsystemDto> subsystemRepository,
		ILimitingParameterService limitingParameterService,
		IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService)
	{
		this.wellOperationRepository = wellOperationRepository;
		this.wellService = wellService;
		this.subsystemOperationTimeService = subsystemOperationTimeService;
		this.subsystemRepository = subsystemRepository;
		this.limitingParameterService = limitingParameterService;
		this.autoGeneratedDailyReportMakerService = autoGeneratedDailyReportMakerService;
	}

	public async Task<PaginationContainer<AutoGeneratedDailyReportInfoDto>> GetListAsync(int idWell,
		AutoGeneratedDailyReportRequest request, 
		CancellationToken cancellationToken)
	{
		var result = new PaginationContainer<AutoGeneratedDailyReportInfoDto>
		{
			Skip = request.Skip ?? 0,
			Take = request.Take ?? 10,
			Items = Enumerable.Empty<AutoGeneratedDailyReportInfoDto>()
		};
		
		var reports = new List<AutoGeneratedDailyReportInfoDto>();

		var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken)
			?? throw new ArgumentInvalidException(nameof(idWell), "Скважина не найдена");

		if (!well.IdTelemetry.HasValue)
			throw new ArgumentInvalidException(nameof(idWell), "Телеметрия для скважины отсутствует");

		var datesRange = await GetDatesRangeAsync(idWell, cancellationToken);
		
		if (datesRange is null)
			return result;
		
		if (request.StartDate.HasValue)
		{
            var startDate = new DateTime(request.StartDate.Value.Year, request.StartDate.Value.Month,
				request.StartDate.Value.Day);

			if(startDate.Date >= datesRange.From.Date)
				datesRange.From = startDate;
		}

		if (request.FinishDate.HasValue)
		{
			var finishDate = new DateTime(request.FinishDate.Value.Year, request.FinishDate.Value.Month,
				request.FinishDate.Value.Day);

			if (finishDate.Date <= datesRange.To.Date)
				datesRange.To = finishDate;
		}

		if (datesRange.From.AddDays(result.Skip) <= datesRange.To)
			result.Count = (int)(Math.Ceiling((datesRange.To - DateTime.UnixEpoch).TotalDays) - Math.Floor((datesRange.From - DateTime.UnixEpoch).TotalDays));

		for (int day = result.Skip; (day - result.Skip) < result.Take && (datesRange.From.AddDays(day)) <= datesRange.To; day++)
		{
			var reportDate = DateOnly.FromDateTime(datesRange.From.AddDays(day));

			reports.Add(new AutoGeneratedDailyReportInfoDto
			{
				FileName = string.Format(fileNameTemplate, well.Caption, well.Cluster, reportDate),
				ReportDate = reportDate,
				FileSize = GetFileSize(reportDate, idWell),
			});
		}

		result.Items = reports;
		
		return result;
	}

	public async Task<(string fileName, Stream stream)> GenerateAsync(int idWell, DateOnly reportDate, 
		CancellationToken cancellationToken)
	{
		var startDate = new DateTime(reportDate.Year, reportDate.Month, reportDate.Day);
		var finishDate = startDate.AddDays(1);

		var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken)
			?? throw new ArgumentInvalidException(nameof(idWell), "Скважина не найдена");

		if (!well.IdTelemetry.HasValue)
			throw new ArgumentInvalidException(nameof(idWell), "Телеметрия для скважины отсутствует");

		var factOperations = await GetFactOperationsAsync(well.Id, startDate, finishDate, 
			cancellationToken);
		
		var report = new AutoGeneratedDailyReportDto
		{
			FileName = string.Format(fileNameTemplate, well.Caption, well.Cluster, reportDate),
			FileSize = GetFileSize(reportDate, idWell),
			ReportDate = reportDate,
			Head = CreateHeadBlock(well, factOperations),
			Subsystems = (await CreateSubsystemBlockAsync(idWell, startDate, finishDate, cancellationToken)).ToArray(),
			LimitingParameters = (await CreateLimitingParameterBlockAsync(idWell, startDate, finishDate, cancellationToken)).ToArray(),
			TimeBalance = factOperations.GroupBy(w => w.CategoryName).Select(x => new TimeBalanceRecordDto
			{
				Name = x.Key ?? "Название операции отсутствует",
				DurationHours = x.Sum(o => o.DurationHours)
			}).ToArray(),
		};

		var stream = await autoGeneratedDailyReportMakerService.MakeReportAsync(report, cancellationToken);

		return (report.FileName, stream);
	}

	public async Task<DatesRangeDto?> GetDatesRangeAsync(int idWell, CancellationToken cancellationToken)
	{
		var factOperations = await GetFactOperationsAsync(idWell, null, null, 
			cancellationToken);

		if (!factOperations.Any())
			return null;
		
		return new DatesRangeDto
		{
			From = factOperations.Min(o => o.DateStart).Date,
			To = factOperations.Max(o => o.DateStart).Date
		};
	}

	private HeadBlockDto CreateHeadBlock(WellDto well, IEnumerable<WellOperationDto> factOperations)
	{
		var customer = well.Companies.FirstOrDefault(company => company.IdCompanyType == 1);
		var sortedFactOperations = factOperations.OrderBy(o => o.DateStart);

        return new HeadBlockDto
		{
			Customer = customer?.Caption ?? string.Empty,
			Deposit = well.Deposit ?? string.Empty,
			Cluster = well.Cluster ?? string.Empty,
			Well = well.Caption,
			DepthFrom = sortedFactOperations.FirstOrDefault()?.DepthStart ?? 0.00,
			DepthTo = sortedFactOperations.LastOrDefault()?.DepthEnd ?? 0.00
		};
	}

	private async Task<IEnumerable<SubsystemRecordDto>> CreateSubsystemBlockAsync(int idWell, DateTime startDate,
		DateTime finishDate,
		CancellationToken cancellationToken)
	{
		var subsystems = await subsystemRepository.GetAllAsync(cancellationToken);
		var subsystemStats = await GetSubsystemStatsAsync(idWell, startDate, finishDate,
			cancellationToken);

		return subsystems.Select(subsystem =>
		{
			var subsytemStat = subsystemStats?.FirstOrDefault(s => s.IdSubsystem == subsystem.Id);

			return new SubsystemRecordDto
			{
				Name = subsystem.Name,
				KUsage = subsytemStat?.KUsage ?? 0.00,
				UsedTimeHours = subsytemStat?.UsedTimeHours ?? 0.00,
				Depth = subsytemStat?.SumDepthInterval ?? 0.00,
			};
		});
	}

	private async Task<IEnumerable<LimitingParameterRecordDto>> CreateLimitingParameterBlockAsync(int idWell,
		DateTime startDate, DateTime finishDate, CancellationToken cancellationToken)
	{
		var limitingParameterStats = (await GetLimitingParameterStatsAsync(idWell,
			startDate, finishDate, cancellationToken)).ToArray();

		var sumDepths = limitingParameterStats.Sum(x => x.Depth);

		return limitingParameterStats.Select(l => new LimitingParameterRecordDto
		{
			NameFeedRegulator = l.NameFeedRegulator,
			Hours = l.TotalMinutes,
			PercentDepth = sumDepths != 0 ? l.Depth / sumDepths : 0,
			Depth = l.Depth,
		});
	}

	private async Task<IOrderedEnumerable<WellOperationDto>> GetFactOperationsAsync(int idWell, DateTime? startDate,
		DateTime? finishDate, CancellationToken cancellationToken)
	{
		var request = new WellOperationRequest
		{
			IdWell = idWell,
			OperationType = WellOperation.IdOperationTypeFact,
			GeDate = startDate,
			LtDate = finishDate,
			SortFields = new[] { "DateStart asc" },
		};

		return (await wellOperationRepository.GetAsync(request, cancellationToken))
			.OrderBy(w => w.DateStart);
	}

	private Task<IEnumerable<SubsystemStatDto>?> GetSubsystemStatsAsync(int idWell, DateTime startDate,
		DateTime finishDate, CancellationToken cancellationToken)
	{
		var request = new SubsystemOperationTimeRequest
		{
			IdWell = idWell,
			GtDate = startDate,
			LtDate = finishDate,
		};

		return subsystemOperationTimeService.GetStatAsync(request, cancellationToken);
	}

	private Task<IEnumerable<LimitingParameterDto>> GetLimitingParameterStatsAsync(int idWell,
		DateTime startDate, DateTime finishDate, CancellationToken cancellationToken)
	{
		var request = new LimitingParameterRequest
		{
			IdWell = idWell,
			GtDate = startDate,
			LtDate = finishDate,
		};

		return limitingParameterService.GetStatAsync(request, cancellationToken);
	}

	private int GetFileSize(DateOnly reportDate, int idWell)
	{
		const int fileSizeTemplate = 10240;
		long ticks = 1L * reportDate.Year * reportDate.Month * reportDate.Day * idWell;
		int remainder = (int)(ticks % (fileSizeTemplate / 10));
		return fileSizeTemplate + remainder;
	}
}