using Microsoft.Extensions.Logging;
using PARR.Core.Services.NextRunServices;
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;

        public WorkloadService(
                ILogger<WorkloadService> logger,
                WorkloadCacheService workloadCacheService,
                INextRunService nextRunService
            )
        {
            this.logger = logger;
            this.workloadCacheService = workloadCacheService;
            this.nextRunService = nextRunService;
        }


        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.WorkGroupItem || reportType == WorkloadReportType.ResponseAreaItem) && 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))
                        statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
                    break;
                case WorkloadReportType.ResponseAreaItem:
                    displayName = "По зоне ответственности";
                    //todo:  тут нужно вернуть другой отчет, либо группы работ, либо список работ
                    var filteredRA = templates.Where(t => t.ResponseArea.Equals(filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
                    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.WorkGroupItem:
                    displayName = "По рабочей группе";
                    //todo:  тут нужно вернуть другой отчет, либо группы работ, либо список работ
                    var filteredWG = templates.Where(t => t.WorkGroup.Equals(filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
                    statistics.Add(BuildStatisticItem(filterParam!, filteredWG, 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;
        }

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

    }
}
