using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.Job;
using PARR.Core.Repositories.Interfaces.Schedule;
using PARR.Core.Services.NextRunServices.Models;
using PARR.Core.Services.NextRunServices.Subservices;
using PARR.Core.Services.Shortcodes;
using PARR.Domain.Entities;
using PARR.Domain.Entities.Job;
using PARR.Domain.Enums;
using PARR.Domain.Settings;

namespace PARR.Core.Services.NextRunServices
{
    internal class NextRunService : INextRunService
    {
        private readonly ILogger<NextRunService> logger;
        private readonly ITemplateRepository templateService;
        private readonly IJobGroupRepository jobGroupService;
        private readonly IEsppScheduleTransformService esppScheduleTransformService;
        private readonly ITemplateDistributor templateDistributor;
        private readonly IShortcodesService shortcodesService;
        private readonly IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService;
        private readonly SettingsFromDb settingsFromDb;

        public NextRunService(
            ILogger<NextRunService> logger,
            ITemplateRepository templateService,
            IJobGroupRepository jobGroupService,
            IEsppScheduleTransformService esppScheduleTransformService,
            ITemplateDistributor templateDistributor,
            IShortcodesService shortcodesService,
            IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService,
            SettingsFromDb settingsFromDb
            )
        {
            this.logger = logger;
            this.templateService = templateService;
            this.jobGroupService = jobGroupService;
            this.esppScheduleTransformService = esppScheduleTransformService;
            this.templateDistributor = templateDistributor;
            this.shortcodesService = shortcodesService;
            this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
            this.settingsFromDb = settingsFromDb;
        }


        public async Task<List<TemplateNextRunResultDto>?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId, TemplateStatusTypeEnum? templateStatusType)
        {
            logger.LogInformation("Начинаю распределять шаблоны для группы работ {gobGroupId}, templateStatusType: {templateStatusType}", jobGroupId, templateStatusType);

            var jobGroup = await jobGroupService.Get()
                                   .AsNoTracking()
                                   .Include(t => t.DistributionConfig)
                                       .ThenInclude(t => t.DistributionPeriod)
                                   .FirstOrDefaultAsync(t => t.Id == jobGroupId);

            if (jobGroup == null)
            {
                logger.LogError("Не найдена группа работа с id: {id}", jobGroupId);
                return null;
            }

            if (!jobGroup.IsAutoDistributionEnabled || jobGroup.DistributionConfig == null)
            {
                logger.LogError("Группа работ id: {id} не подходит для автораспределения, у нее или отсутствуют настройки или не включено автораспределение. IsAutoDistributionEnabled: {IsAutoDistributionEnabled}. Есть конфиг: {DistributionConfig}", jobGroupId, jobGroup.IsAutoDistributionEnabled, jobGroup.DistributionConfig != null);
                return null;
            }

            logger.LogInformation("Параметры распределения. groupId: {groupId}, name: {groupName}, referenceDate: {referenceDate}, " +
                       "distributionPeriodName: {distributionPeriodName}, distributionPeriodDuration: {distributionPeriodDuration}, " +
                       "distributionPeriodType: {distributionPeriodType}, IsExcludeWeekends: {IsExcludeWeekends}, IsGroupingByWorkGroup: {IsGroupingByWorkGroup}, IsResponseAreaTimezone: {IsResponseAreaTimezone}",
                       jobGroupId, jobGroup.GroupName, jobGroup.ReferenceDate, jobGroup.DistributionConfig.DistributionPeriod.Name, jobGroup.DistributionConfig.DistributionPeriod.Duration, jobGroup.DistributionConfig.DistributionPeriod.Type,
                       jobGroup.DistributionConfig.IsExcludeWeekends, jobGroup.DistributionConfig.IsGroupingByWorkGroup, jobGroup.IsResponseAreaTimezone);

            //получаем шаблоны только в статусе Used
            var templateQuery = templateService.Get()
                                                    .AsNoTracking()
                                                    .Include(t => t.Job)
                                                    .Where(t =>
                                                        t.Job!.GroupId == jobGroupId
                                                    //&& t.StatusTypeId == TemplateStatusTypeEnum.Used
                                                    );

            if (templateStatusType.HasValue)
                templateQuery = templateQuery.Where(t => t.StatusTypeId == templateStatusType);


            var allTemplates = await templateQuery.ToListAsync();

            if (!allTemplates.Any())
            {
                logger.LogInformation("В группе c ИД {jobGroupId} отсутствуют шаблоны в статусе Used", jobGroupId);
                return null;
            }

            logger.LogDebug("Всего шаблонов для распределения в статусе Used: {count} шт.", allTemplates.Count);


            // продолжительность в днях
            var durationDays = GetDurationDays(jobGroup.DistributionConfig);

            // группировать по РГ
            var isGroupingByWorkGroup = jobGroup.DistributionConfig.IsGroupingByWorkGroup;

            // использовать часовой пояс РГ
            var isResponseAreaTimeZone = jobGroup.IsResponseAreaTimezone;

            var distributedTemplates = new List<TemplateNextRunResultDto>();

            if (isGroupingByWorkGroup)
            {
                // группировать по РГ
                logger.LogDebug("Нужно группировать по РГ");

                // получаем для каждого шаблона РГ и сразу группируем по РГ
                var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
                foreach (var template in allTemplates)
                {
                    var workGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
                    // Если даже workGroupName == null, ну и ладно, сгруппируем по null, и распределим шаблоны в рамках этого null
                    if (workGroupName == null)
                        logger.LogWarning("Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'",
                                                    template.Id, template.Job!.WorkGroupMask, workGroupName);

                    // Добавляем шаблон в группу
                    if (!templatesByWorkGroup.TryGetValue(workGroupName, out var templatesList))
                    {
                        templatesList = new List<Template>();
                        templatesByWorkGroup[workGroupName] = templatesList;
                    }
                    templatesList.Add(template);
                }
                logger.LogDebug("Групп для распределения: {count}", templatesByWorkGroup.Count);

                var dateStart = GetDateStart();

                // Будем распределять каждую РГ отдельно
                foreach (var (workGroupName, templates) in templatesByWorkGroup)
                {
                    logger.LogDebug("Готовлюсь распределать шаблоны {templateCount} шт. в РГ '{workGroup}'", templates.Count, workGroupName);

                    if (isResponseAreaTimeZone)
                    {
                        // использовать часовой пояс РГ
                        logger.LogDebug("Использовать часовой пояс РГ");

                        // считаем, что у всех шаблонов сгруппированных по РГ, одна ЗО
                        // получаем ЗО для первого шаблона
                        var firstTemplate = templates.First();
                        var responseArea = await shortcodesService.ApplyShortcodesAsync(firstTemplate.Job!.ResponseAreaMask, firstTemplate);
                        if (responseArea == null)
                            logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
                                                            firstTemplate.Id, firstTemplate.Job!.ResponseAreaMask, responseArea);

                        var offset = GetOffsetForDistributionByResponseArea(responseArea);
                        var templatesForDistribute = templates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
                        var referenceDate = GetReferenceDate(jobGroup, responseArea);

                        logger.LogInformation("Буду распределать шаблоны {count} шт, сгруппированные по РГ '{workGroup}', использую часовой пояс ЗО. dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}",
                                                                        templatesForDistribute.Count, workGroupName, dateStart, offset, referenceDate);

                        var distributionResult = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);

                        logger.LogDebug("Закончил распределение для РГ '{workGroupName}', referenceDate: {referenceDate}, часового пояса ЗО {responseArea}, utcOffset: {offset}, распределил шаблонов: {count} шт.",
                                                                            workGroupName, referenceDate, responseArea, offset, distributionResult.Count);

                        distributedTemplates.AddRange(distributionResult);
                    }
                    else
                    {
                        // не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
                        logger.LogDebug("Не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)");

                        var offset = GetOffsetForDistributionByResponseArea();
                        var templatesForDistribute = templates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
                        var referenceDate = GetReferenceDate(jobGroup, null);

                        logger.LogInformation("Буду распределать шаблоны {count} шт, сгруппированные по РГ '{workGroup}', НЕ НУЖНО использовать часовой пояс ЗО. dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}",
                                                                        templatesForDistribute.Count, workGroupName, dateStart, offset, referenceDate);

                        var distributionResult = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);

                        logger.LogDebug("Закончил распределение для РГ '{workGroup}', referenceDate: {referenceDate}, utcOffset: {offset}, распределил шаблонов: {count} шт.", workGroupName, referenceDate, offset, distributionResult.Count);

                        distributedTemplates.AddRange(distributionResult);
                    }
                }
            }
            else
            {
                //не нужно группировать по РГ
                logger.LogDebug("Не нужно группировать по РГ");

                if (isResponseAreaTimeZone)
                {
                    // использовать ЧАСОВОЙ ПОЯС ЗО (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)
                    logger.LogDebug("Использовать ЧАСОВОЙ ПОЯС ЗО (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)");

                    // получаем для каждого шаблона ЗО и сразу группируем по ЗО
                    var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
                    foreach (var template in allTemplates)
                    {
                        var responseArea = await shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
                        if (responseArea == null)
                            logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
                                                            template.Id, template.Job!.ResponseAreaMask, responseArea);

                        // Добавляем шаблон в группу
                        if (!templatesByResponseArea.TryGetValue(responseArea, out var templateList))
                        {
                            templateList = new List<Template>();
                            templatesByResponseArea[responseArea] = templateList;
                        }
                        templateList.Add(template);
                    }
                    logger.LogDebug("Групп для распределения: {count}", templatesByResponseArea.Count);

                    var dateStart = GetDateStart();

                    // для каждой ЗО получить offset и распределить шаблоны
                    foreach (var (responseArea, templates) in templatesByResponseArea)
                    {
                        var offset = GetOffsetForDistributionByResponseArea(responseArea);
                        var templatesForDistribute = templates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
                        var referenceDate = GetReferenceDate(jobGroup, responseArea);

                        logger.LogInformation("Буду распределять шаблоны {count} шт, их НЕ НУЖНО группировать по РГ, НУЖНО использовать часовой пояс ЗО: {responseArea}, utcOffset: {offset}. dateStart: {dateStart}, referenceDate: {referenceDate}",
                                                       templatesForDistribute.Count, responseArea, offset, dateStart, referenceDate);

                        var distributionResult = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);

                        logger.LogDebug("Закончил распределение для часового пояса ЗО {responseArea}, utcOffset: {offset}, referenceDate: {referenceDate}, распределил шаблонов: {count} шт.", responseArea, offset, referenceDate, distributionResult.Count);

                        distributedTemplates.AddRange(distributionResult);
                    }
                }
                else
                {
                    // не нужно использовать часовой пояс ЗО
                    logger.LogDebug("Не нужно использовать часовой пояс ЗО. Распределю все шаблоны без группировки.");

                    var offset = GetOffsetForDistributionByResponseArea();
                    var templatesForDistribute = allTemplates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
                    var referenceDate = GetReferenceDate(jobGroup, null);
                    var dateStart = GetDateStart();

                    logger.LogInformation("Буду распределать шаблоны {count} шт, их НЕ НУЖНО группировать по РГ, НЕ НУЖНО использовать часовой пояс ЗО. dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}",
                                                                        templatesForDistribute.Count, dateStart, offset, referenceDate);

                    distributedTemplates = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);
                }
            }

            logger.LogInformation("Закончил распределение шаблонов {count} шт. для jobGroupId: {jobGroupId}. Изменено: {changedCount}, без изменений: {oldCount}",
                                            distributedTemplates.Count, jobGroupId, distributedTemplates.Count(t => t.NextRun != t.NextRunOld), distributedTemplates.Count(t => t.NextRun == t.NextRunOld));

            return distributedTemplates;
        }


        public async Task<DateTimeOffset?> GetNextRunForNewTemplateAsync(Guid jobGroupId, string workGroupName, string responseAreaName)
        {
            logger.LogDebug("Получить nextRun для вновь создаваемого шаблона. jobGroupId: {jobGroupId}, workGroupName: {workGroupName}, responseAreaName: {responseAreaName}",
                            jobGroupId, workGroupName, responseAreaName);

            var jobGroup = await jobGroupService.Get()
                                        .AsNoTracking()
                                        .Include(t => t.DistributionConfig)
                                            .ThenInclude(t => t.DistributionPeriod)
                                        .FirstOrDefaultAsync(t => t.Id == jobGroupId);

            if (jobGroup == null)
            {
                logger.LogError("Не найдена группа работ с Id: {jobGroupId}.", jobGroupId);
                //throw new ArgumentNullException(nameof(jobGroupId), $"Не найдена группа работ с Id: {jobGroupId}");
                return null;
            }

            if (jobGroup.IsAutoDistributionEnabled)
            {
                // автораспределение
                logger.LogDebug("Расчет используя метод распределения работ");

                if (jobGroup.DistributionConfig == null)
                {
                    logger.LogError("Для группы работ {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}", jobGroupId, nameof(JobGroupDistributionConfig));
                    //throw new ArgumentNullException(nameof(JobGroupDistributionConfig), $"Для jobGroupId: {jobGroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}");
                    return null;
                }

                var startDate = GetDateStart();
                var duration = GetDurationDays(jobGroup.DistributionConfig);
                var excludeWeekends = jobGroup.DistributionConfig.IsExcludeWeekends;
                var targetTemplate = new TemplateNextRunDto(Guid.Empty, null);
                var templateStatusType = TemplateStatusTypeEnum.Used;
                var allTemplatesQuery = templateService.Get()
                                              .AsNoTracking()
                                              .Include(t => t.Job)
                                              .Where(t => t.StatusTypeId == templateStatusType && t.Job!.GroupId == jobGroupId);

                // группировать по РГ
                var isGroupingByWorkGroup = jobGroup.DistributionConfig.IsGroupingByWorkGroup;

                // использовать часовой пояс РГ
                var isResponseAreaTimeZone = jobGroup.IsResponseAreaTimezone;

                if (isGroupingByWorkGroup)
                {
                    // группировать по РГ
                    logger.LogDebug("Нужно группировать по РГ");

                    // получаем для всех шаблонов РГ и группируем их по РГ
                    var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
                    var allTemplates = await allTemplatesQuery.ToListAsync();
                    foreach (var template in allTemplates)
                    {
                        var templateWorkGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);

                        // Если даже workGroupName == null, ну и ладно, сгруппируем по null
                        if (templateWorkGroupName == null)
                            logger.LogWarning("Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'",
                                                        template.Id, template.Job!.WorkGroupMask, templateWorkGroupName);

                        // Добавляем шаблон в группу
                        if (!templatesByWorkGroup.TryGetValue(templateWorkGroupName, out var templatesList))
                        {
                            templatesList = new List<Template>();
                            templatesByWorkGroup[templateWorkGroupName] = templatesList;
                        }
                        templatesList.Add(template);
                    }

                    //Берем шаблоны только нашей РГ workGroupName
                    templatesByWorkGroup.TryGetValue(workGroupName, out var templatesWithWorkGroup);

                    var templatesByWorkGroupDto = (templatesWithWorkGroup ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();

                    logger.LogDebug("Шаблонов в РГ {workGroupName} - {count} шт.", workGroupName, templatesByWorkGroupDto.Count);

                    if (isResponseAreaTimeZone)
                    {
                        // использовать часовой пояс РГ
                        logger.LogDebug("Использовать часовой пояс РГ '{responseAreaName}'", responseAreaName);

                        var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
                        var referenceDate = GetReferenceDate(jobGroup, responseAreaName);

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew: true);

                        return nextRun.NextRun;
                    }
                    else
                    {
                        // не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
                        logger.LogDebug("Не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)");

                        var offset = GetOffsetForDistributionByResponseArea();
                        var referenceDate = GetReferenceDate(jobGroup, null);

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew: true);

                        return nextRun.NextRun;
                    }
                }
                else
                {
                    // не нужно группировать по РГ
                    logger.LogDebug("Не нужно группировать по РГ");

                    if (isResponseAreaTimeZone)
                    {
                        // использовать ЧАСОВОЙ ПОЯС ЗО

                        logger.LogDebug("Нужно использовать часовой пояс ЗО '{responseAreaName}'", responseAreaName);

                        // получить все шаблоны, сгруппировать их по ЗО, получить nextRun в рамках переданной ЗО
                        var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
                        var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
                        var allTemplates = await allTemplatesQuery.ToListAsync();

                        // получаем для каждого шаблона ЗО и сразу группируем по ЗО
                        var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
                        foreach (var template in allTemplates)
                        {
                            var responseArea = await shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
                            if (responseArea == null)
                                logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
                                                                template.Id, template.Job!.ResponseAreaMask, responseArea);

                            // Добавляем шаблон в группу
                            if (!templatesByResponseArea.TryGetValue(responseArea, out var templateList))
                            {
                                templateList = new List<Template>();
                                templatesByResponseArea[responseArea] = templateList;
                            }
                            templateList.Add(template);
                        }

                        // из сгруппированных шаблонов, получаем существующие с нашей переданной responseAreaName
                        templatesByResponseArea.TryGetValue(responseAreaName, out var templatesWithResponseArea);
                        var templatesWithResponseAreaDto = (templatesWithResponseArea ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();

                        logger.LogDebug("Существующих шаблонов в БД с responseAreaName: {responseAreaName} - {count} шт.", responseAreaName, templatesWithResponseAreaDto.Count);

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesWithResponseAreaDto, excludeWeekends, isNew: true);

                        return nextRun.NextRun;
                    }
                    else
                    {
                        // не нужно использовать часовой пояс ЗО
                        logger.LogDebug("Не нужно использовать часовой пояс ЗО.");

                        var referenceDate = GetReferenceDate(jobGroup, null);
                        var offset = GetOffsetForDistributionByResponseArea();
                        var allTemplates = await allTemplatesQuery
                                                    .Select(t => new TemplateNextRunDto(t.Id, t.NextRun))
                                                    .ToListAsync();

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, allTemplates, excludeWeekends, isNew: true);

                        return nextRun.NextRun;
                    }
                }
            }
            else
            {
                // расписание ЕСПП
                logger.LogDebug("Расчет используя метод ЕСПП");

                if (jobGroup.IsResponseAreaTimezone)
                {
                    // использовать часовой пояс ЗО

                    var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
                    var offset = GetOffsetForDistributionByResponseArea(responseAreaName);

                    logger.LogDebug("Расчет nextRun используя метод ЕСПП, использую часовой пояс ЗО '{responseAreaName}, referenceDate: {referenceDate}, offset: {offset}'", responseAreaName, referenceDate, offset);

                    var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroupId, referenceDate, offset);

                    return nextRun;
                }
                else
                {
                    // не использовать часовой пояс ЗО
                    // так как это классическое расписание еспп, и не используется часовой пояс РГ, то нам нужно считать в часовом поясе УЗ ЕСПП, т.е. МСК

                    var referenceDate = GetReferenceDate(jobGroup, null);
                    var offset = GetOffsetForDistributionByResponseArea();

                    logger.LogDebug("Расчет nextRun используя метод ЕСПП, не использую часовой пояс ЗО, использую часовой пояс УЗ ЕСПП, referenceDate: {referenceDate}, offset: {offset}", referenceDate, offset);

                    var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroupId, referenceDate, offset);

                    return nextRun;
                }
            }
        }


        public async Task<DateTimeOffset?> GetNextRunForTemplateAsync(Guid templateId, bool isNew)
        {
            logger.LogDebug("Получить nextRun для существующего шаблона, templateId: {templateId}, isNew: {isNew}", templateId, isNew);

            var template = await templateService.Get()
                             .AsNoTracking()
                             .Include(t => t.Job)
                                 .ThenInclude(t => t.Group).ThenInclude(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod)
                             .FirstOrDefaultAsync(t => t.Id == templateId);

            if (template == null)
            {
                logger.LogError("Не найден шаблон с Id: {templateId}.", templateId);
                //throw new ArgumentNullException(nameof(templateId), $"Не найден шаблон с Id: {templateId}");
                return null;
            }

            if (template.Job!.Group!.IsAutoDistributionEnabled == true)
            {
                // включено автораспределение
                logger.LogDebug("Включено автораспределение");

                if (template.Job!.Group.DistributionConfig == null)
                {
                    logger.LogError("Для шаблона {templateId}, jobGroupId {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}", template.Id, template.Job.GroupId, nameof(JobGroupDistributionConfig));
                    // throw new ArgumentNullException(nameof(JobGroupDistributionConfig), $"Для jobGroupId: {template.Job.GroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}");
                    return null;
                }

                var startDate = GetDateStart();
                var duration = GetDurationDays(template.Job.Group.DistributionConfig);
                var excludeWeekends = template.Job.Group.DistributionConfig.IsExcludeWeekends;
                var targetTemplate = new TemplateNextRunDto(template.Id, template.NextRun);
                var templateStatusType = TemplateStatusTypeEnum.Used;

                var jobGroup = template.Job.Group;

                // получаем все шаблоны из этой же группы что и template
                var allTemplatesQuery = templateService.Get()
                                        .AsNoTracking()
                                        .Include(t => t.Job)
                                        .Where(t => t.StatusTypeId == templateStatusType && t.Job!.GroupId == jobGroup.Id);

                // группировать по РГ
                var isGroupingByWorkGroup = jobGroup.DistributionConfig.IsGroupingByWorkGroup;

                // использовать часовой пояс РГ
                var isResponseAreaTimeZone = jobGroup.IsResponseAreaTimezone;

                if (isGroupingByWorkGroup)
                {
                    var targetTemplateWorkGroup = await shortcodesService.ApplyShortcodesAsync(template.Job.WorkGroupMask, template);

                    // группировать по РГ
                    logger.LogDebug("Нужно группировать по РГ");

                    // получаем для всех шаблонов РГ и группируем их по РГ
                    var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
                    var allTemplates = await allTemplatesQuery.ToListAsync();
                    foreach (var templateItem in allTemplates)
                    {
                        var templateWorkGroupName = await shortcodesService.ApplyShortcodesAsync(templateItem.Job!.WorkGroupMask, templateItem);

                        // Если даже workGroupName == null, ну и ладно, сгруппируем по null
                        if (templateWorkGroupName == null)
                            logger.LogWarning("Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'",
                                                        templateItem.Id, templateItem.Job!.WorkGroupMask, templateWorkGroupName);

                        // Добавляем шаблон в группу
                        if (!templatesByWorkGroup.TryGetValue(templateWorkGroupName, out var templatesList))
                        {
                            templatesList = new List<Template>();
                            templatesByWorkGroup[templateWorkGroupName] = templatesList;
                        }
                        templatesList.Add(templateItem);
                    }

                    //Берем шаблоны только нашей РГ workGroupName
                    templatesByWorkGroup.TryGetValue(targetTemplateWorkGroup, out var templatesWithWorkGroup);

                    var templatesByWorkGroupDto = (templatesWithWorkGroup ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();

                    logger.LogDebug("Шаблонов в РГ {targetTemplateWorkGroup} - {count} шт.", targetTemplateWorkGroup, templatesByWorkGroupDto.Count);

                    if (isResponseAreaTimeZone)
                    {
                        // использовать часовой пояс РГ

                        var targetTemplateResponseArea = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);

                        logger.LogDebug("Использовать часовой пояс РГ '{responseAreaName}'", targetTemplateResponseArea);

                        var offset = GetOffsetForDistributionByResponseArea(targetTemplateResponseArea);
                        var referenceDate = GetReferenceDate(jobGroup, targetTemplateResponseArea);

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew);

                        return nextRun.NextRun;
                    }
                    else
                    {
                        // не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
                        logger.LogDebug("Не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)");

                        var offset = GetOffsetForDistributionByResponseArea();
                        var referenceDate = GetReferenceDate(jobGroup, null);

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew);

                        return nextRun.NextRun;
                    }
                }
                else
                {
                    // не нужно группировать по РГ
                    logger.LogDebug("Не нужно группировать по РГ");

                    if (isResponseAreaTimeZone)
                    {
                        // использовать ЧАСОВОЙ ПОЯС ЗО

                        var responseAreaName = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);

                        logger.LogDebug("Нужно использовать часовой пояс ЗО '{responseAreaName}'", responseAreaName);

                        // получить все шаблоны, сгруппировать их по ЗО, получить nextRun в рамках переданной ЗО
                        var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
                        var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
                        var allTemplates = await allTemplatesQuery.ToListAsync();

                        // получаем для каждого шаблона ЗО и сразу группируем по ЗО
                        var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
                        foreach (var templateItem in allTemplates)
                        {
                            var responseArea = await shortcodesService.ApplyShortcodesAsync(templateItem.Job!.ResponseAreaMask, templateItem);
                            if (responseArea == null)
                                logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
                                                                templateItem.Id, templateItem.Job!.ResponseAreaMask, responseArea);

                            // Добавляем шаблон в группу
                            if (!templatesByResponseArea.TryGetValue(responseArea, out var templateList))
                            {
                                templateList = new List<Template>();
                                templatesByResponseArea[responseArea] = templateList;
                            }
                            templateList.Add(templateItem);
                        }

                        // из сгруппированных шаблонов, получаем существующие с ЗО нашего шаблона responseAreaName
                        templatesByResponseArea.TryGetValue(responseAreaName, out var templatesWithResponseArea);
                        var templatesWithResponseAreaDto = (templatesWithResponseArea ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();

                        logger.LogDebug("Существующих шаблонов в БД с responseAreaName: {responseAreaName} - {count} шт.", responseAreaName, templatesWithResponseAreaDto.Count);

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesWithResponseAreaDto, excludeWeekends, isNew);

                        return nextRun.NextRun;
                    }
                    else
                    {
                        // не нужно использовать часовой пояс ЗО
                        logger.LogDebug("Не нужно использовать часовой пояс ЗО.");

                        var referenceDate = GetReferenceDate(jobGroup, null);
                        var offset = GetOffsetForDistributionByResponseArea();
                        var allTemplates = await allTemplatesQuery
                                                    .Select(t => new TemplateNextRunDto(t.Id, t.NextRun))
                                                    .ToListAsync();

                        var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, allTemplates, excludeWeekends, isNew);

                        return nextRun.NextRun;
                    }
                }
            }
            else
            {
                // метод расчета ЕСПП
                logger.LogDebug("Метод расчета ЕСПП");

                if (template.Job.Group.IsResponseAreaTimezone)
                {
                    // использовать часовой пояс ЗО
                    var responseAreaName = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);
                    var jobGroup = template.Job.Group;

                    var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
                    var offset = GetOffsetForDistributionByResponseArea(responseAreaName);

                    logger.LogDebug("Расчет nextRun используя метод ЕСПП, использую часовой пояс ЗО '{responseAreaName}, referenceDate: {referenceDate}, offset: {offset}'", responseAreaName, referenceDate, offset);

                    var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroup.Id, referenceDate, offset);

                    return nextRun;
                }
                else
                {
                    // не использовать часовой пояс ЗО
                    // так как это классическое расписание еспп, и не используется часовой пояс РГ, то нам нужно считать в часовом поясе УЗ ЕСПП, т.е. МСК

                    var referenceDate = GetReferenceDate(template.Job.Group, null);
                    var offset = GetOffsetForDistributionByResponseArea();

                    logger.LogDebug("Расчет nextRun используя метод ЕСПП, не использую часовой пояс ЗО, использую часовой пояс УЗ ЕСПП, referenceDate: {referenceDate}, offset: {offset}", referenceDate, offset);

                    var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job.GroupId, referenceDate, offset);

                    return nextRun;
                }

            }
        }


        public int GetDurationDays(JobGroupDistributionConfig config)
        {
            var period = config.DistributionPeriod;
            var periodType = period!.Type;
            int.TryParse(period.Duration, out var duration);

            var periodDays = duration;

            if (periodType == DistributionPeriodTypeEnum.Day.ToString())
                periodDays = duration;


            if (periodType == DistributionPeriodTypeEnum.Month.ToString())
                // в месяце 30 дней, duration*30
                periodDays = duration * 30;


            if (periodType == DistributionPeriodTypeEnum.Year.ToString())
                // в году 365 дней, duration*365
                periodDays = duration * 365;


            logger.LogDebug("Период распределения: {name}, duration: {duration}, type: {type}. Итого в днях: {days}", period.Name, period.Duration, period.Type, periodDays);

            if (periodDays == 0)
            {
                periodDays = 1;
                logger.LogWarning("Полученный период распределения 0 дней. Неверный конфиг распределения в таблице {table}. {name}, duration: {duration}, type: {type}. Устанавливаем минимальный период распределения {periodDays} дней.", nameof(DistributionPeriod), period.Name, period.Duration, period.Type, periodDays);
            }

            return periodDays;
        }


        public async Task<List<DateOnly>> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends)
        {
            return await templateDistributor.GetWorkDaysAsync(start, end, excludeWeekends);
        }


        public async Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate)
        {
            return await templateDistributor.GetWeekendsAsync(startDate, endDate);
        }

        /// <summary>
        /// Получить часовую зону для распределения
        /// </summary>
        /// <returns></returns>
        private TimeSpan GetOffsetForDistributionByResponseArea(string? responseArea = null)
        {
            // или часовая зона робота, или часовая зона РГ
            if (responseArea != null)
            {
                // возвращаем часовой пояс ЗО
                var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;
                logger.LogDebug("Получил часовой пояс для ЗО: {responseArea}, UtcTimeOffset: {responseAreaOffset}", responseArea, responseAreaOffset);

                return responseAreaOffset;
            }
            else
            {
                // возвращаем часовой пояс робота
                var esppOffset = GetEsppAccountOffset();
                logger.LogDebug("Не передана ЗО, использую часовой пояс УЗ ЕСПП, UtcTimeOffset: {UtcTimeOffset}", esppOffset);

                return esppOffset;
            }
        }


        public TimeSpan GetEsppAccountOffset()
        {
            var esppOffset = TimeSpan.FromHours(settingsFromDb.EsppRobotAccountTimeZoneHour);
            logger.LogDebug("Оффсет УЗ ЕСПП: {esppOffset}", esppOffset);

            return esppOffset;
        }


        /// <summary>
        /// Получить дату начала распределения
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        private DateOnly GetDateStart()
        {
            //var dateStart = DateOnly.FromDateTime(DateTimeOffset.UtcNow.ToOffset(offset).Date);
            //logger.LogDebug("Дата начала распределения (сегодня) в целевом часовом поясе {dateStart}, offset: {offset}", dateStart, offset);
            var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Date);
            logger.LogDebug("Дата начала распределения (сегодня): {dateStart}", dateStart);

            return dateStart;
        }


        /// <summary>
        /// Получить referenceDate для расчета с учетом часового пояса ЗО или без учета
        /// </summary>
        /// <param name="jobGroup"></param>
        /// <param name="responseArea">Не обзяательный параметр, нужен обязательно при расчете с учетом часового пояса ЗО</param>
        /// <returns></returns>
        private DateTimeOffset GetReferenceDate(JobGroup jobGroup, string? responseArea)
        {
            // для того чтобы посчитать nextRun в часовом поясе ЗО, нужно пересчитать refDate чтобы понять в какое время хотел выполнять заказчик

            logger.LogDebug("Исходный referenceDate UTC: {referenceDate}", jobGroup.ReferenceDate);

            if (jobGroup.IsResponseAreaTimezone)
            {
                // нужно вернуть с учетом часового пояса клиента сохранившего refDate (используется для расчета с учетом часового пояса ЗО)

                var clientOffset = jobGroup.UserTimeZoneOffset;

                if (!clientOffset.HasValue)
                {
                    logger.LogError("При расчете referenceDate для ЗО '{responseArea}', UserTimeZoneOffset=null. Вернул referenceDate {referenceDate} без смещения ЗО",
                                                            responseArea, jobGroup.ReferenceDate);
                    return jobGroup.ReferenceDate;
                }

                if (responseArea == null)
                {
                    logger.LogError("При расчете referenceDate для ЗО '{responseArea}', responseArea=null. Вернул referenceDate {referenceDate} без смещения ЗО",
                                                         responseArea, jobGroup.ReferenceDate);
                    return jobGroup.ReferenceDate;
                }

                var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;

                // = реф + офКлиента(который нажал в ГУИ сохранить)-офЗО

                var resultOffset = clientOffset.Value - responseAreaOffset;

                var refDateWithResponseAreaOffset = jobGroup.ReferenceDate.Add(resultOffset);

                logger.LogDebug("Клиент ожидает, что будет выполняться задача в каждой ЗО в {time}. referenceDate+clientOffset, {referenceDate}, {clientOffset}",
                                                         TimeOnly.FromDateTime(jobGroup.ReferenceDate.Add(clientOffset.Value).DateTime), jobGroup.ReferenceDate, clientOffset);
                logger.LogDebug("Итоговый referenceDate UTC: {resultRefDate}, для использования часовой зоны ЗО: '{responseArea}', добавлено времени {resultOffset} к исходному {refDate}",
                                            refDateWithResponseAreaOffset, responseArea, resultOffset, jobGroup.ReferenceDate);
                logger.LogDebug("Если итоговый referenceDate UTC {refDateWithResponseAreaOffset} перевести в часовой пояс ЗО {responseAreaOffset}, то дата в часовом поясе ЗО будет {refDateTargetTz}",
                                                        refDateWithResponseAreaOffset, responseAreaOffset, refDateWithResponseAreaOffset.Add(responseAreaOffset));

                return refDateWithResponseAreaOffset;
            }
            else
            {
                // если НЕ НУЖНО в часовом поясе ЗО, то просто возвращаем refDate
                logger.LogDebug("Итоговый referenceDate UTC: {refDate}, не нужно использовать часовой пояс ЗО", jobGroup.ReferenceDate);

                return jobGroup.ReferenceDate;
            }
        }

    }
}
