using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
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,
            ParserHandlerDelegate<EsppObject> parser,
            ConvertDbObjToComparisonObjHandlerDelegate<EsppObject> converterToEsppObject
            )
        {
            logger.LogDebug($"Получил строку. Начинаю работать. Строка: {str}");

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

            var esppObject = parser.Invoke(str);

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

            using (var scope = serviceProvider.CreateScope())
            {
                var templateService = GetServiceInScope<ITemplateService>(scope);
                var robotConfigurationService = GetServiceInScope<IRobotConfigurationService>(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($"Найден объект в ЕСПП с именем шаблона {esppObject.TemplateName} незарегистрированный в ПАРР.");
                        return;
                    }

                    var dbObjectInEsppObject = converterToEsppObject.Invoke(template);

                    // Проверяем наличие 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);
                            }
                        }
                    }

                    bool isChanged = false;
                    //TODO: FIX ME Please, BRO
                    //bool isChanged;

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

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

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

                    if (isChanged)
                    {
                        logger.LogDebug($"Есть изменения, требуется обновление. {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);

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

                        #region Old logic

                        //SetUpdateStatus(ref template, robotConfigurationService, esppObject.Robot);

                        //if (!await templateService.CommitAsync())
                        //    logger.LogError($"Не удалось изменить запись Template {template.Name}, Robot: {esppObject.Robot}");
                        //else
                        //    logger.LogInformation($"Установлен принудительный статус {TaskStatusEnum.Updating}, Template {template.Name}, Robot: {esppObject.Robot}");
                        #endregion
                    }//надо ли проверять если не изменился, но был статус Updating не понятно. Доверяем роботу пока, что после окончания работ он точно сообщит 
                    else
                    {
                        logger.LogDebug($"Нет изменений, обновление не требуется. {esppObject.TemplateName}");

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


        private void SetUpdateStatus(ref Template template, IRobotConfigurationService robotConfigurationService, RobotsEnum robot)
        {
            var robotConfig = robotConfigurationService.GetFromTemplateByRobotCode(robot, template);
            robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, robotConfig);
        }


        private Service GetServiceInScope<Service>(IServiceScope scope)
        {
            var service = scope.ServiceProvider.GetService<Service>();
            if (service == null)
                throw new Exception($"Не найден сервис: {nameof(Service)}");

            return service;
        }


        //private bool IsChanged(EsppObject esppObj, EsppObject dbObj)
        private bool IsChanged(object esppObj, object dbObj, string templateName)
        {
            foreach (var prop in dbObj.GetType().GetProperties())
            {
                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 = Normalize(dbValue!.ToString()!);
                var esppValueStr = Normalize(esppValue!.ToString()!);

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

                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// Удаляет ненужные символы из строки
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private string Normalize(string str)
        {
            str = str.Replace("\r", string.Empty);
            str = str.Replace("\n", string.Empty);
            str = str.Replace(" ", string.Empty);

            return str.ToLower();
        }
    }
}
