using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Services.Shortcodes;
using PARR.Domain.Entities;
using PARR.Domain.Entities.Base.History;
using PARR.Domain.Enums;
using PARR.EsppSync.Attributes;
using PARR.EsppSync.Helpers;
using System.Reflection;

namespace PARR.EsppSync
{
    internal class SyncService<EsppObject> : ISyncService<EsppObject> where EsppObject : class, IEsppObject
    {
        private readonly ILogger<SyncService<EsppObject>> logger;
        private readonly IServiceProvider serviceProvider;

        public SyncService(
            ILogger<SyncService<EsppObject>> logger,
            IServiceProvider serviceProvider
            )
        {
            this.logger = logger;
            this.serviceProvider = serviceProvider;
        }


        public async Task SyncEsppObjectAsync(
            string str,
            ParseStringToEsppObject<EsppObject> parseStringToEsppObject,
            ConvertDbToEsppObject<EsppObject> converterDbToEsppObject,
            CustomComparisionCheckAsync<EsppObject>? customComparisionAsync = null,
            AfterParseStringToEsppObjectAsync<EsppObject>? afterParseStringToEsppObjectAsync = null
            )
        {
            logger.LogDebug("Получил строку. Начинаю работать. Строка: {String}", str);

            if (string.IsNullOrEmpty(str))
            {
                logger.LogWarning("Получил пустую строку, ничего не делаю.");
                return;
            }

            var esppObject = parseStringToEsppObject.Invoke(str);

            if (esppObject == null)
            {
                logger.LogWarning("После парсинга строки, esppObject = null. Дальше ничего не буду делать.");
                return;
            }

            // Вызовем кастомный метод, если он есть
            if (afterParseStringToEsppObjectAsync != null)
                await afterParseStringToEsppObjectAsync.Invoke(esppObject);


            using (var scope = serviceProvider.CreateScope())
            {
                var templateService = GetServiceInScope<ITemplateRepository>(scope);
                var robotConfigurationService = GetServiceInScope<IRobotConfigurationRepository>(scope);
                var shortcodesService = GetServiceInScope<IShortcodesService>(scope);

                try
                {
                    // Загружаем Template и TemplateForShortcodes в одном запросе
                    var query = templateService.Get()
                        //.AsNoTracking()
                        .Include(h => h.Unit)
                            .ThenInclude(t => t!.UnitValues)
                                .ThenInclude(t => t.Value)
                        .Include(h => h.Unit)
                            .ThenInclude(t => t!.UnitValues)
                                .ThenInclude(t => t.Field)
                        .Include(t => t.RobotConfigurations)
                        .Include(t => t.Job)
                            .ThenInclude(j => j.Group)
                                .ThenInclude(g => g.GroupType)
                        .Include(t => t.Job)
                            .ThenInclude(t => t.Group)
                                .ThenInclude(t => t.ScheduleExcludeType)
                        .Include(t => t.Job)
                            .ThenInclude(t => t.Group)
                                .ThenInclude(t => t.ScheduleExcludeTypeCalendar)
                        .Include(t => t.Job)
                            .ThenInclude(j => j.Tnk)
                                .ThenInclude(s => s!.Subprocess)
                                    .ThenInclude(p => p!.Process)
                        .Include(t => t.UnitsInTemplate);

                    var template = await query.FirstOrDefaultAsync(t => t.Name == esppObject.TemplateName);

                    if (template == null)
                    {
                        logger.LogWarning("Найден объект в ЕСПП с именем шаблона '{TemplateName}' незарегистрированный в ПАРР. Строка: {String}", esppObject.TemplateName, str);
                        return;
                    }

                    var dbObjectInEsppObject = converterDbToEsppObject.Invoke(template);

                    await ApplyShortcodesAsync(dbObjectInEsppObject, template, shortcodesService);

                    bool isChanged = false;

                    // если в БД isActive == false, то синхронизировать только по полям из IEsppObject

                    if (dbObjectInEsppObject.IsActive == false)
                    {
                        logger.LogDebug("Объект деактивирован в ПАРР. Сравниваем только обязательные поля. {TemplateName}", esppObject.TemplateName);
                        var lightDbObj = new EsppLightObject(dbObjectInEsppObject);
                        var lightEsppObject = new EsppLightObject(esppObject);

                        isChanged = IsChanged(lightEsppObject, lightDbObj, esppObject.TemplateName);
                    }
                    else
                    {
                        logger.LogDebug("Объект активирован в ПАРР. Сравниваем все поля. {TemplateName}", esppObject.TemplateName);
                        isChanged = IsChanged(esppObject, dbObjectInEsppObject, esppObject.TemplateName);

                        // выполняем кастомную дополнительную проверку (только если isChanged==false, чтоб лишний раз не гонять)
                        if (!isChanged)
                        {
                            if (customComparisionAsync != null)
                            {
                                // добавлена дополнительная проверка
                                var isCustomComparision = await customComparisionAsync.Invoke(esppObject, dbObjectInEsppObject, template.Id);
                                if (isCustomComparision)
                                {
                                    logger.LogDebug("Дополнительная проверка прошла.");
                                }
                                else
                                {
                                    // если дополнительная проверка не прошла, то говорим что есть изменения
                                    isChanged = true;
                                    logger.LogDebug("Дополнительная проверка не прошла, ставим статус isChanged: {isChanged}", isChanged);
                                }
                            }
                            else
                            {
                                logger.LogDebug("Дополнительная проверка отсутствует");
                            }
                        }
                    }

                    if (isChanged)
                    {
                        logger.LogDebug("Есть изменения, требуется обновление. {TemplateName}", esppObject.TemplateName);

                        var config = robotConfigurationService.GetFromTemplateByRobotCode(esppObject.Robot, template);

                        // Если предыдущий статус был Create или Ок, то ставим ему Update
                        // пусть даже Create завершился с ошибками, но раз он уже есть в ЕСПП, то изменим на Update, сбросим все счетчики и пусть попробует обновить и исправить все
                        if (config.TaskStatusCode != (int)TaskStatusEnum.Updating)
                        {
                            //SetUpdateStatus(ref template, robotConfigurationService, esppObject.Robot);

                            var isChangedStatus = robotConfigurationService.SetUpdateTaskStatusIfAllow(config);

                            if (isChangedStatus)
                            {
                                if (!await templateService.CommitAsync(GetInitiator()))
                                    logger.LogError("Не удалось изменить запись Template {TemplateName}, Robot: {Robot}", template.Name, esppObject.Robot);
                                else
                                    logger.LogInformation("Установлен принудительный статус {TaskStatus}, Template {templateName}, Robot: {Robot}", TaskStatusEnum.Updating, template.Name, esppObject.Robot);
                            }
                            else
                            {
                                logger.LogInformation("Нельзя установить статус Updating для шаблона {templateName}, так как текущий статус это запрещает.", template.Name);
                            }
                        }
                        else
                        {
                            // Если пред статус был Update, то ничего не делаем, так его и оставляем, не сбрасывам кол-во попыток и ошибок
                            logger.LogInformation("Есть изменения в Template {TemplateName}, но предыдущий статус TaskStatusCode: {TaskStatusCode}. Не меняем статус, будем разбираться вручную.", template.Name, (TaskStatusEnum)config.TaskStatusCode);
                        }

                    }//надо ли проверять если не изменился, но был статус Updating не понятно. Доверяем роботу пока, что после окончания работ он точно сообщит 
                    else
                    {
                        logger.LogDebug("Нет изменений, обновление не требуется. {TemplateName}", esppObject.TemplateName);

                        //если все поля совпали
                        //проверяем, какой был статус предыдущий статус в БД, если он был не Ок, то ставим ему ОК
                        var robotConfig = robotConfigurationService.GetFromTemplateByRobotCode(esppObject.Robot, template);
                        if (robotConfig.TaskStatusCode != (int)TaskStatusEnum.Ok)
                        {
                            robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Ok, robotConfig);
                            if (!await templateService.CommitAsync(GetInitiator()))
                                logger.LogError("Не удалось изменить запись Template {TemplateName}, Robot: {Robot}", template.Name, esppObject.Robot);
                            else
                                logger.LogInformation("Установлен принудительный статус {TaskStatus}, Template {TemplateName}, Robot: {Robot}", TaskStatusEnum.Ok, template.Name, esppObject.Robot);
                        }
                    }
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "Ошибка синхронизации объекта АСУ ЕСПП {TemplateName}", esppObject.TemplateName);
                }
            }
        }


        /// <summary>
        /// Получить инициатора изменений для сохранения в БД
        /// </summary>
        /// <returns></returns>
        private IHistoryInitiator GetInitiator()
        {
            return new HistoryInitiator
            {
                InitiatorComment = "Обновление статуса синхронизации, SyncService.",
                InitiatorParrComponentId = typeof(EsppObject).Name.Contains("Template") ? ParrComponentsEnum.EsppTemplateSync : ParrComponentsEnum.EsppScheduleSync
            };
        }


        /// <summary>
        /// Применяем шорткоды
        /// </summary>
        /// <param name="dbObjectInEsppObject"></param>
        /// <param name="template"></param>
        /// <param name="shortcodesService"></param>
        /// <returns></returns>
        private async Task ApplyShortcodesAsync(EsppObject dbObjectInEsppObject, Template template, IShortcodesService shortcodesService)
        {
            // Проверяем наличие Shortcode в полях объекта из БД
            var properties = dbObjectInEsppObject.GetType().GetProperties();

            foreach (PropertyInfo property in properties)
            {
                if (property.PropertyType == typeof(string))
                {
                    var value = property.GetValue(dbObjectInEsppObject)?.ToString();
                    if (!string.IsNullOrEmpty(value))
                    {
                        // Передаём исходный template — он уже загружен с Include
                        var processedValue = await shortcodesService.ApplyShortcodesAsync(value, template);
                        property.SetValue(dbObjectInEsppObject, processedValue);
                    }
                }
            }
        }


        ///// <summary>
        ///// Установить статус - Обновить
        ///// </summary>
        ///// <param name="template"></param>
        ///// <param name="robotConfigurationService"></param>
        ///// <param name="robot"></param>
        //private void SetUpdateStatus(ref Template template, IRobotConfigurationService robotConfigurationService, RobotsEnum robot)
        //{
        //    var robotConfig = robotConfigurationService.GetFromTemplateByRobotCode(robot, template);
        //    robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, robotConfig);
        //}


        /// <summary>
        /// Получить сервис из scope
        /// </summary>
        /// <typeparam name="Service"></typeparam>
        /// <param name="scope"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private Service GetServiceInScope<Service>(IServiceScope scope)
        {
            var service = scope.ServiceProvider.GetService<Service>();
            if (service == null)
                throw new Exception($"Не найден сервис: {nameof(Service)}");

            return service;
        }


        /// <summary>
        /// Сравнение объектов
        /// </summary>
        /// <param name="esppObj"></param>
        /// <param name="dbObj"></param>
        /// <param name="templateName"></param>
        /// <returns></returns>
        private bool IsChanged(object esppObj, object dbObj, string templateName)
        {
            foreach (var prop in dbObj.GetType().GetProperties())
            {
                // Пропускаем свойства, помеченные атрибутом SkipComparison
                if (Attribute.IsDefined(prop, typeof(SkipComparisonAttribute)))
                {
                    logger.LogDebug("Пропущено сравнение поля {PropertyName} (помечено [SkipComparison]). Шаблон: {TemplateName}", prop.Name, templateName);
                    continue;
                }

                if (prop == null)
                    continue;

                var dbValue = dbObj.GetType().GetProperty(prop.Name)?.GetValue(dbObj, null);
                var esppValue = esppObj.GetType().GetProperty(prop.Name)?.GetValue(esppObj, null);

                if (dbValue == null || esppValue == null)
                    continue;

                //Replace("\r","").Replace("\n","") - в подробном описании могут быть переносы строк, в Rabbit прилетает без переносов. Убираем переносы для стравнения
                var dbValueStr = EsppSyncHelpers.Normalize(dbValue!.ToString());
                var esppValueStr = EsppSyncHelpers.Normalize(esppValue!.ToString());

                if (dbValueStr != esppValueStr)
                {
                    logger.LogInformation("Не совпадают поля ({PropertyName}). dbValueStr: {DbValueStr}, esppValueStr: {EsppValueStr}. Имя шаблона: {TemplateName}", prop.Name, dbValueStr, esppValueStr, templateName);

                    return true;
                }
            }

            return false;
        }

    }
}