using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using PARR.AIHITMainSyncer.Settings;
using PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces;
using PARR.DAL.Models.Unit;
using PARR.DAL.Services.Interfaces.Unit;

namespace PARR.AIHITMainSyncer.Services
{
    internal class SyncerService : ISyncerService
    {
        private readonly ILogger<SyncerService> logger;
        private readonly ITransformService transformService;
        private readonly IUnitService unitService;
        private readonly IUnitFieldService unitFieldService;
        private readonly IUnitFieldValueService unitFieldValueService;
        private readonly FieldValueReplacementsSettings fieldValueReplacementsSettings;

        public SyncerService(
            ILogger<SyncerService> logger,
            ITransformService transformService,
            IUnitService unitService,
            IUnitFieldService unitFieldService,
            IUnitFieldValueService unitFieldValueService,
            FieldValueReplacementsSettings fieldValueReplacementsSettings
            )
        {
            this.logger = logger;
            this.transformService = transformService;
            this.unitService = unitService;
            this.unitFieldService = unitFieldService;
            this.unitFieldValueService = unitFieldValueService;
            this.fieldValueReplacementsSettings = fieldValueReplacementsSettings;
        }


        public async Task SyncAsync(string msg)
        {
            //logger.LogInformation("Запуск синхронизации данных из очереди сообщений в ПАРР.");
            var objFromQuery = transformService.GetModelFromJson<AihitMainDataMq>(msg);
            if (objFromQuery == null)
                return;

            await SyncUnitAsync(objFromQuery);
        }


        private async Task SyncUnitAsync(AihitMainDataMq objFromQuery)
        {
            //Получаем сразу словарь атрибутов, чтобы не ходить каждый раз в базу
            var fieldsFromDB = await unitFieldService.Get().AsNoTracking()/*.Select(t => new FieldDto { Id = t.Id, Name = t.AihitName, IsMultipleValue = t.IsMultipleValue })*/.ToListAsync();

            if (!objFromQuery.Properties.Any())
                return;

            // 1. Создаем копию и нормализуем свойства (удаляем лишние пробелы)
            var normalizedProperties = new Dictionary<string, string?>();
            foreach (var kvp in objFromQuery.Properties)
                normalizedProperties[kvp.Key.Trim()] = kvp.Value?.Trim();

            // 2. Применяем замены значений к нормализованным свойствам
            ApplyFieldValueReplacements(normalizedProperties);

            var listAihitData = new List<KeyValuePair<string, string?>>();

            // 3. Обрабатываем простые значения из нормализованных свойств
            var propertisWithSimpleValues = normalizedProperties
                .Where(t => fieldsFromDB.Any(a => IsStringEqual(t.Key.Trim(), a.AihitName) && a.IsMultipleValue != true))
                .Select(s => new KeyValuePair<string, string?>(s.Key.Trim(), s.Value?.Trim())).ToList();

            listAihitData.AddRange(propertisWithSimpleValues);

            // 4. Обрабатываем множественные значения из нормализованных свойств
            var propertiesWithMultipleValues = objFromQuery.Properties.Where(t => !propertisWithSimpleValues.Any(a => t.Key.Trim() == a.Key)).ToList();

            propertiesWithMultipleValues.ForEach(p => listAihitData.AddRange(
                SplitValues(p).Distinct().Select(s => new KeyValuePair<string, string?>(s.Key.Trim(), s.Value?.Trim()))
                ));

            var unit = await GetUnit(objFromQuery.Name);

            var incomingPairs = listAihitData
                .Select(kv => new { kv.Key, kv.Value })
                .ToList();

            // Определяем, какие ПОЛЯ обновляются этим сообщением чтобы случайно не удалить не наши
            var fieldsInMessage = listAihitData
                .Select(kv => kv.Key)
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToHashSet();

            var unitValuesToRemove = unit.UnitValues
                .Where(uv =>
                    uv.Field != null &&
                    fieldsInMessage.Contains(uv.Field.AihitName) &&
                    !listAihitData.Any(kv =>
                        IsStringEqual(uv.Field.AihitName, kv.Key) &&
                        IsStringEqual(uv.Value?.Value, kv.Value)
                    )
                )
                .ToList();

            unitValuesToRemove.ForEach(uv => unit.UnitValues.Remove(uv));

            var unitValueMissing = listAihitData.Where(t =>
                                                !unit.UnitValues.Any(a =>
                                                                    IsStringEqual(t.Key, a.Field!.AihitName) &&
                                                                    IsStringEqual(t.Value, a.Value!.Value)
                                                )).ToList();

            if (unitValueMissing.Any())
            {
                var fieldsInChanges = unitValueMissing.Select(s => s.Key).Distinct().ToList();
                var valuesInChanges = unitValueMissing.Select(s => s.Value).Distinct().ToList();

                await SyncFieldsAsync(fieldsInChanges!, fieldsFromDB);

                await SyncValuesAsync(valuesInChanges);

                var normalizedFieldNames = fieldsInChanges
                    .Select(n => n?.Trim())
                    .Where(n => n != null)
                    .ToHashSet(StringComparer.OrdinalIgnoreCase);

                var fieldsNameWithId = await unitFieldService.Get().AsNoTracking()
                    .Where(f => normalizedFieldNames.Contains(f.AihitName))
                    .ToListAsync();

                var normalizedValues = valuesInChanges
                    .Where(v => v != null)
                    .Select(v => v!.Trim())
                    .ToHashSet(StringComparer.OrdinalIgnoreCase);

                var valuesWithId = await unitFieldValueService.Get().AsNoTracking()
                    .Where(v =>
                        (v.Value == null && valuesInChanges.Contains(null)) ||
                        (v.Value != null && normalizedValues.Contains(v.Value)))
                    .ToListAsync();

                foreach (var change in unitValueMissing)
                    ApplyChange(unit, fieldsNameWithId, valuesWithId, change);

                if (!await unitService.CommitAsync())
                    logger.LogError($"Не удалось применить изменения по актуализиции аттрибуто Unit {unit.Name} в базе данных");
            }

            return;
        }


        private static List<KeyValuePair<string, string?>> SplitValues(KeyValuePair<string, string?> multiValueProperty)
        {
            var splitedProperties = new List<KeyValuePair<string, string?>>();

            if (multiValueProperty.Value == null)
                splitedProperties.Add(new KeyValuePair<string, string?>(multiValueProperty.Key, multiValueProperty.Value));
            else
            {
                var values = multiValueProperty.Value.Split(',').Select(s => s.Trim());

                foreach (var value in values)
                    splitedProperties.Add(new KeyValuePair<string, string?>(multiValueProperty.Key, value));
            }

            return splitedProperties;
        }


        private void ApplyChange(Unit unit, List<UnitField> fieldsNameWithId, List<UnitFieldValue> valuesWithId, KeyValuePair<string, string?> change)
        {
            var Field = fieldsNameWithId.FirstOrDefault(t => IsStringEqual(t.AihitName, change.Key));
            var Value = valuesWithId.FirstOrDefault(t => IsStringEqual(t.Value, change.Value));

            if (Field == null || Value == null)
                logger.LogError($"Ошибка присвоения значения({change.Value}) аттрибуту({change.Key}). FieldId - {Field}, ValueId - {Value}");
            else
            {
                var f = new UnitInValue
                {
                    UnitId = unit.Id,
                    FieldId = Field.Id,
                    ValueId = Value.Id,
                    DateCreated = DateTimeOffset.UtcNow
                };

                unit.UnitValues.Add(f);
            }

        }


        private async Task SyncValuesAsync(List<string?> values)
        {
            bool hasNull = values.Any(v => v is null);
            // все поля из АИХИТ
            var normalizedNoneNullValues = values
                                            .Where(v => v != null)
                                            .Select(v => v!.Trim())
                                            .Distinct(StringComparer.OrdinalIgnoreCase)
                                            .ToList();

            // Получаем существующие значения из БД
            var existingValuesInDb = await unitFieldValueService.Get()
                .Where(v => normalizedNoneNullValues.Contains(v.Value!) || v.Value == null)
                .Select(v => v.Value)
                .ToListAsync();

            if (!hasNull)
                existingValuesInDb.RemoveAll(v => v is null);

            var newNonNullValues = normalizedNoneNullValues
                                    .Where(v => !existingValuesInDb.Any(ev =>
                                        string.Equals(ev, v, StringComparison.OrdinalIgnoreCase)))
                                    .ToList();

            var newValuesToInsert = new List<string?>();
            newValuesToInsert.AddRange(newNonNullValues);

            if (hasNull && !existingValuesInDb.Contains(null))
                newValuesToInsert.Add(null);

            if (newValuesToInsert.Count == 0)
                return;

            // Создаём сущности — сохраняем оригинальный регистр из nonNullValues!
            //    (для null — просто null)
            var newFieldValues = newValuesToInsert.Select(v =>
                new UnitFieldValue
                {
                    Id = Guid.NewGuid(),
                    Value = v  // ← v — уже trim()-нутый non-null, или null
                })
                .ToList();

            if (!await unitFieldValueService.AddRangeAsync(newFieldValues) || !await unitFieldValueService.CommitAsync())
            {
                logger.LogError(
                    "Не удалось сохранить {Count} новых значений FieldValues",
                    newValuesToInsert.Count);
            }
            else
            {
                if (newValuesToInsert.Count <= 10)
                {
                    var preview = string.Join(", ", newValuesToInsert.Select(v => v ?? "<null>"));
                    logger.LogInformation("Добавлено {Count} значений FieldValues: [{Values}]", newValuesToInsert.Count, preview);
                }
                else
                {
                    logger.LogInformation("Добавлено {Count} значений FieldValues (первые 5: {Preview})",
                        newValuesToInsert.Count,
                        string.Join(", ", newValuesToInsert.Take(5).Select(v => v ?? "<null>")));
                }
            }
        }


        private async Task SyncFieldsAsync(List<string> fieldsFromAihit, List<UnitField> fieldsFromDB)
        {
            var newFields = fieldsFromAihit.Where(t => !fieldsFromDB.Any(f => f.AihitName == t));

            if (newFields.Any())
            {
                foreach (var item in newFields)
                {
                    var field = new UnitField
                    {
                        Id = Guid.NewGuid(),
                        AihitName = item,
                        EsppName = null,
                    };

                    if (!await unitFieldService.CreateAsync(field))
                        logger.LogError("Не удалось создать запись в таблице Fields:{item},{field}", item, field);
                    else
                        logger.LogInformation("Создана запись а таблице Fields: Fields:{item},{field}", item, field);
                }
                if (!await unitFieldService.CommitAsync())
                    logger.LogError($"Не удалось применить изменения по добавлению новых Fields в базе данных");
            }
        }


        private bool IsStringEqual(string? value1, string? value2)
        {
            return string.Equals(
                    value1?.Trim(),
                    value2?.Trim(),
                    StringComparison.OrdinalIgnoreCase);
        }


        private async Task<Unit> GetUnit(string name)
        {
            var unit = await GetUnitByName(name.Trim());

            if (unit == null)
                unit = await CreateUnitAsync(name);

            return unit;
        }


        private async Task<Unit> CreateUnitAsync(string name)
        {
            var unit = new Unit { Name = name.Trim().ToUpperInvariant() };

            if (!await unitService.CreateAsync(unit) || !await unitService.CommitAsync())
                logger.LogError($"Не удалось создать Unit {unit.Name}");
            else
                logger.LogInformation($"----- Создан Unit: {unit.Name}  -----");

            return unit;
        }


        public async Task<Unit?> GetUnitByName(string name)
        {
            var normalized = name.Trim().ToUpperInvariant();
            return await GetUnitWithFieldsAndValues()
                .FirstOrDefaultAsync(u => u.Name == normalized);
        }


        private IQueryable<Unit> GetUnitWithFieldsAndValues()
        {
            return unitService.Get()
                        .Include(t => t.UnitValues)
                            .ThenInclude(uv => uv.Field)
                        .Include(t => t.UnitValues)
                            .ThenInclude(uf => uf.Value);
        }

        private void ApplyFieldValueReplacements(Dictionary<string, string?> properties)
        {
            foreach (var replacement in fieldValueReplacementsSettings.Replacements)
            {
                if (properties.ContainsKey(replacement.FieldName))
                {
                    var currentValue = properties[replacement.FieldName];

                    // Это костыль, List<string?> не может хранить null, вместо null он хранит ""
                    var stringNull = "null";

                    // Проверяем, содержится ли текущее значение в списке OldValues (с учетом регистра)
                    if (replacement.OldValues.Contains(currentValue ?? stringNull, StringComparer.OrdinalIgnoreCase))
                    {
                        logger.LogDebug("Заменяем значение поля '{FieldName}' с '{OldValue}' на '{NewValue}'",
                                        replacement.FieldName, currentValue, replacement.NewValue);
                        properties[replacement.FieldName] = replacement.NewValue;
                    }

                }
            }
        }
    }
}