using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.JobRepositories;
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.Cache.Models;
using PARR.Domain.DTOs.Workload;
using PARR.Domain.Enums;
using PARR.Domain.Enums.Workload;

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 ITemplateRepository templateRepository;

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


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

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

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

            // Если есть отчет в КЭШ, вернуть
            var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, responseArea, workGroup);
            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, null, group, reportDays, offset));
                    break;
                case WorkloadReportType.ResponseAreaWorkGroups:
                    displayName = "По зоне ответственности";
                    // Фильтруем по ЗО
                    var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, responseArea, StringComparison.OrdinalIgnoreCase)).ToList();
                    // Группируем работы по РГ
                    foreach (var group in filteredRA.GroupBy(t => t.WorkGroup, StringComparer.OrdinalIgnoreCase))
                        statistics.Add(BuildStatisticItem(group.Key, null, group, reportDays, offset));
                    break;
                case WorkloadReportType.WorkGroupAll:
                    displayName = "По рабочим группам";
                    foreach (var group in templates.GroupBy(t => t.WorkGroup))
                        statistics.Add(BuildStatisticItem(group.Key, null, group, reportDays, offset));
                    break;
                case WorkloadReportType.WorkGroupJob:
                    displayName = "По рабочей группе";
                    // Фильтруем по РГ
                    var filteredWG = templates.Where(t => string.Equals(t.WorkGroup, workGroup, StringComparison.OrdinalIgnoreCase)).ToList();
                    if (!string.IsNullOrWhiteSpace(responseArea))
                    {
                        // Дополнительно фильтруем по ЗО
                        filteredWG = filteredWG.Where(t => string.Equals(t.ResponseArea, responseArea, 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.Key, group, reportDays, offset));
                    }
                    break;
            }


            var report = new WorkloadReport
            {
                Type = reportType,
                DisplayName = displayName,
                SubfilterName = string.Join(", ", new[] { responseArea, workGroup }),
                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, responseArea, workGroup);
            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 };
        }


        public async Task<List<WorkloadTemplateReport>> GetTemplateReportAsync(
            WorkloadTemplateReportType reportType,
            string filter,
            WorkloadTemplateReportState state,
            DateOnly date,
            TimeSpan offset,
            string? responseArea,
            string? workGroup
            )
        {
            if (string.IsNullOrWhiteSpace(filter))
            {
                logger.LogWarning("Не передано значение фильтра");
                throw new ArgumentException(nameof(filter), "Не передано значение фильтра.");
            }

            // Template cache вообще есть, он актуальный?
            if (!await workloadCacheService.IsTemplateReportCacheActualAsync())
            {
                logger.LogDebug("Не могу рассчитать отчет Workload, так как нет основного кэш или он не актуальный.");
                return new List<WorkloadTemplateReport>();
            }

            var trimmedFilter = filter.Trim();


            // Формируем дату и время начала и конца
            var dtStart = new DateTimeOffset(date.Year, date.Month, date.Day, 0, 0, 0, offset);
            var dtEnd = dtStart.AddDays(1);

            // Конвертируем в UTC для совместимости с PostgreSQL timestamptz
            var dtStartUtc = dtStart.ToUniversalTime();
            var dtEndUtc = dtEnd.ToUniversalTime();

            // Запрос к БД
            var query = templateRepository.Get().AsNoTracking()
                                    .Where(t => dtStartUtc <= t.NextRun && t.NextRun < dtEndUtc);

            // Фильтруем по состоянию
            switch (state)
            {
                case WorkloadTemplateReportState.All:
                    break;
                case WorkloadTemplateReportState.Activated:
                    query = query.Where(t => t.IsActiveTemplate && t.IsActiveSchedule);
                    break;
                case WorkloadTemplateReportState.Deactivated:
                    query = query.Where(t => !t.IsActiveTemplate || !t.IsActiveSchedule);
                    break;
            }

            // Если тип - работы, получим из бд сразу нужные работы
            if (reportType == WorkloadTemplateReportType.Job)
            {
                if (!Guid.TryParse(trimmedFilter, out Guid jobId))
                    throw new ArgumentException("Значение фильтра не является Guid");

                query = query.Where(t => t.JobId == jobId);
            }

            // Загружаем шаблоны
            var dbTemplates = await query.OrderBy(t => t.Name)
                                         .Select(t => new WorkloadTemplateReport(t.Id, t.Name, t.NextRun, t.IsActiveTemplate, t.IsActiveSchedule))
                                         .ToListAsync();

            if (dbTemplates.Count == 0)
                return new List<WorkloadTemplateReport>();

            // Если тип - работы, и нет дополнительных фильтров по ЗО и РГ, не полезем в кэш, вернем сразу
            if (reportType == WorkloadTemplateReportType.Job && string.IsNullOrWhiteSpace(responseArea) && string.IsNullOrWhiteSpace(workGroup))
            {
                // Тут кэш не нужен, вернем сразу из бд (нет фильтров по РГ и ЗО, нужен список всех работ на дату)
                return FilterWorkloadJobTemplateReport(dbTemplates, null);
            }

            // Получаем из кэша шаблоны (если их в кэше нет, догружаем в кэш)
            var cacheData = await workloadCacheService.GetTemplateReportDataByIdsAsync(dbTemplates.Select(t => t.TemplateId).ToHashSet());

            if (cacheData == null)
                throw new Exception("Из кэша не получили значения.");

            if (dbTemplates.Count != cacheData.Count)
                logger.LogWarning("Количество шаблонов в БД {DbCount} не совпадает с количеством шаблонов полученных из кэша {CacheCount}", dbTemplates.Count, cacheData.Count);

            // Дополнительные фильтры
            var trimmedResponseArea = responseArea?.Trim();
            var trimmedWorkGroup = workGroup?.Trim();

            // Фильтр по типу
            switch (reportType)
            {
                case WorkloadTemplateReportType.Job:
                    // Смотрим есть ли дополнительные фильтры
                    if (!string.IsNullOrEmpty(trimmedResponseArea) || !string.IsNullOrEmpty(trimmedWorkGroup))
                    {
                        // Фильтруем из кэша
                        IEnumerable<TemplateCache> cacheTemplatesJobs = cacheData;

                        // Фильтруем по ЗО если есть
                        if (!string.IsNullOrEmpty(trimmedResponseArea))
                            cacheTemplatesJobs = cacheTemplatesJobs.Where(t => string.Equals(t.Data.ResponseArea, trimmedResponseArea, StringComparison.OrdinalIgnoreCase));

                        // Дополнительный фильтр по РГ, если есть
                        if (!string.IsNullOrEmpty(trimmedWorkGroup))
                            cacheTemplatesJobs = cacheTemplatesJobs.Where(t => string.Equals(t.Data.WorkGroup, trimmedWorkGroup, StringComparison.OrdinalIgnoreCase));

                        var filteredTemplateJobIds = cacheTemplatesJobs.Select(t => t.Data.TemplateId).ToHashSet();

                        return FilterWorkloadJobTemplateReport(dbTemplates, filteredTemplateJobIds);
                    }
                    return FilterWorkloadJobTemplateReport(dbTemplates, null);
                case WorkloadTemplateReportType.WorkGroup:
                    var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase));
                    // Дополнительный фильтр по ЗО, если есть
                    if (!string.IsNullOrEmpty(trimmedResponseArea))
                        cacheTemplatesWg = cacheTemplatesWg.Where(t => string.Equals(t.Data.ResponseArea, trimmedResponseArea, StringComparison.OrdinalIgnoreCase));

                    var filteredTemplateWgIds = cacheTemplatesWg.Select(t => t.Data.TemplateId).ToHashSet();

                    return dbTemplates.Where(t => filteredTemplateWgIds.Contains(t.TemplateId)).ToList();
                case WorkloadTemplateReportType.ResponseArea:
                    var cacheTemplatesRa = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedFilter, StringComparison.OrdinalIgnoreCase));
                    // Дополнительный фильтр по РГ если есть
                    if (!string.IsNullOrEmpty(trimmedWorkGroup))
                        cacheTemplatesRa = cacheTemplatesRa.Where(t => string.Equals(t.Data.WorkGroup, trimmedWorkGroup, StringComparison.OrdinalIgnoreCase));

                    var filteredTemplateRaIds = cacheTemplatesRa.Select(t => t.Data.TemplateId).ToHashSet();

                    return dbTemplates.Where(t => filteredTemplateRaIds.Contains(t.TemplateId)).ToList();
            }

            logger.LogWarning("Не обработанный тип отчета {ReportType}", reportType);

            return new List<WorkloadTemplateReport>();
        }


        private List<WorkloadTemplateReport> FilterWorkloadJobTemplateReport(List<WorkloadTemplateReport> dbTemplates, HashSet<Guid>? filteredCacheTemplates)
        {
            if (filteredCacheTemplates == null)
                return dbTemplates;

            // Отсекаем шаблоны которых нет в кэше
            return dbTemplates.Where(t => filteredCacheTemplates.Contains(t.TemplateId)).ToList();
        }


        /// <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, Guid? objId, 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, ObjId = objId };
        }
    }
}
