using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Extensions;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.JobRepositories;
using PARR.Core.Repositories.Interfaces.TemplateRepositories;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Core.Services.NextRunServices;
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
using PARR.Domain.Entities;
using PARR.Domain.Entities.JobEntities;
using PARR.Domain.Entities.TemplateEntities;
using PARR.Domain.Enums;

namespace PARR.TemplateUpdater.Services
{
    internal class TemplateUpdaterService : ITemplateUpdaterService
    {
        private readonly ILogger<TemplateUpdaterService> logger;
        private readonly ITemplateRepository templateService;
        private readonly IJobRepository jobService;
        private readonly IUnitRepository unitService;
        private readonly IRobotConfigurationRepository robotConfigurationService;
        private readonly INextRunService nextRunService;
        private readonly IUnitInValueRepository unitInValueService;
        private readonly ITemplateRenamePendingRepository _templateRenamePendingRepository;

        public TemplateUpdaterService(
                ILogger<TemplateUpdaterService> logger,
                ITemplateRepository templateService,
                IJobRepository jobService,
                IUnitRepository unitService,
                IRobotConfigurationRepository robotConfigurationService,
                INextRunService nextRunService,
                IUnitInValueRepository unitInValueService,
                ITemplateRenamePendingRepository templateRenamePendingRepository
            )
        {
            this.logger = logger;
            this.templateService = templateService;
            this.jobService = jobService;
            this.unitService = unitService;
            this.robotConfigurationService = robotConfigurationService;
            this.nextRunService = nextRunService;
            this.unitInValueService = unitInValueService;
            _templateRenamePendingRepository = templateRenamePendingRepository;
        }


        public async Task UpdateTemplateAsync(TemplateUpdaterMessage query)
        {
            var isValid = await IsValidAsync(query);
            if (!isValid)
            {
                logger.LogError("Не валидны входные данные. Конец обработки сообщения {Query}", query.ToJson());
                return;
            }

            var template = await templateService.Get()
                                    .Include(t => t.RobotConfigurations)
                                    .Include(t => t.UnitsInTemplate)
                                    //.AsSplitQuery()
                                    .AsSingleQuery()
                                    .FirstOrDefaultAsync(t => t.Id == query.TemplateId);
            if (template == null)
            {
                logger.LogError("Не найден шаблон с id {TemplateId}", query.TemplateId);
                return;
            }

            var templateIsChanged = false;
            var scheduleIsChanged = false;

            var trimmedNewName = query.Name.Trim();
            if (template.Name != trimmedNewName)
            {
                var prepareOldNameResult = await PrepareOldTemplateNameAsync(template.Name, trimmedNewName, template);
                if (!prepareOldNameResult)
                    return;

                template.Name = trimmedNewName;
                templateIsChanged = true;
                scheduleIsChanged = true;
            }

            if (template.JobId != query.JobId)
            {
                template.JobId = query.JobId;
                templateIsChanged = true;
                scheduleIsChanged = true;
            }

            if (template.IsActiveTemplate != query.IsActiveTemplate)
            {
                template.IsActiveTemplate = query.IsActiveTemplate;
                templateIsChanged = true;
            }

            if (template.IsActiveSchedule != query.IsActiveSchedule)
            {
                template.IsActiveSchedule = query.IsActiveSchedule;
                scheduleIsChanged = true;
            }

            if (template.UnitId != query.UnitId)
            {
                template.UnitId = query.UnitId;
                templateIsChanged = true;
                scheduleIsChanged = true;
            }

            template.Index = query.Index;
            template.StatusTypeId = query.StatusTypeId;

            // === Обработка изменения состава UnitsInTemplate ===
            // Формируем HashSet пар (UnitId, UnitFieldValueId) для быстрого сравнения
            var currentEntries = template.UnitsInTemplate
                .Select(u => new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId })
                .ToHashSet();

            var newEntries = query.UnitsInTemplate.ToHashSet();

            if (!currentEntries.SetEquals(newEntries))
            {
                // Удаляем связи, которых нет в новом наборе
                var toRemove = template.UnitsInTemplate
                    .Where(u => !newEntries.Contains(new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId }))
                    .ToList();

                foreach (var item in toRemove)
                    template.UnitsInTemplate.Remove(item);

                // Добавляем новые связи
                var toAdd = newEntries.Except(currentEntries);
                foreach (var entry in toAdd)
                {
                    template.UnitsInTemplate.Add(new UnitsInTemplate
                    {
                        TemplateId = template.Id,
                        UnitId = entry.UnitId,
                        UnitFieldValueId = entry.UnitFieldValueId,
                        DateCreated = DateTimeOffset.UtcNow
                    });
                }

                templateIsChanged = true;
            }
            // === Конец обработки UnitsInTemplate ===

            // Для правильного расчета shortcod'ов в БД должны быть сохранены данные UnitsInTemplate, JobId, UnitId и т.д.
            if (templateIsChanged && !await templateService.CommitAsync(query.Initiator))
            {
                logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMessage), query.ToJson());
                return;
            }

            var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, query.IsNew);
            if (!nextRun.HasValue)
            {
                logger.LogError("Ошибка при расчете нового nextRun (вернулся null) для шаблона {TemplateId}, {TemplateName}", template.Id, template.Name);
                return;
            }

            if (template.NextRun != nextRun.Value)
            {
                template.LastRun = template.NextRun;
                template.NextRun = nextRun.Value;
                scheduleIsChanged = true;
            }

            if (templateIsChanged)
            {
                var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template);
                robotConfigurationService.SetUpdateTaskStatusIfAllow(config);
                logger.LogDebug("Для шаблона {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString());
            }

            if (scheduleIsChanged)
            {
                var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, template);
                robotConfigurationService.SetUpdateTaskStatusIfAllow(config);
                logger.LogDebug("Для расписания {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString());
            }

            if (!await templateService.CommitAsync(query.Initiator))
            {
                logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMessage), query.ToJson());
                return;
            }

            logger.LogInformation("Выполнено изменение шаблона в БД. Отправлен запрос на синхронизацию шаблона: {TemplateIsChanged}, расписания: {ScheduleIsChanged}. Query {Query}", templateIsChanged, scheduleIsChanged, query.ToJson());
        }


        /// <summary>
        /// Валидация входящего сообщения
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        private async Task<bool> IsValidAsync(TemplateUpdaterMessage query)
        {
            var template = await templateService.Get().AsNoTracking()
                                    .Include(t => t.RobotConfigurations)
                                    .FirstOrDefaultAsync(t => t.Id == query.TemplateId);

            if (template == null)
            {
                logger.LogError("Сообщение не валидно. Не найден шаблон с id: {TemplateId}", query.TemplateId);
                return false;
            }

            //if (template.ScheduleEsppId == null)
            //{
            //    logger.LogError("Сообщение не валидно. Номер расписания не может быть null, робот не сможет обновить шаблон: {TemplateName}", template.Name);
            //    return false;
            //}

            var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template);
            if (config == null)
            {
                logger.LogError("Сообщение не валидно. Не создана конфигурация роботов: {TemplateName}", template.Name);
                return false;
            }

            //if (config.TaskStatusCode != (int)TaskStatusEnum.Ok)
            //{
            //    logger.LogError("Сообщение не валидно. Не закончено создание или предыдущее обновление - нельзя начинать новое изменение шаблона: {TemplateName}", template.Name);
            //    return false;
            //}

            if (string.IsNullOrEmpty(query.Name?.Trim()))
            {
                logger.LogError("Сообщение не валидно. Имя шаблона не может быть пустым: {TemplateName}", query.Name);
                return false;
            }

            var existTemplateName = await templateService.Get().AsNoTracking().AnyAsync(t =>
                                                t.Id != query.TemplateId
                                                && t.Name!.ToUpper() == query.Name.Trim().ToUpper()
                                                && t.Index == query.Index);
            if (existTemplateName)
            {
                logger.LogError("Сообщение не валидно. Имя шаблона не уникально: {TemplateName}", query.Name);
                return false;
            }

            var job = await jobService.Get().AsNoTracking().AnyAsync(t => t.Id == query.JobId);
            if (!job)
            {
                logger.LogError("Сообщение не валидно. Не найдена работа с JobId: {JobId}", query.JobId);
                return false;
            }

            var unit = await unitService.Get().AsNoTracking().AnyAsync(t => t.Id == query.UnitId);
            if (!unit)
            {
                logger.LogError("Сообщение не валидно. Не найден unit с UnitId: {UnitId}", query.UnitId);
                return false;
            }

            if (query.UnitsInTemplate != null && query.UnitsInTemplate.Any())
            {
                var pairsToCheck = query.UnitsInTemplate.Distinct().ToList();
                var unitIdsToCheck = pairsToCheck.Select(p => p.UnitId).Distinct().ToList();
                var valueIdsToCheck = pairsToCheck.Select(p => p.UnitFieldValueId).Distinct().ToList();

                // Загружаем только те связи, которые относятся к проверяемым ID
                var existingLinks = await unitInValueService.Get()
                    .AsNoTracking()
                    .Where(uiv => unitIdsToCheck.Contains(uiv.UnitId) && valueIdsToCheck.Contains(uiv.ValueId))
                    .Select(uiv => new { uiv.UnitId, uiv.ValueId })
                    .ToListAsync();

                // Формируем HashSet существующих пар для быстрой проверки в памяти
                var existingPairsSet = existingLinks
                    .Select(x => (x.UnitId, UnitFieldValueId: x.ValueId))
                    .ToHashSet();

                // Проверяем, что каждая запрошенная пара реально существует в БД
                var allPairsValid = pairsToCheck.All(p => existingPairsSet.Contains((p.UnitId, p.UnitFieldValueId)));

                if (!allPairsValid)
                {
                    logger.LogError("Сообщение не валидно. Не все пары (UnitId, UnitFieldValueId) из UnitsInTemplate существуют в UnitInValue.");
                    return false;
                }
            }

            return true;
        }


        /// <summary>
        /// Добавление записи в таблицу ожидания переименования
        /// </summary>
        /// <param name="oldName"></param>
        /// <param name="newName"></param>
        /// <param name="template"></param>
        /// <returns></returns>
        private async Task<bool> PrepareOldTemplateNameAsync(string oldName, string newName, Template template)
        {
            // Проверяем, не запущено ли уже переименование для этого шаблона
            var alreadyPending = await _templateRenamePendingRepository.Get()
                                                .AsNoTracking()
                                                .FirstOrDefaultAsync(t => t.TemplateId == template.Id);

            if (alreadyPending != null)
            {
                logger.LogError("При попытке переименования шаблона {TemplateId}, из '{OldName}' в '{NewName}', " +
                    "произошла ошибка, этот шаблон уже находится в процессе переименования (старое имя {PendingName})", template.Id, oldName, newName, alreadyPending.OldName);
                return false;
            }

            // Уникально ли имя в таблице ожидания переименования
            var existPendingOldName = await _templateRenamePendingRepository.Get()
                                                    .AsNoTracking()
                                                    .FirstOrDefaultAsync(t => t.OldName == oldName);

            if (existPendingOldName != null)
            {
                logger.LogError("При добавлении старого имени в таблицу ожидания для шаблона {TemplateId} обнаружен конфликт: " +
                                           "имя '{ExistOldName}' уже зарезервировано другим процессом для шаблона {ExistTemplateId}",
                                           template.Id, existPendingOldName.OldName, existPendingOldName.TemplateId);

                return false;
            }

            // Все нормально, добавляем запись в таблицу
            var pendingRename = new TemplateRenamePending
            {
                TemplateId = template.Id,
                DateCreated = DateTimeOffset.UtcNow,
                OldName = oldName,
                Template = template
            };

            var addResult = await _templateRenamePendingRepository.CreateAsync(pendingRename);
            if (!addResult)
                return false;

            return true;
        }
    }
}