using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces.Job;
using PARR.Core.Repositories.Interfaces.TaskRepositories;
using PARR.Core.Services.NextRunServices;
using PARR.Core.Services.TaskServices.Helpers;
using PARR.Core.Services.Workload.Interfaces;
using PARR.Core.Services.Workload.Models;
using PARR.Domain.DTOs.Workload;
using PARR.Domain.Enums;

namespace PARR.Core.Services.Workload.Implementations
{
    internal class WorkloadService : IWorkloadService
    {
        private readonly ILogger<WorkloadService> logger;
        private readonly WorkloadCacheService workloadCacheService;
        private readonly INextRunService nextRunService;
        private readonly ITaskRepository taskRepository;
        private readonly IMapper mapper;
        private readonly IJobRepository jobRepository;

        //private readonly ITaskManagementService taskManagementService;

        public WorkloadService(
                ILogger<WorkloadService> logger,
                WorkloadCacheService workloadCacheService,
                INextRunService nextRunService,
                ITaskRepository taskRepository,
                IMapper mapper,
                IJobRepository jobRepository
            //ITaskManagementService taskManagementService
            )
        {
            this.logger = logger;
            this.workloadCacheService = workloadCacheService;
            this.nextRunService = nextRunService;
            this.taskRepository = taskRepository;
            this.mapper = mapper;
            this.jobRepository = jobRepository;
            //this.taskManagementService = taskManagementService;
        }


        public async Task<WorkloadReport?> GetWorkloadReport(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
        {
            logger.LogDebug("Начинаю формировать отчет о загруженности по параметрам: reportType '{ReportType}', dateStart {DateStart}, durationDays {DurationDays}, offset {Offset}",
                                reportType.ToString(), dateStart, durationDays, offset);

            if ((reportType == WorkloadReportType.WorkGroupJob || reportType == WorkloadReportType.ResponseAreaWorkGroups) && string.IsNullOrEmpty(filterParam))
            {
                logger.LogError("Для типа отчета {ReportType} не передан параметр.", reportType.ToString());
                throw new ArgumentNullException(nameof(filterParam), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
            }


            // Если есть отчет в КЭШ, вернуть
            var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filterParam);
            if (cacheReport != null)
            {
                logger.LogDebug("Вернул отчет из кэша.");
                return cacheReport;
            }

            // Если нет основного кэша, вернуть null, пусть пользователь нажимает кнопку сформировать отчет
            var templates = await workloadCacheService.GetTemplateReportDataAsync(dateStart, durationDays, offset);
            if (templates == null)
            {
                logger.LogDebug("Не могу рассчитать отчет Workload, так как нет основного кэш или он не актуальный.");
                return null;
            }

            // Есть основной кэш, построить отчет и сохранить в кэш отчета
            logger.LogDebug("Основной кэш есть, начинаю строить отчет.");

            // Список дней
            var reportDays = await GetReportDays(dateStart, durationDays);

            var displayName = string.Empty;

            // Формируем отчет в зависимости от типа
            var statistics = new List<WorkloadStatisticItem>();
            switch (reportType)
            {
                case WorkloadReportType.ResponseAreaAll:
                    displayName = "По зонам ответственности";
                    foreach (var group in templates.GroupBy(t => t.ResponseArea, StringComparer.OrdinalIgnoreCase))
                        statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
                    break;
                case WorkloadReportType.ResponseAreaWorkGroups:
                    displayName = "По зоне ответственности";
                    // Фильтруем по ЗО
                    var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
                    // Группируем работы по РГ
                    foreach (var group in filteredRA.GroupBy(t => t.WorkGroup, StringComparer.OrdinalIgnoreCase))
                        statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
                    //statistics.Add(BuildStatisticItem(filterParam!, filteredRA, reportDays, offset));
                    break;
                case WorkloadReportType.WorkGroupAll:
                    displayName = "По рабочим группам";
                    foreach (var group in templates.GroupBy(t => t.WorkGroup))
                        statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
                    break;
                case WorkloadReportType.WorkGroupJob:
                    displayName = "По рабочей группе";
                    // Фильтруем по РГ
                    var filteredWG = templates.Where(t => string.Equals(t.WorkGroup, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();

                    // Получим все JobName из бд
                    var jobIds = filteredWG.Select(t => t.JobId).ToHashSet();
                    var jobs = await jobRepository.Get().AsNoTracking()
                                        .Where(t => jobIds.Contains(t.Id))
                                        .ToDictionaryAsync(t => t.Id, t => t.Name);

                    // Группируем работы по JobId
                    foreach (var group in filteredWG.GroupBy(t => t.JobId))
                    {
                        var jobName = jobs.GetValueOrDefault(group.Key) ?? "Работа";
                        statistics.Add(BuildStatisticItem(jobName, group, reportDays, offset));
                    }
                    break;
            }

            var report = new WorkloadReport
            {
                Type = reportType,
                DisplayName = displayName,
                SubfilterName = filterParam,
                ReportCacheCreatedAt = DateTimeOffset.UtcNow,
                TemplateCacheCreatedAt = templates.FirstOrDefault()?.CacheDate ?? DateTimeOffset.MinValue,
                Days = reportDays,
                Statistics = statistics.OrderBy(t => t.Title).ToList()
            };

            // Сохраним в кэш
            await workloadCacheService.SetWorkloadReport(report, reportType, dateStart, durationDays, offset, filterParam);
            logger.LogDebug("Отчет построен и сохранен в кэш");

            return report;
        }


        public async Task<WorkloadCacheInfo> GetReportInfoAsync()
        {
            // может быть три статуса
            // 1 - отчет сейчас формируется (дата начала формирования отчета)
            // 2 - отчет сформирован (вернуть дату формирования кэш)
            // 3 - отчет сейчас не формируется, кэша нет (date = null)


            // 1. проверить что выполнились все задания на обновление кэш
            //var activeTasks = await taskManagementService.GetActiveTasksAsync(TaskTypeEnum.Workload);
            var activeTasks = await TaskManagementHelpers.GetActiveTasksAsync(taskRepository, mapper, TaskTypeEnum.Workload);
            if (activeTasks.Count > 0)
            {
                // идет формирование отчета
                // задача может быт только одна, так как у нее такой тип
                var task = activeTasks.First();

                return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.Processing, Date = task.DateCreated, ProgressPercent = task.ProgressPercent };
            }

            // 2. получить дату кэш
            // Если нет активных задач, значит может быть кэш существует, пробуем получить дату из кэш
            var templateCache = await workloadCacheService.GetFirstTemplateCacheAsync();
            if (templateCache != null)
            {
                return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.Generated, Date = templateCache.Timestamp };
            }

            // Если кэш нет, то возвращаем null    
            return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.NotFound };
        }


        /// <summary>
        /// Получить список дней для отчета, с отметкой выходной/рабочий
        /// </summary>
        /// <param name="dateStart"></param>
        /// <param name="durationDays"></param>
        /// <returns></returns>
        private async Task<List<WorkloadDayItem>> GetReportDays(DateOnly dateStart, int durationDays)
        {
            var dateEnd = dateStart.AddDays(durationDays - 1);
            var days = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false);
            var weekends = await nextRunService.GetWeekendsAsync(dateStart, dateEnd);

            return days.Select(t => new WorkloadDayItem { Date = t, IsWeekend = weekends.Contains(t) })
                            .OrderBy(t => t.Date)
                            .ToList();
        }

        /// <summary>
        /// Составить статистику по одному объекту за период
        /// </summary>
        /// <param name="templates"></param>
        /// <param name="reportDays"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        private List<WorkloadDailyItem> GetDailyMetrics(List<TemplateReportData> templates, List<WorkloadDayItem> reportDays, TimeSpan offset)
        {
            var result = new List<WorkloadDailyItem>();

            foreach (var dayItem in reportDays)
            {
                var dateStart = new DateTimeOffset(dayItem.Date.Year, dayItem.Date.Month, dayItem.Date.Day, 0, 0, 0, offset);
                var dateEnd = dateStart.AddDays(1);

                var filteredItems = templates.Where(t => dateStart <= t.NextRun && t.NextRun < dateEnd);

                var dailyItem = new WorkloadDailyItem
                {
                    Date = dayItem.Date,
                    IsWeekend = dayItem.IsWeekend,
                    Total = filteredItems.Count(),
                    Activated = filteredItems.Count(t => t.IsActiveTemplate && t.IsActiveSchedule),
                    Deactivated = filteredItems.Count(t => !t.IsActiveTemplate || !t.IsActiveSchedule)
                };

                result.Add(dailyItem);
            }

            return result.OrderBy(t => t.Date).ToList();
        }

        /// <summary>
        /// Заполнить Summary
        /// </summary>
        /// <param name="dailyMetrics"></param>
        /// <returns></returns>
        private WorkloadSummary GetSummary(List<WorkloadDailyItem> dailyMetrics, int allDays)
        {
            // Локальная функция для расчета одной метрики
            static WorkloadSummaryItem Calc(List<WorkloadDailyItem> dailyMetrics, int allDays, Func<WorkloadDailyItem, int> selector) => new()
            {
                Count = dailyMetrics.Sum(selector),
                Average = allDays > 0 ? Math.Round((double)dailyMetrics.Sum(selector) / allDays, 2) : 0,
                Max = dailyMetrics.Max(selector)
            };

            return new WorkloadSummary
            {
                Total = Calc(dailyMetrics, allDays, t => t.Total),
                Activated = Calc(dailyMetrics, allDays, t => t.Activated),
                Deactivated = Calc(dailyMetrics, allDays, t => t.Deactivated)
            };
        }

        /// <summary>
        /// Формирует статистику по одной группе
        /// </summary>
        /// <param name="title"></param>
        /// <param name="groupTemplates"></param>
        /// <param name="reportDays"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        private WorkloadStatisticItem BuildStatisticItem(string title, IEnumerable<TemplateReportData> groupTemplates, List<WorkloadDayItem> reportDays, TimeSpan offset)
        {
            var dailyMetrics = GetDailyMetrics(groupTemplates.ToList(), reportDays, offset);
            var summary = GetSummary(dailyMetrics, reportDays.Count);

            return new WorkloadStatisticItem { DailyMetrics = dailyMetrics, Summary = summary, Title = title };
        }

    }
}
