using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Common.Interfaces;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Services.Shortcodes;
using PARR.Core.Services.Workload.Models;
using PARR.Domain.Cache;
using PARR.Domain.Cache.Models;
using PARR.Domain.DTOs.Workload;
using PARR.Domain.Entities;
using PARR.Domain.Enums;

namespace PARR.Core.Services.Workload.Implementations
{
    /// <summary>
    /// Класс управления КЭШем для workload
    /// </summary>
    public class WorkloadCacheService
    {
        private readonly ILogger<WorkloadCacheService> logger;
        private readonly IRedisCacheService redisCacheService;
        private readonly ITemplateRepository templateRepository;
        private readonly IShortcodesService shortcodeService;

        public WorkloadCacheService(
            ILogger<WorkloadCacheService> logger,
            IRedisCacheService redisCacheService,
            ITemplateRepository templateRepository,
            IShortcodesService shortcodeService
            )
        {
            this.logger = logger;
            this.redisCacheService = redisCacheService;
            this.templateRepository = templateRepository;
            this.shortcodeService = shortcodeService;
        }


        //todo:----------- Настройки, вынести -----------

        /// <summary>
        /// TTL кэша шаблонов
        /// </summary>
        private readonly TimeSpan cacheTemplateTtl = TimeSpan.FromHours(12);

        /// <summary>
        /// Допустимая разница записей в БД и в кэш
        /// </summary>
        private readonly int maxCacheDiff = 1000;

        /// <summary>
        /// TTL кэша отчетов workload
        /// </summary>
        private readonly TimeSpan cacheWorkloadTtl = TimeSpan.FromMinutes(15);
        //=========== Настройки, вынести ===========

        /// <summary>
        /// Создать КЭШ шаблонов.
        /// КЭШ рабочих групп и зон ответственности
        /// </summary>
        /// <returns></returns>
        public async Task CreateTemplateCacheAsync(IProgressAsync<int> progress)
        {
            var dtStart = DateTimeOffset.UtcNow;

            logger.LogInformation("Создание КЭШа шаблонов.");

            //1. Получить из БД все шаблоны (тяжелый запрос, тянет вообще все данные, 8 сек)
            var templates = await templateRepository.Get()
                                            .AsNoTracking()
                                            // Загружаем дополнительные данные, чтоб быстрее считался шорткод
                                            .Include(t => t.Job).ThenInclude(t => t.Group).ThenInclude(t => t.GroupType)
                                            .ToListAsync();

            logger.LogInformation("Получил из БД шаблонов {TemplateCount} шт.", templates.Count);

            //Ставим сразу 5% - получили из БД
            await progress.ReportAsync(5);


            if (templates.Count == 0)
            {
                logger.LogWarning("Список шаблонов пуст, кэш не формируется");
                await progress.ReportAsync(100);
                return;
            }

            //2. Получить по каждому шаблону ЗО и РГ
            var cacheData = new List<TemplateCache>();

            int templateCount = 0;
            int totalTemplates = templates.Count;
            // последний отправленный процент
            int lastReportedPercent = 5;

            foreach (var template in templates)
            {
                var (workGroup, responseArea) = await ApplyShortcodesAsync(template);

                logger.LogDebug("[{TemplateCount}] Для шаблона {TemplateId} {TemplateName} получена ЗО '{ResponseArea}', РГ '{WorkGroup}'", templateCount, template.Id, template.Name, responseArea, workGroup);

                var cacheItem = new TemplateCache
                {
                    Data = new TemplateCacheDto { TemplateId = template.Id, ResponseArea = responseArea, WorkGroup = workGroup, JobGroupId = template.Job!.GroupId, JobId = template.JobId },
                    Source = nameof(WorkloadCacheService),
                    Timestamp = DateTimeOffset.UtcNow
                };
                cacheData.Add(cacheItem);

                templateCount++;

                #region Расчет и отправка прогресса
                // Считаем текущий процент(0 - 100). Этап идёт от 5% до 95%
                var currentPercent = 5 + (int)((templateCount * 90.0) / totalTemplates);

                // Округляем вниз до кратного 5 (33→30, 37→35, 99→95)
                var roundedPercent = (currentPercent / 5) * 5;

                // Отправляем, только если прогресс реально изменился (защита от дублей)
                if (roundedPercent > lastReportedPercent && roundedPercent <= 95)
                {
                    await progress.ReportAsync(roundedPercent);
                    lastReportedPercent = roundedPercent;
                }
                #endregion
            }

            //95%
            await progress.ReportAsync(95);

            //3. Сохранить в Redis
            logger.LogDebug("Для сохранения в Redis подготовлен массив из {Count} строк", cacheData.Count);
            //var hashKey = redisCacheService.GetKey(cacheTemplateKey);
            var hashKey = redisCacheService.GetKey(CacheKeys.Workload.TemplateHash());

            foreach (var item in cacheData)
            {
                var templateKey = redisCacheService.GetKey(CacheKeys.Workload.Template(item.Data.TemplateId));
                //await redisCacheService.SetHashFieldAsync(hashKey, item.Data.TemplateId.ToString(), item, cacheTemplateTtl);
                await redisCacheService.SetHashFieldAsync(hashKey, templateKey, item, cacheTemplateTtl);
            }

            await progress.ReportAsync(100);

            var duration = DateTimeOffset.UtcNow - dtStart;
            logger.LogInformation("Завершено формирование кэша. Key: {HashKey}, ttl: {Ttl}, кол-во записей: {CacheCount}. Продолжительность формирования КЭШ {Duration}", hashKey, cacheTemplateTtl, cacheData.Count, duration);
        }

        /// <summary>
        /// Удалить КЭШ шаблонов.
        /// </summary>
        /// <returns></returns>
        public async Task DeleteTemplateCacheAsync()
        {
            //var hashKey = redisCacheService.GetKey(cacheTemplateKey);
            var hashKey = redisCacheService.GetKey(CacheKeys.Workload.TemplateHash());
            logger.LogDebug("Удаляю кэш шаблонов, {HashKey}", hashKey);

            await redisCacheService.DeleteHashAsync(hashKey);
        }

        /// <summary>
        /// Удалить КЭШ отчетов
        /// </summary>
        /// <returns></returns>
        public async Task DeleteWorkloadCacheAsync()
        {
            logger.LogDebug("Удаляю кэш отчетов");

            //await redisCacheService.DeleteKeysByPatternAsync(redisCacheService.GetKey(cacheWorkloadResponseAreaAllKey) + "*");
            await redisCacheService.DeleteKeysByPatternAsync(redisCacheService.GetKey(CacheKeys.Workload.WorkloadResponseAreaAll()) + "*");
            await redisCacheService.DeleteKeysByPatternAsync(redisCacheService.GetKey(CacheKeys.Workload.WorkloadResponseAreaItem()) + "*");

            await redisCacheService.DeleteKeysByPatternAsync(redisCacheService.GetKey(CacheKeys.Workload.WorkloadWorkGroupAll()) + "*");
            await redisCacheService.DeleteKeysByPatternAsync(redisCacheService.GetKey(CacheKeys.Workload.WorkloadWorkGroupItem()) + "*");
        }

        /// <summary>
        /// Получить кэш РГ и ЗО всех шаблонов, получить недостающие поля из БД.
        /// Сравнит кол-во записей в БД и в КЭШ, если не хватает до 1000 шаблонов, доформирует кэш,
        /// если больше 1000, вернет null, так как считается кэш неактуальным.
        /// </summary>
        /// <param name="dateStart">Дата начала</param>
        /// <param name="durationDays">Продолжительность дней</param>
        /// <param name="offset">Смещение часового пояса</param>
        /// <returns>null - если нет кэша или он не актуальный. Иначе, список шаблонов.</returns>
        public async Task<List<TemplateReportData>?> GetTemplateReportDataAsync(DateOnly dateStart, int durationDays, TimeSpan offset)
        {
            logger.LogDebug("Получаю данные для формирования отчета. DateStart {DateStart}, DurationDays {DurationDays}, Offset {Offset}", dateStart, durationDays, offset);

            // Получим кэш целиком
            var cacheData = await redisCacheService.GetAllHashFieldsAsync<TemplateCache>(redisCacheService.GetKey(CacheKeys.Workload.TemplateHash()));
            if (cacheData.Count == 0)
            {
                logger.LogDebug("Записей в кэш 0. Конец.");
                return null;
            }

            // Сравним кол-во записей в БД и в КЭШ, вдруг там разница больше допустимой
            var templatesCount = await templateRepository.Get().AsNoTracking().CountAsync();
            if (templatesCount - cacheData.Count > maxCacheDiff)
            {
                logger.LogInformation("Разница в количестве шаблонов в БД ({TemplateCount}) и в КЭШ ({CacheCount}) больше допустимой {MaxCacheDiff}. Считаем КЭШ неактуальным. Требуется повторное формирование КЭШа.",
                    templatesCount, cacheData.Count, maxCacheDiff);
                return null;
            }

            // Получаем из БД только нужные данные для отчета, попадающие в интервал
            var dtStart = new DateTimeOffset(dateStart.Year, dateStart.Month, dateStart.Day, 0, 0, 0, offset);
            var dtEnd = dtStart.AddDays(durationDays);

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

            logger.LogDebug("Фильтруем шаблоны в БД по интервалу: Start={DtStart:yyyy-MM-dd HH:mm:ss zzz}, End={DtEnd:yyyy-MM-dd HH:mm:ss zzz}", dtStart, dtEnd);

            var templates = await templateRepository.Get()
                .AsNoTracking()
                .Where(t => dtStartUtc <= t.NextRun && t.NextRun < dtEndUtc)
                .Select(t => new
                {
                    Id = t.Id,
                    IsActiveTemplate = t.IsActiveTemplate,
                    IsActiveSchedule = t.IsActiveSchedule,
                    Name = t.Name,
                    NextRun = t.NextRun
                })
                .ToListAsync();

            logger.LogDebug("Загружено шаблонов из БД {Count}", templates.Count);

            var reportData = new List<TemplateReportData>();

            foreach (var template in templates)
            {
                //cacheData.TryGetValue(template.Id.ToString(), out var cache);
                var templateKey = redisCacheService.GetKey(CacheKeys.Workload.Template(template.Id));
                cacheData.TryGetValue(templateKey, out var cache);

                var (workGroup, responseArea) = ("", "");
                var cacheDate = DateTimeOffset.UtcNow;
                var jobId = Guid.Empty;

                if (cache != null)
                {
                    workGroup = cache.Data.WorkGroup;
                    responseArea = cache.Data.ResponseArea;
                    cacheDate = cache.Timestamp;
                    jobId = cache.Data.JobId;
                }
                else
                {
                    (workGroup, responseArea, jobId) = await GetTemplatePropsAsync(template.Id);
                }

                var data = new TemplateReportData(
                       template.Id,
                       responseArea,
                       workGroup,
                       template.Name,
                       template.NextRun,
                       template.IsActiveTemplate,
                       template.IsActiveSchedule,
                       cacheDate,
                       jobId);
                reportData.Add(data);
            }

            logger.LogDebug("Сформировано итоговых данных для отчета, строк: {Count}", reportData.Count);

            return reportData;
        }

        /// <summary>
        /// Получить РГ и ЗО из шорткода. Сохранить в КЭШ.
        /// </summary>
        /// <param name="templateId"></param>
        /// <returns></returns>
        private async Task<(string WorkGroup, string ResponseArea, Guid jobId)> GetTemplatePropsAsync(Guid templateId)
        {
            logger.LogDebug("Дозагружаю недостающие данные из БД для шаблона {TemplateId}", templateId);

            var template = await templateRepository.Get()
                                           .AsNoTracking()
                                           // Загружаем дополнительные данные, чтоб быстрее считался шорткод
                                           .Include(t => t.Job).ThenInclude(t => t.Group).ThenInclude(t => t.GroupType)
                                           .FirstOrDefaultAsync(t => t.Id == templateId);

            if (template == null)
            {
                logger.LogWarning("Шаблон {TemplateId} не найден в БД", templateId);
                return ($"РГ {templateId}", $"ЗО {templateId}", templateId);
            }

            var (workGroup, responseArea) = await ApplyShortcodesAsync(template);

            // Запишем результат в кэш
            var cacheItem = new TemplateCache
            {
                Data = new TemplateCacheDto { TemplateId = template.Id, ResponseArea = responseArea, WorkGroup = workGroup, JobId = template.JobId, JobGroupId = template.Job!.GroupId },
                Source = nameof(WorkloadCacheService),
                Timestamp = DateTimeOffset.UtcNow
            };

            //await redisCacheService.SetHashFieldAsync(redisCacheService.GetKey(cacheTemplateKey), templateId.ToString(), cacheItem);
            await redisCacheService.SetHashFieldAsync(
                                            redisCacheService.GetKey(CacheKeys.Workload.TemplateHash()),
                                            redisCacheService.GetKey(CacheKeys.Workload.Template(templateId)),
                                            cacheItem
                                        );
            logger.LogDebug("Добавлена недостающая запись в кэш. {TemplateId}, '{WorkGroup}', '{ResponseArea}'", templateId, workGroup, responseArea);

            return (workGroup, responseArea, template.JobId);
        }

        /// <summary>
        /// Получить РГ и ЗО из шорткодов
        /// </summary>
        /// <param name="template"></param>
        /// <returns></returns>
        private async Task<(string WorkGroup, string ResponseArea)> ApplyShortcodesAsync(Template template)
        {
            var workGroup = await shortcodeService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
            var responseArea = await shortcodeService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);

            return (workGroup, responseArea);
        }


        /// <summary>
        /// Получить отчет Workload из кэша
        /// </summary>
        /// <param name="reportType"></param>
        /// <param name="dateStart"></param>
        /// <param name="durationDays"></param>
        /// <param name="offset"></param>
        /// <param name="filterParam">Наименование ЗО или РГ или null</param>
        /// <returns></returns>
        public async Task<WorkloadReport?> GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
        {
            var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);

            return await redisCacheService.GetCachedDataAsync<WorkloadReport>(key, true);
        }


        /// <summary>
        /// Записать отчет Workload в кэш
        /// </summary>
        /// <param name="report"></param>
        /// <param name="reportType"></param>
        /// <param name="dateStart"></param>
        /// <param name="durationDays"></param>
        /// <param name="offset"></param>
        /// <param name="filterParam">Наименование ЗО или РГ или null</param>
        /// <returns></returns>
        public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
        {
            var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
            await redisCacheService.SetCachedDataAsync(key, report, cacheWorkloadTtl, true);
        }


        /// <summary>
        /// Вернуть первую запись из TamplateCache если есть
        /// </summary>
        /// <returns></returns>
        public async Task<TemplateCache?> GetFirstTemplateCacheAsync()
        {
            //var cache = await redisCacheService.GetFirstHashFieldAsync<TemplateCache>(redisCacheService.GetKey(cacheTemplateKey));
            var cache = await redisCacheService.GetFirstHashFieldAsync<TemplateCache>(redisCacheService.GetKey(CacheKeys.Workload.TemplateHash()));

            return cache?.Value;
        }


        /// <summary>
        /// Формирует ключ для отчета Workload
        /// </summary>
        /// <param name="reportType"></param>
        /// <param name="dateStart"></param>
        /// <param name="durationDays"></param>
        /// <param name="offset"></param>
        /// <param name="filterParam"></param>
        /// <returns></returns>
        private string GetReportCacheKey(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam = null)
        {
            // формируе ключ, вида: основа + дата начала + продолжительность + смещение
            // Если есть параметр (filterParam), добавляется параметр в виде Hash

            var keyList = new List<string>();

            switch (reportType)
            {
                case WorkloadReportType.ResponseAreaAll:
                    //keyList.AddRange(cacheWorkloadResponseAreaAllKey);
                    keyList.AddRange(CacheKeys.Workload.WorkloadResponseAreaAll());
                    break;
                case WorkloadReportType.ResponseAreaWorkGroups:
                    keyList.AddRange(CacheKeys.Workload.WorkloadResponseAreaItem());
                    break;
                case WorkloadReportType.WorkGroupAll:
                    keyList.AddRange(CacheKeys.Workload.WorkloadWorkGroupAll());
                    break;
                case WorkloadReportType.WorkGroupJob:
                    keyList.AddRange(CacheKeys.Workload.WorkloadWorkGroupItem());
                    break;
                default:
                    logger.LogError("Недопустимое значение тип отчета '{ReportType}'", reportType);
                    throw new ArgumentException("Недопустимое значение тип отчета", nameof(reportType));
            }

            keyList.AddRange(new string[] { dateStart.ToString("yyyy-MM-dd"), durationDays.ToString(), offset.ToString() });

            var keyStr = string.Empty;

            if (filterParam == null)
                keyStr = redisCacheService.GetKey(keyList.ToArray());
            else
                keyStr = redisCacheService.GetKey(keyList.ToArray(), new string[] { filterParam });

            logger.LogDebug("Сформирован ключ для кэш '{Key}'", keyStr);

            return keyStr;
        }
    }
}
