using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Common.Interfaces;
using PARR.Core.Repositories.Interfaces.JobRepositories;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
using PARR.Core.Services.UnitFilterService.Models;
using PARR.Core.Services.UnitService.Interfaces;
using PARR.Domain.Entities.JobEntities;
using PARR.Domain.Entities.Unit;
using System.Diagnostics;

namespace PARR.Core.Services.UnitFilterService;

internal class UnitFilterService : IUnitFilterService
{
#if DEBUG
    private readonly Guid debugTargetUnitId = Guid.Parse("a3b5f3e2-928e-48b7-b481-df4556e1ed32");
#endif


    private readonly ILogger<UnitFilterService> logger;
    private readonly IJobRepository jobRepository;
    private readonly IUnitInUnitRepository unitInUnitRepository;
    private readonly IUnitInValueRepository unitInValueRepository;
    private readonly IUnitService unitService;
    private readonly IUnitFieldMatcher unitFieldMatcher;
    private readonly IUnitRelationshipMatcher unitRelationshipMatcher;
    private readonly IUmbrellaFilter umbrellaFilter;
    private readonly IUnitFilterResultLoader resultLoader;
    private readonly IUnitNameResolver nameResolver;

    public UnitFilterService(
        ILogger<UnitFilterService> logger,
        IJobRepository jobRepository,
        IUnitRepository unitRepository,
        IUnitInUnitRepository unitInUnitRepository,
        IUnitInValueRepository unitInValueRepository,
        IRedisCacheService cacheService,
        IUnitFieldRepository unitFieldRepository,
        IUnitService unitService,
        IUnitFieldMatcher unitFieldMatcher,
        IUnitRelationshipMatcher unitRelationshipMatcher,
        IUmbrellaFilter umbrellaFilter,
        IUnitFilterResultLoader resultLoader,
        IUnitNameResolver nameResolver
        )
    {
        this.logger = logger;
        this.jobRepository = jobRepository;
        this.unitInUnitRepository = unitInUnitRepository;
        this.unitInValueRepository = unitInValueRepository;
        this.unitService = unitService;
        this.unitFieldMatcher = unitFieldMatcher;
        this.unitRelationshipMatcher = unitRelationshipMatcher;
        this.umbrellaFilter = umbrellaFilter;
        this.resultLoader = resultLoader;
        this.nameResolver = nameResolver;
    }


    public async Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
        Guid jobId,
        int? takeCount = null,
        CancellationToken cancellationToken = default
        )
    {
        var job = await LoadJobWithFiltersAsync(jobId, cancellationToken);
        return job == null ? null : await GetUnitsByJobFilterAsync(job, takeCount, cancellationToken);
    }


    public async Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(
        Job job,
        int? takeCount = null,
        CancellationToken cancellationToken = default
        )
    {
        var filteredUnits = await GetUnitsByJobFilterAsync(job, takeCount, cancellationToken);
        return filteredUnits?.Select(u => u.Id).ToList();
    }


    public async Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
        Job job,
        int? takeCount = null,
        CancellationToken cancellationToken = default
        )
    {
        if (job.Group == null)
            throw new ArgumentNullException(nameof(job.Group), $"Job {job.Id} не содержит Group");
        if (job.Group.GroupType == null)
            throw new ArgumentNullException(nameof(job.Group.GroupType), $"Job {job.Id} не содержит GroupType");
        if (job.UnitFilters == null || !job.UnitFilters.Any())
            throw new ArgumentNullException(nameof(job.UnitFilters), $"Job {job.Id} не содержит UnitFilters");

        var totalStopwatch = Stopwatch.StartNew();
        logger.LogInformation("Начало фильтрации юнитов для Job {JobId} с {FilterCount} фильтрами", job.Id, job.UnitFilters.Count);

        // Собираем все контексты юнитов, прошедших фильтрацию
        var allFilteredContexts = new List<UnitFilterMatchResult>();

        // Преобразуем в список для индексации
        var unitFiltersList = job.UnitFilters.ToList();

        // Этап 1: Применение основных фильтров (Field, Relationship) на уровне SQL
        for (int i = 0; i < unitFiltersList.Count; i++)
        {
            var filter = unitFiltersList[i];
            var filterStopwatch = Stopwatch.StartNew();

            try
            {
                logger.LogDebug("Применение фильтра #{Index} (Id={FilterId})", i + 1, filter.Id);

                // 1. Найти ID юнитов по UnitFilter
                var initialUnitIds = await nameResolver.ResolveAsync(filter.UnitFilter, cancellationToken);
                if (!initialUnitIds.Any())
                {
                    logger.LogDebug("Фильтр #{Index}: пропущен (0 юнитов)", i + 1);
                    continue;
                }

#if DEBUG
                if (initialUnitIds.Contains(debugTargetUnitId))
                {
                    logger.LogDebug("DEBUG: Юнит {TargetUnitId} найден в initialUnitIds для фильтра #{Index}", debugTargetUnitId, i + 1);
                }
#endif

                // 2. Применить FieldFilters 
                var fieldStopwatch = Stopwatch.StartNew();
                //var fieldFilteredIds = await ApplyFieldFiltersOnDbAsync(initialUnitIds, filter.FieldFilters, cancellationToken);
                var fieldFilteredIds = await unitFieldMatcher.MatchAsync(initialUnitIds, filter.FieldFilters, cancellationToken);

                if (!fieldFilteredIds.Any())
                {
                    logger.LogDebug("Фильтр #{Index}: 0 юнитов после FieldFilters", i + 1);
                    continue;
                }

#if DEBUG
                if (initialUnitIds.Contains(debugTargetUnitId) && !fieldFilteredIds.Contains(debugTargetUnitId))
                {
                    logger.LogDebug("DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе FieldFilters (Фильтр #{Index})", debugTargetUnitId, i + 1);
                }
#endif

                // 3. Применить RelationshipFilters
                var relStopwatch = Stopwatch.StartNew();
                var relationshipFilteredContexts = await unitRelationshipMatcher.MatchAsync(
                    fieldFilteredIds, filter.RelationshipFilters, cancellationToken);

                if (!relationshipFilteredContexts.Any())
                {
                    logger.LogDebug("Фильтр #{Index}: 0 юнитов после RelationshipFilters", i + 1);
                    continue;
                }

#if DEBUG
                var targetContext = relationshipFilteredContexts.FirstOrDefault(c => c.UnitId == debugTargetUnitId);
                if (targetContext != null)
                {
                    logger.LogDebug("DEBUG: Юнит {TargetUnitId} прошёл фильтр #{Index}. Родителей: {ParentCount}, Детей: {ChildCount}",
                        debugTargetUnitId, i + 1, targetContext.ValidParentIds.Count, targetContext.ValidChildIds.Count);
                }
#endif

                // Добавляем отфильтрованные контексты в общий набор
                allFilteredContexts.AddRange(relationshipFilteredContexts);

                filterStopwatch.Stop();
                logger.LogDebug(
                    "Фильтр #{Index}: добавлено {Count} юнитов. Всего: {Total}. [Field: {F}ms, Rel: {R}ms, Total: {T}ms]",
                    i + 1,
                    relationshipFilteredContexts.Count,
                    allFilteredContexts.Count,
                    fieldStopwatch.ElapsedMilliseconds,
                    relStopwatch.ElapsedMilliseconds,
                    filterStopwatch.ElapsedMilliseconds
                );
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Ошибка при обработке фильтра {FilterId} для Job {JobId}",
                    filter.Id, job.Id);
            }
        }

        // Убираем дубликаты по UnitId, объединяя связи
        var mergedContexts = allFilteredContexts
            .GroupBy(c => c.UnitId)
            .Select(g => new UnitFilterMatchResult
            {
                UnitId = g.Key,
                ValidParentIds = new HashSet<Guid>(g.SelectMany(c => c.ValidParentIds)),
                ValidChildIds = new HashSet<Guid>(g.SelectMany(c => c.ValidChildIds))
            })
            .ToList();
        logger.LogInformation("Этап базовой фильтрации завершён: собрано {UnitCount} уникальных юнитов", mergedContexts.Count);

        // Этап 2: Применение Umbrella-фильтра
        var umbrellaFilteredContexts = umbrellaFilter.Apply(mergedContexts, job);

        // Этап 3: Применение takeCount и загрузка полных данных для результата
        var finalContexts = umbrellaFilteredContexts.Take(takeCount ?? int.MaxValue).ToList();
        var loadStopwatch = Stopwatch.StartNew();
        var result = await resultLoader.LoadAsync(finalContexts, cancellationToken);
        loadStopwatch.Stop();

        totalStopwatch.Stop();
        logger.LogInformation(
            "Job {JobId} завершён за {TotalMs} мс. Найдено {UnitCount} юнитов. [Load: {LoadMs} мс]",
            job.Id,
            totalStopwatch.ElapsedMilliseconds,
            result.Count,
            loadStopwatch.ElapsedMilliseconds);

        return result;
    }

    private async Task<Job?> LoadJobWithFiltersAsync(Guid jobId, CancellationToken cancellationToken = default)
    {
        return await jobRepository
            .Get().AsNoTracking()
            .AsSplitQuery()
            .Include(j => j.UnitFilters).ThenInclude(uf => uf.FieldFilters).ThenInclude(ff => ff.UnitField)
            .Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters).ThenInclude(rf => rf.UnitField)
            .Include(j => j.Group).ThenInclude(g => g!.GroupType)
            .FirstOrDefaultAsync(j => j.Id == jobId, cancellationToken);
    }


    public async Task<List<string>> GetRelatedUnitNamesAsync(Guid jobId, Guid unitId, CancellationToken cancellationToken = default)
    {
        logger.LogDebug("Начало GetRelatedUnitNamesAsync. JobId: {JobId}, UnitId: {UnitId}", jobId, unitId);

        var job = await jobRepository
            .Get().AsNoTracking()
            .Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters)
            .FirstOrDefaultAsync(j => j.Id == jobId, cancellationToken);

        if (job == null)
        {
            logger.LogWarning("Job с Id {JobId} не найден.", jobId);
            throw new ArgumentException($"Job {jobId} не найден.", nameof(jobId));
        }

        logger.LogDebug("Найден Job: {JobName}. Количество UnitFilters: {FilterCount}", job.Name, job.UnitFilters.Count());

        var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        foreach (var filter in job.UnitFilters)
        {
            if (!filter.RelationshipFilters.Any()) continue;

            logger.LogDebug("Обработка UnitFilter.Id {FilterId}. Количество RelationshipFilters: {RelFilterCount}", filter.Id, filter.RelationshipFilters.Count());

            // Получить все связи для юнита
            var parentLinks = await unitInUnitRepository.GetByChildIdAsync(unitId);
            var childLinks = await unitInUnitRepository.GetByParentIdAsync(unitId);

            // Собрать все UnitId, участвующие в связях
            var allRelatedUnitIds = parentLinks
                .Select(l => l.ParentUnitId)
                .Concat(childLinks.Select(l => l.ChildUnitId))
                .Distinct()
                .ToList();

            if (!allRelatedUnitIds.Any()) continue;

            // Получить значения для всех связанных юнитов
            var allUnitValues = await unitInValueRepository.GetByUnitIdsAsync(allRelatedUnitIds);

            // Сгруппировать значения по UnitId
            var valuesByUnit = allUnitValues
                .GroupBy(uv => uv.UnitId)
                .ToDictionary(g => g.Key, g => g.ToList());

            // Найти UnitId, которые проходят все RelationshipFilters
            var matchingUnitIds = new HashSet<Guid>();

            foreach (var relatedUnitId in allRelatedUnitIds)
            {
                bool passesAllFilters = filter.RelationshipFilters.All(rf =>
                {
                    var values = valuesByUnit.GetValueOrDefault(relatedUnitId, new List<UnitInValue>());

                    var matchingValues = values
                        .Where(uv => uv.FieldId == rf.FieldId && uv.Value?.Value != null)
                        .ToList();

                    if (!matchingValues.Any())
                    {
                        return rf.IsInverse;
                    }

                    var hasMatch = matchingValues.Any(uv => uv.Value!.Value!.Contains(rf.ValueMask.Trim('%'), StringComparison.OrdinalIgnoreCase));

                    if (rf.IsInverse)
                        hasMatch = !hasMatch;

                    return hasMatch;
                });

                if (passesAllFilters)
                    matchingUnitIds.Add(relatedUnitId);
            }

            if (matchingUnitIds.Any())
            {
                // Используем кэширующий сервис вместо прямого запроса к БД
                var cachedUnits = await unitService.GetWithCachingAsync(matchingUnitIds);
                var names = cachedUnits.Values
                    .Select(u => u.Name)
                    .Where(n => !string.IsNullOrEmpty(n));
                result.UnionWith(names!);
            }
        }

        return result.ToList();
    }
}