using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Common.Interfaces;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.Job;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Core.Services.Shortcodes.Enums;
using PARR.Core.Services.Shortcodes.Handlers;
using PARR.Core.Services.Shortcodes.Models;
using PARR.Domain.Cache.Models;
using PARR.Domain.DTOs.Shortcode;
using PARR.Domain.Entities;
using PARR.Domain.Entities.Job;
using PARR.Domain.Enums;
using PARR.Domain.Settings;
using System.Runtime.CompilerServices;

namespace PARR.Core.Services.Shortcodes;

internal class ShortcodesService : IShortcodesService
{
    private const string MissingUnitNameWarningMsg =
        "[{Caller}] Шаблон {TemplateId} не содержит Unit.Name в переданных данных. Данные будут догружены из БД. " +
        "Рекомендуется обновить запрос шаблона с Include(t => t.Unit).";

    private const int MaxIterations = 3;

    private readonly ILogger<ShortcodesService> logger;
    private readonly IEnumerable<IShortcodeHandler> handlers;
    private readonly IRedisCacheService cacheService;
    private readonly SettingsFromDb settingsFromDb;
    private readonly IJobRepository jobService;
    private readonly IUnitRepository unitService;
    private readonly IUnitFieldRepository unitFieldRepo;
    private readonly ITemplateRepository templateRepo;

    public ShortcodesService(
        ILogger<ShortcodesService> logger,
        IEnumerable<IShortcodeHandler> handlers,
        IRedisCacheService cacheService,
        SettingsFromDb settingsFromDb,
        IJobRepository jobService,
        IUnitRepository unitService,
        IUnitFieldRepository unitFieldService,
        ITemplateRepository templateRepo
        )
    {
        this.logger = logger;
        this.handlers = handlers.OrderBy(h => h.Order).ToList();
        this.cacheService = cacheService;
        this.settingsFromDb = settingsFromDb;
        this.jobService = jobService;
        this.unitService = unitService;
        this.unitFieldRepo = unitFieldService;
        this.templateRepo = templateRepo;
    }

    public async Task<string> ApplyShortcodesAsync(string str, Template template, [CallerMemberName] string? caller = null)
    {
        if (string.IsNullOrEmpty(str)) return str;
        var callerName = caller ?? "Unknown";

        // Кэш результата (проверяем один раз на входе)
        var cacheKey = cacheService.GetKey(new[] { "shortcodes", "result", template.Id.ToString("N") }, new[] { str });
        var cachedResult = await cacheService.GetCachedDataAsync<ShortcodesResult>(cacheKey).ConfigureAwait(false);
        if (cachedResult != null) return cachedResult.Data.Result;

        // Начальный контекст
        var initialUnitName = template.Unit?.Name;

        var initialJob = template.Job != null ? MapJobForShortcodes(template.Job) : null;

        var initialUnitsInTemplate = template.UnitsInTemplate?.Select(uit =>
            new UnitInTemplateForShortcode(uit.UnitId, uit.UnitFieldValueId)).ToList()
            ?? new List<UnitInTemplateForShortcode>();

        var data = new TemplateForShortcode(
            Id: template.Id,
            Index: template.Index,
            JobId: template.JobId,
            UnitId: template.UnitId,
            UnitName: initialUnitName ?? string.Empty, 
            Job: initialJob,                     
            UnitsInTemplate: initialUnitsInTemplate,
            UnitTags: new List<string>(),
            RelatedUnitTags: new Dictionary<Guid, List<string>>()
        );

        var result = str;
        var iteration = 0;

        while (iteration < MaxIterations)
        {
            // 1. Находим хендлеры, которые сработают для ТЕКУЩЕЙ версии строки
            var matchingHandlers = handlers
                .Where(h => h.Pattern.IsMatch(result))
                .ToList();

            if (matchingHandlers.Count == 0) break; // Нечего заменять

            // 2. Агрегируем требования только активных хендлеров
            var currentRequirements = matchingHandlers
                .Aggregate(ShortcodeDataRequirementsEnum.None, (acc, h) => acc | h.Requirements);

            // 3. ОБНОВЛЯЕМ ДАННЫЕ (Ленивая загрузка)
            // Передаем текущий 'data', чтобы метод знал, что уже загружено, и не бил в БД повторно
            data = await PrepareTemplateDataAsync(template, currentRequirements, data, callerName).ConfigureAwait(false);

            // 4. Выполняем замену
            var previousResult = result;
            foreach (var handler in matchingHandlers)
            {
                result = await handler.ResolveAsync(result, data, callerName, CancellationToken.None).ConfigureAwait(false);
            }

            // 5. Если строка не изменилась — стабилизация достигнута
            if (result == previousResult) break;

            iteration++;
        }

        if (iteration == MaxIterations && result != str)
        {
            logger.LogWarning("[{Caller}] Достигнут лимит итераций для шаблона {TemplateId}. Возможна циклическая зависимость.", callerName, template.Id);
        }

        // Сохранение в кэш
        if (!string.IsNullOrEmpty(result) && result != str && template.Id != Guid.Empty)
        {
            await cacheService.SetCachedDataAsync(cacheKey, new ShortcodesResult
            {
                Data = new ShortcodesResultDto { Result = result },
                Timestamp = DateTimeOffset.UtcNow,
                Source = nameof(ShortcodesService)
            }, TimeSpan.FromMinutes(15)).ConfigureAwait(false);
        }

        return result;
    }

    private async Task<TemplateForShortcode> PrepareTemplateDataAsync(
        Template template,
        ShortcodeDataRequirementsEnum requirements,
        TemplateForShortcode currentData,
        string caller)
    {
        // 1. Проверка UnitName
        var unitName = currentData.UnitName;
        if (string.IsNullOrEmpty(unitName) && requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitName))
        {
            // Логирование и загрузка только если нужно и еще не загружено
            logger.LogWarning(MissingUnitNameWarningMsg, caller, template.Id);
            var unit = await unitService.Get().AsNoTracking().FirstOrDefaultAsync(u => u.Id == template.UnitId).ConfigureAwait(false);
            unitName = unit?.Name ?? throw new InvalidOperationException($"Unit {template.UnitId} не найден.");
        }

        // 2. Проверка Job и навигационных свойств
        JobForShortcode? jobData = currentData.Job;
        bool needLoadJob = false;

        if (requirements.HasFlag(ShortcodeDataRequirementsEnum.Job))
        {
            if (jobData == null)
            {
                needLoadJob = true;
            }
            else
            {
                // Job загружен, но хватает ли вложенных свойств?
                // Так как LoadFullJobAsync грузит всё сразу, если jobData != null, значит там есть всё.
                // Но на всякий случай проверим флаги, если в будущем загрузка станет частичной.
                if (requirements.HasFlag(ShortcodeDataRequirementsEnum.JobGroup) && jobData.Group == null) needLoadJob = true;
                if (requirements.HasFlag(ShortcodeDataRequirementsEnum.JobTnk) && jobData.Tnk == null) needLoadJob = true;
            }
        }

        if (needLoadJob)
        {
            var missing = new List<string>();
            if (requirements.HasFlag(ShortcodeDataRequirementsEnum.JobGroup)) missing.Add("Group");
            if (requirements.HasFlag(ShortcodeDataRequirementsEnum.JobTnk)) missing.Add("Tnk");

            // Логируем только если реально пошли в БД
            logger.LogWarning(
                "[{Caller}] Шаблон {TemplateId} требует догрузки данных ({Missing}). Данные догружены.",
                caller, template.Id, string.Join(", ", missing));

            jobData = await LoadFullJobAsync(template.JobId, caller).ConfigureAwait(false);
        }

        // 3. Проверка UnitsInTemplate
        var unitsInTemplate = currentData.UnitsInTemplate;
        if ((unitsInTemplate == null || unitsInTemplate.Count == 0) &&
            requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitsInTemplate))
        {
            logger.LogWarning(
                "[{Caller}] Шаблон {TemplateId} передан без UnitsInTemplate. Данные догружены автоматически.",
                caller, template.Id);

            var fullTemplate = await templateRepo.Get()
                .AsNoTracking()
                .Include(t => t.UnitsInTemplate)
                .FirstOrDefaultAsync(t => t.Id == template.Id)
                .ConfigureAwait(false);

            unitsInTemplate = fullTemplate?.UnitsInTemplate
                ?.Select(uit => new UnitInTemplateForShortcode(uit.UnitId, uit.UnitFieldValueId))
                .ToList() ?? new List<UnitInTemplateForShortcode>();
        }


        // 5. Проверка UnitTags
        var unitTags = currentData.UnitTags;
        if ((unitTags == null || unitTags.Count == 0) &&
            requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitTags))
        {
            logger.LogWarning(
                "[{Caller}] Шаблон {TemplateId} требует теги (UnitTags). Данные догружены автоматически.",
                caller, template.Id);

            unitTags = await unitService.Get()
                .AsNoTracking()
                .Where(u => u.Id == template.UnitId)
                .SelectMany(u => u.UnitValues
                    .Where(uv => uv.Field != null &&
                                 uv.Field.AihitName == "ПАРР тег" &&
                                 uv.Value != null &&
                                 uv.Value.Value != null)
                    .Select(uv => uv.Value!.Value!))
                .ToListAsync()
                .ConfigureAwait(false);
        }

        // 6. Проверка RelatedUnitTags
        var relatedUnitTags = currentData.RelatedUnitTags;
        var relatedUnitIds = template.UnitsInTemplate?.Select(uit => uit.UnitId).Distinct().ToList() ?? new List<Guid>();

        if ((relatedUnitTags == null || relatedUnitTags.Count == 0) &&
            requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitsInTemplateTags) &&
            relatedUnitIds.Count > 0)
        {
            logger.LogWarning(
                "[{Caller}] Шаблон {TemplateId} требует теги связанных ЭК (UnitsInTemplateTags). Данные догружены автоматически.",
                caller, template.Id);

            var rawTags = await unitService.Get()
                .AsNoTracking()
                .Where(u => relatedUnitIds.Contains(u.Id))
                .SelectMany(u => u.UnitValues
                    .Where(uv => uv.Field != null &&
                                 uv.Field.AihitName == "ПАРР тег" &&
                                 uv.Value != null &&
                                 uv.Value.Value != null)
                    .Select(uv => new { UnitId = u.Id, Tag = uv.Value!.Value! }))
                .ToListAsync()
                .ConfigureAwait(false);

            relatedUnitTags = rawTags
                .GroupBy(x => x.UnitId)
                .ToDictionary(g => g.Key, g => g.Select(x => x.Tag).ToList());
        }

        // 7. Возвращаем обновленный рекорд
        return currentData with
        {
            UnitName = unitName ?? string.Empty,
            Job = jobData,
            UnitsInTemplate = unitsInTemplate ?? currentData.UnitsInTemplate,
            UnitTags = unitTags ?? currentData.UnitTags,
            RelatedUnitTags = relatedUnitTags ?? currentData.RelatedUnitTags
        };
    }


    private async Task<JobForShortcode> LoadFullJobAsync(Guid jobId, string caller)
    {
        var job = await jobService.Get().AsNoTracking().AsSingleQuery()
            .Include(j => j.Group).ThenInclude(g => g!.GroupType)
            .Include(j => j.Tnk)
            .FirstOrDefaultAsync(j => j.Id == jobId)
            .ConfigureAwait(false);

        if (job == null)
            throw new InvalidOperationException($"Job {jobId} не найден.");

        return MapJobForShortcodes(job);
    }


    private JobForShortcode MapJobForShortcodes(Job job)
    {
        return new JobForShortcode(
            Group: job.Group == null ? null : new JobGroupForShortcode(
                Id: job.Group.Id,
                GroupingUnitFieldId: job.Group.GroupingUnitFieldId,
                GroupName: job.Group.GroupName ?? string.Empty,
                GroupType: job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes(job.Group.GroupType.Code)
            ),
            Tnk: job.Tnk == null ? null : new TnkForShortcode(
                Name: job.Tnk.Name ?? string.Empty,
                ShortName: job.Tnk.ShortName ?? string.Empty
            ),
            WorkName: job.WorkName ?? string.Empty,
            Name: job.Name ?? string.Empty
        );
    }


    public async Task<List<ShortcodeInfoDto>> GetAvailableShortcodesAsync()
    {
        var result = new List<ShortcodeInfoDto>();

        foreach (var constant in settingsFromDb.TemplateNameConstantPartsList)
        {
            result.Add(new ShortcodeInfoDto
            {
                Shortcode = $"%{constant.Name}%",
                Description = $"Константа: {constant.Value ?? "(пусто)"}",
                Type = ShortcodeTypeEnum.Static
            });
        }

        result.AddRange(new[]
        {
            new ShortcodeInfoDto { Shortcode = "%ЭК%", Description = "Наименование ЭК(Код поиска)", Type = ShortcodeTypeEnum.Standard },
            new ShortcodeInfoDto { Shortcode = "%ГРУППА_РАБОТ%", Description = "Наименование группы работ", Type = ShortcodeTypeEnum.Standard },
            new ShortcodeInfoDto { Shortcode = "%РАБОТА%", Description = "Наименование работы в АСУ ЕСПП", Type = ShortcodeTypeEnum.Standard },
            new ShortcodeInfoDto { Shortcode = "%ТНК%", Description = "Полное наименование ТНК", Type = ShortcodeTypeEnum.Standard },
            new ShortcodeInfoDto { Shortcode = "%ТНК-КРАТКО%", Description = "Краткое наименование ТНК", Type = ShortcodeTypeEnum.Standard },
            new ShortcodeInfoDto { Shortcode = "%ТИКТАК%", Description = "Текущее время в формате Unix timestamp", Type = ShortcodeTypeEnum.Standard },
            new ShortcodeInfoDto { Shortcode = "%ИД%", Description = "Уникальный идентификатор шаблона (GUID без дефисов)", Type = ShortcodeTypeEnum.Standard },
            new ShortcodeInfoDto { Shortcode = "%ТЕГ:ИМЯ_ТЕГА%", Description = "Значение тега ЭК после двоеточия (например, для 'БД_в_ЗО_РГ:ЕРП' вернет 'ЕРП')", Type = ShortcodeTypeEnum.Standard }
        });

        result.AddRange(new[]
        {
            new ShortcodeInfoDto { Shortcode = "%ИНДЕКС%", Description = "Порядковый индекс шаблона для групповых работ", Type = ShortcodeTypeEnum.GroupValue },
            new ShortcodeInfoDto { Shortcode = "%МАКС:ИМЯ АТРИБУТА%", Description = "Наиболее частое значение поля в группе", Type = ShortcodeTypeEnum.GroupValue },
            new ShortcodeInfoDto { Shortcode = "%ГР_ПОЛЕ-ПН%", Description = "Нумерованный список связанных ЭК со значениями", Type = ShortcodeTypeEnum.GroupValue },
            new ShortcodeInfoDto { Shortcode = "%СВЯЗЬ:ИМЯ АТРИБУТА%", Description = "Первое значение поля из связанных ЭК", Type = ShortcodeTypeEnum.GroupValue },
            new ShortcodeInfoDto { Shortcode = "%ТЕГ_СВЯЗИ:ИМЯ_ТЕГА%", Description = "Самое популярное значение тега среди связанных ЭК (по алфавиту при равенстве частот)", Type = ShortcodeTypeEnum.GroupValue}
        });

        result.Add(new ShortcodeInfoDto
        {
            Shortcode = "%БУКВЫ:ИМЯ АТРИБУТА%",
            Description = "Извлекает только буквы из значения поля",
            Type = ShortcodeTypeEnum.Transform
        });

        result.AddRange(new[]
        {
            new ShortcodeInfoDto { Shortcode = "%СВЯЗИ%", Description = "Связанные ЭК (по одному на строку)", Type = ShortcodeTypeEnum.Relationship },
            new ShortcodeInfoDto { Shortcode = "%СВЯЗИ-ПН%", Description = "Связанные ЭК с нумерацией", Type = ShortcodeTypeEnum.Relationship }
        });

        var fieldNames = await unitFieldRepo.Get().AsNoTracking().Select(t => new { t.AihitName, t.DisplayName }).ToListAsync().ConfigureAwait(false);
        foreach (var fieldName in fieldNames.OrderBy(n => n.AihitName))
        {
            result.Add(new ShortcodeInfoDto
            {
                Shortcode = $"%{fieldName.AihitName}%",
                Description = $"Атрибут: {fieldName.DisplayName ?? fieldName.AihitName}",
                Type = ShortcodeTypeEnum.FieldValue
            });
        }

        return result;
    }
}