using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests.BaseRequests;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Contracts.V1.Responses.Statistics;
using PARR.API.Controllers.V1.Base;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.Job;
using PARR.Core.Repositories.Interfaces.Schedule;
using PARR.Core.Services.NextRunServices;
using PARR.Core.Services.Shortcodes;
using PARR.Domain.Common.Roles;
using PARR.Domain.Entities;
using PARR.Domain.Entities.Job;
using PARR.Domain.Enums;

namespace PARR.API.Controllers.V1.Statistics
{
    /// <summary>
    /// Статистика распределения шаблонов
    /// </summary>
    [Authorize(Roles = ParrRoles.Administrator.Role)]
    public class StatTemplateDistributionController : BaseApiController
    {
        private readonly ITemplateRepository templateService;
        private readonly IJobGroupRepository jobGroupService;
        private readonly IMapper mapper;
        private readonly IShortcodesService shortcodesService;
        private readonly INextRunService nextRunService;
        private readonly ILogger<StatTemplateDistributionController> logger;
        private readonly IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService;

        public StatTemplateDistributionController(
            ITemplateRepository templateService,
            IJobGroupRepository jobGroupService,
            IMapper mapper,
            IShortcodesService shortcodesService,
            INextRunService nextRunService,
            ILogger<StatTemplateDistributionController> logger,
            IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService
            )
        {
            this.templateService = templateService;
            this.jobGroupService = jobGroupService;
            this.mapper = mapper;
            this.shortcodesService = shortcodesService;
            this.nextRunService = nextRunService;
            this.logger = logger;
            this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
        }


        /// <summary>
        /// Статистика распределения шаблонов по jobGroupId, только для Used
        /// </summary>
        /// <param name="jobGroupId"></param>
        /// <returns></returns>
        [HttpGet(ApiRoutes.StatTemplateDistribution.Get)]
        public async Task<IActionResult> Get([FromRoute] Guid jobGroupId, [FromQuery] TimeZoneOffsetClient timeZoneQuery)
        {
            // eadc5498-dba6-4f10-9b4b-a1653e3c3e61

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

            if (jobGroup == null)
                return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Не найдена группа работ" } }));

            if (!jobGroup.IsAutoDistributionEnabled || jobGroup.DistributionConfig == null)
                return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Для группы работ не включено автораспределение" } }));

            var items = await templateService.Get()
                                    .Where(t => t.Job!.GroupId == jobGroupId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
                                    .Select(t => new
                                    {
                                        WorkGroupMask = t.Job!.WorkGroupMask,
                                        ResponseAreaMask = t.Job!.ResponseAreaMask,
                                        Template = t
                                    })
                                    .AsNoTracking()
                                    .ToListAsync();

            if (!items.Any())
                return Ok(new Response<StatTemplateDistributorResponse>(new StatTemplateDistributorResponse
                {
                    JobGroup = mapper.Map<JobGroupWithDistributionConfigResponse>(jobGroup),
                    ItemsList = null
                }, true));


            var (dateStart, dateEnd) = GetStartEndPeriod(jobGroup, items.Min(t => t.Template.NextRun), items.Max(t => t.Template.NextRun), timeZoneQuery.TimeZoneOffset);

            // получаем список рабочих дней
            // получет все дни, даже с выходными, чтоб видеть реальную картину
            var allDays = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false);

            // получить список выходных дней (показываем только если IsExcludeWeekends = true)
            var weekends = jobGroup.DistributionConfig.IsExcludeWeekends
                                        ? await nextRunService.GetWeekendsAsync(dateStart, dateEnd)
                                        : new HashSet<DateOnly>();


            var responseItemsList = new List<StatDistributorItemResponse>();

            if (jobGroup.DistributionConfig.IsGroupingByWorkGroup)
            {
                // тут группируем по рабочим группам
                logger.LogDebug("Группируем по рабочим группам");

                // Получаем список пар (WorkGroupName, Template)
                var templatesWithWorkGroupNames = new List<(string WorkGroupName, Template Template)>();

                // получаем названия рабочих групп
                foreach (var item in items)
                {
                    var workGroupName = await shortcodesService.ApplyShortcodesAsync(item.WorkGroupMask, item.Template);
                    templatesWithWorkGroupNames.Add((workGroupName, item.Template));
                }

                // Группируем по WorkGroupName
                var grouped = templatesWithWorkGroupNames
                                    .GroupBy(x => x.WorkGroupName)
                                    .ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList());

                foreach (var groupedItem in grouped)
                {
                    var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset);
                    responseItemsList.Add(statResult);
                }
            }
            else if (jobGroup.IsResponseAreaTimezone && !jobGroup.DistributionConfig.IsGroupingByWorkGroup)
            {
                // грппируем только по ЗО
                logger.LogDebug("Группируем по ЗО");

                // Будет группироваться по EsppValue, по MSK, MSK+1...
                // получаем названия ЗО, из ЗО часовой пояс, формируем список шаблонов с часовым поясом ЗО
                var templatesWithTimeZone = new List<(Template Template, string MskTimeZone)>();
                foreach (var item in items)
                {
                    var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template);
                    var mskTimeZone = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea);
                    templatesWithTimeZone.Add((item.Template, mskTimeZone.EsppValue));
                }

                // Группируем по часовому поясу
                var grouped = templatesWithTimeZone
                                 .GroupBy(x => x.MskTimeZone)
                                 .ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList());

                foreach (var groupedItem in grouped)
                {
                    var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset);
                    responseItemsList.Add(statResult);
                }
            }
            else
            {
                // не нужно группировать
                logger.LogDebug("Без группировки");

                // сразу формируем response
                responseItemsList.Add(GetResponseByWorkGroup(null, items.Select(t => t.Template).ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset));
            }

            var response = new StatTemplateDistributorResponse
            {
                JobGroup = mapper.Map<JobGroupWithDistributionConfigResponse>(jobGroup),
                ItemsList = responseItemsList.OrderBy(t => t.GroupFieldName).ToList()
            };

            return Ok(new Response<StatTemplateDistributorResponse>(response, true));
        }

        /// <summary>
        /// Получить дату начала и конца периода
        /// </summary>
        /// <param name="jobGroup"></param>
        /// <param name="minNextRunTemplate"></param>
        /// <param name="maxNextRunTemplate"></param>
        /// <param name="clientOffset"></param>
        /// <returns></returns>
        private (DateOnly dateStart, DateOnly dateEnd) GetStartEndPeriod(JobGroup jobGroup, DateTimeOffset minNextRunTemplate, DateTimeOffset maxNextRunTemplate, TimeSpan clientOffset)
        {
            var durationDays = nextRunService.GetDurationDays(jobGroup.DistributionConfig!);

            var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Add(clientOffset));

            // последний день, не может быть меньше чем durationDays
            var dateEnd = dateStart.AddDays(durationDays);

            // последний день, не может быть меньше чем refDate+durationDays
            var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.Add(clientOffset).AddDays(durationDays).Date);
            if (dateEnd < sumRefDuration)
                dateEnd = sumRefDuration;

            // последний день, не может быть меньше чем дата последнего шаблона maxNextRunTemplate
            var lastTemplateDate = DateOnly.FromDateTime(maxNextRunTemplate.Add(clientOffset).Date);
            if (dateEnd < lastTemplateDate)
                dateEnd = lastTemplateDate;

            // Дата начала, не может быть меньше чем minNextRunTemplate
            var minNextRunDate = DateOnly.FromDateTime(minNextRunTemplate.Add(clientOffset).Date);
            if (dateStart > minNextRunDate)
                dateStart = minNextRunDate;

            return (dateStart, dateEnd);
        }

        /// <summary>
        /// Заполнить респонс
        /// </summary>
        /// <param name="groupFieldName"></param>
        /// <param name="templateList"></param>
        /// <param name="allDays"></param>
        /// <param name="weekends"></param>
        /// <param name="clientOffset"></param>
        /// <returns></returns>
        private StatDistributorItemResponse GetResponseByWorkGroup(string? groupFieldName, List<Template> templateList, List<DateOnly> allDays, HashSet<DateOnly> weekends, TimeSpan clientOffset)
        {
            var result = new StatDistributorItemResponse
            {
                GroupFieldName = groupFieldName,
                Statistics = allDays.OrderBy(t => t).Select(t => new StatTemplateDistributorDateStat
                {
                    AllCount = 0,
                    // вычтем clientOffset, чтоб когда на клиенте он прибавился обратно, дата стала верной для клиента
                    Date = new DateTimeOffset(t.Year, t.Month, t.Day, 0, 0, 0, TimeSpan.Zero).Add(-clientOffset),
                    IsWorkDay = !weekends.Contains(t),
                    IsActivatedCount = 0,
                    IsDeactivatedCount = 0,
                    Templates = new List<StatTemplateDistribItem>()
                }).ToList()
            };

            // смещаем по часовой зоне клиента и группируем по дате
            foreach (var templatesFromDate in templateList.GroupBy(t => DateOnly.FromDateTime(t.NextRun.Add(clientOffset).Date)))
            {
                var statisticsForDate = result.Statistics.FirstOrDefault(t => DateOnly.FromDateTime(t.Date.Add(clientOffset).Date) == templatesFromDate.Key);
                if (statisticsForDate == null)
                {
                    logger.LogWarning("В списке дней, нет дня для шаблонов {count} шт. выполняющихся в {date}", templatesFromDate.Count(), templatesFromDate.Key);
                    continue;
                }

                statisticsForDate.AllCount = templatesFromDate.Count();
                statisticsForDate.IsDeactivatedCount = templatesFromDate.Count(t => !t.IsActiveTemplate || !t.IsActiveSchedule);
                statisticsForDate.IsActivatedCount = templatesFromDate.Count(t => t.IsActiveTemplate && t.IsActiveSchedule);
                statisticsForDate.Templates = templatesFromDate.Select(t => new StatTemplateDistribItem
                {
                    Id = t.Id,
                    Name = t.Name,
                    IsActiveSchedular = t.IsActiveSchedule,
                    IsActiveTemplate = t.IsActiveTemplate,
                    NextRun = t.NextRun
                })
                .OrderBy(t => t.Name)
                .ToList();
            }

            return result;
        }




    }
}
