using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
using PARR.Core.Services.UnitFilterService.Models;
using PARR.Domain.Entities.JobEntities;

namespace PARR.Core.Services.UnitFilterService.Matchers;

/// <summary>
/// Сопоставляет юниты с фильтрами по связям.
/// </summary>
internal class UnitRelationshipMatcher : IUnitRelationshipMatcher
{
    private readonly IUnitInUnitRepository unitInUnitRepository;
    private readonly IUnitInValueRepository unitInValueRepository;
    private readonly ILogger<UnitRelationshipMatcher> logger;

    public UnitRelationshipMatcher(
        IUnitInUnitRepository unitInUnitRepository,
        IUnitInValueRepository unitInValueRepository,
        ILogger<UnitRelationshipMatcher> logger)
    {
        this.unitInUnitRepository = unitInUnitRepository;
        this.unitInValueRepository = unitInValueRepository;
        this.logger = logger;
    }

    public async Task<List<UnitFilterMatchResult>> MatchAsync(
        IReadOnlyList<Guid> unitIds,
        IEnumerable<JobRelationshipFilter> relationshipFilters,
        CancellationToken ct = default)
    {
        var filters = relationshipFilters?.ToList();
        if (filters == null || filters.Count == 0 || unitIds.Count == 0)
        {
            logger.LogDebug("UnitRelationshipMatcher: вход {UnitCount} юнитов, фильтров: 0 → возврат без изменений",
                unitIds.Count);
            return unitIds.Select(id => new UnitFilterMatchResult { UnitId = id }).ToList();
        }

        logger.LogDebug("UnitRelationshipMatcher: вход {UnitCount} юнитов, фильтров: {FilterCount}",
            unitIds.Count, filters.Count);

        var preFilteredUnitsById = unitIds.ToDictionary(id => id, id => new UnitFilterMatchResult { UnitId = id });

        var parentRelFilters = filters.Where(rf => rf.IsParent).ToList();
        var childRelFilters = filters.Where(rf => !rf.IsParent).ToList();

        if (parentRelFilters.Any())
        {
            await ApplyFiltersToUnitsAsync(
                unitIds, preFilteredUnitsById, parentRelFilters,
                isParentDirection: true, ct);
        }

        if (childRelFilters.Any())
        {
            await ApplyFiltersToUnitsAsync(
                unitIds, preFilteredUnitsById, childRelFilters,
                isParentDirection: false, ct);
        }

        var result = preFilteredUnitsById.Values.ToList();

        logger.LogDebug("UnitRelationshipMatcher: выход {ContextCount} контекстов (из {InitialCount})",
            result.Count, preFilteredUnitsById.Count);

        return result;
    }

    /// <summary>
    /// Применяет фильтры к связям (родителям или детям)
    /// </summary>
    private async Task ApplyFiltersToUnitsAsync(
        IReadOnlyList<Guid> unitIds,
        Dictionary<Guid, UnitFilterMatchResult> preFilteredUnitsById,
        List<JobRelationshipFilter> relFilters,
        bool isParentDirection,
        CancellationToken cancellationToken)
    {
        if (!relFilters.Any())
            return;

        var directionName = isParentDirection ? "Родительские" : "Дочерние";
        logger.LogDebug("  {Direction} фильтры ({Count}):", directionName, relFilters.Count);

        // 1. Получить все связи для юнитов из unitIds
        var allLinks = await unitInUnitRepository.Get()
            .AsNoTracking()
            .Where(link => isParentDirection
                ? unitIds.Contains(link.ChildUnitId)
                : unitIds.Contains(link.ParentUnitId))
            .Select(link => new
            {
                SourceId = isParentDirection ? link.ChildUnitId : link.ParentUnitId,
                TargetId = isParentDirection ? link.ParentUnitId : link.ChildUnitId
            })
            .ToListAsync(cancellationToken);

        var allRelatedUnitIds = allLinks.Select(l => l.TargetId).Distinct().ToList();
        logger.LogDebug("    Найдено {TargetCount} уникальных {TargetType} для {LinkCount} связей",
            allRelatedUnitIds.Count,
            isParentDirection ? "родителей" : "детей",
            allLinks.Count);

        // 2. Сгруппировать связи по SourceId
        var relationshipsByUnitId = allLinks.GroupBy(l => l.SourceId)
            .ToDictionary(g => g.Key, g => g.Select(l => l.TargetId).ToList());

        // 3. Для каждого фильтра найти TargetId, которые ему соответствуют
        var matchedTargetIdsByFilter = new Dictionary<JobRelationshipFilter, HashSet<Guid>>();

        foreach (var relFilter in relFilters)
        {
            var valueMask = relFilter.ValueMask?.Trim();
            if (string.IsNullOrWhiteSpace(valueMask))
                continue;

            string dbValueMask = NormalizeLikeMask(valueMask);
            var fieldName = relFilter.UnitField?.AihitName ?? $"FieldId={relFilter.FieldId}";

            logger.LogDebug("    RelationshipFilter ({Direction}): Поле='{FieldName}', Маска='{Mask}', IsInverse={IsInverse}, IsFullMatch={IsFullMatch}",
                isParentDirection ? "Parent" : "Child",
                fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);

            var matchingTargetIds = await unitInValueRepository.GetMatchingTargetIds(relFilter.FieldId, dbValueMask)
                .ToListAsync(cancellationToken);

            var filteredMatchingTargetIds = matchingTargetIds
                .Intersect(allRelatedUnitIds)
                .ToList();

            logger.LogDebug("      Найдено {MatchCount} {TargetType} по маске",
                filteredMatchingTargetIds.Count,
                isParentDirection ? "родителей" : "детей");

            HashSet<Guid> targetIdsThatPassThisFilter;

            if (relFilter.IsInverse)
            {
                targetIdsThatPassThisFilter = new HashSet<Guid>(allRelatedUnitIds.Except(filteredMatchingTargetIds));
                logger.LogDebug("      (IsInverse) Юниты, прошедшие фильтр: {Count}", targetIdsThatPassThisFilter.Count);
            }
            else
            {
                targetIdsThatPassThisFilter = new HashSet<Guid>(filteredMatchingTargetIds);
                logger.LogDebug("      (Direct) Юниты, прошедшие фильтр: {Count}", targetIdsThatPassThisFilter.Count);
            }

            matchedTargetIdsByFilter[relFilter] = targetIdsThatPassThisFilter;
        }

        // 4. Определить, какие юниты проходят ВСЕ фильтры с учётом IsFullMatch
        var unitIdsToRemove = new HashSet<Guid>();

        foreach (var context in preFilteredUnitsById.Values)
        {
            var relatedUnitIds = relationshipsByUnitId.GetValueOrDefault(context.UnitId, new List<Guid>()).ToHashSet();
            bool hasTargets = relatedUnitIds.Count > 0;

            if (!hasTargets)
            {
                var hasFiltersWithMask = relFilters.Any(rf => !string.IsNullOrWhiteSpace(rf.ValueMask?.Trim()));
                if (hasFiltersWithMask)
                {
                    unitIdsToRemove.Add(context.UnitId);
                    continue;
                }
            }

            bool unitPassesCurrentFilterGroup = true;

            foreach (var relFilter in relFilters)
            {
                var valueMask = relFilter.ValueMask?.Trim();
                if (string.IsNullOrWhiteSpace(valueMask))
                    continue;

                var targetIdsThatPassThisFilter = matchedTargetIdsByFilter[relFilter];

                if (relFilter.IsFullMatch)
                {
                    bool allTargetsPass = relatedUnitIds.All(targetId => targetIdsThatPassThisFilter.Contains(targetId));
                    if (!allTargetsPass)
                    {
                        unitPassesCurrentFilterGroup = false;
                        break;
                    }
                }
                else
                {
                    bool anyTargetPasses = relatedUnitIds.Any(targetId => targetIdsThatPassThisFilter.Contains(targetId));
                    if (!anyTargetPasses)
                    {
                        unitPassesCurrentFilterGroup = false;
                        break;
                    }
                }
            }

            if (!unitPassesCurrentFilterGroup)
            {
                unitIdsToRemove.Add(context.UnitId);
            }
            else
            {
                var validRelatedIds = new HashSet<Guid>(relatedUnitIds);

                foreach (var relFilter in relFilters)
                {
                    var valueMask = relFilter.ValueMask?.Trim();
                    if (string.IsNullOrWhiteSpace(valueMask))
                        continue;

                    validRelatedIds.IntersectWith(matchedTargetIdsByFilter[relFilter]);
                }

                if (isParentDirection)
                    context.ValidParentIds.UnionWith(validRelatedIds);
                else
                    context.ValidChildIds.UnionWith(validRelatedIds);
            }
        }

        foreach (var unitId in unitIdsToRemove)
            preFilteredUnitsById.Remove(unitId);

        logger.LogDebug("    Удалено {RemovedCount} юнитов, не прошедших фильтры", unitIdsToRemove.Count);

        var totalAdded = preFilteredUnitsById.Values.Sum(c =>
            isParentDirection ? c.ValidParentIds.Count : c.ValidChildIds.Count);

        logger.LogDebug("    Добавлено {Total} {TargetType} связей для {UnitCount} юнитов",
            totalAdded,
            isParentDirection ? "родительских" : "дочерних",
            preFilteredUnitsById.Count);
    }

    /// <summary>
    /// Обрабатывает маску LIKE для корректной работы с SQL
    /// </summary>
    private static string NormalizeLikeMask(string valueMask)
    {
        if (string.IsNullOrWhiteSpace(valueMask))
            return valueMask;

        valueMask = valueMask.Trim();
        bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
        bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");

        if (isStartsWith && isEndsWith)
            return $"%{valueMask.Trim('%')}%";
        else if (isStartsWith)
            return $"{valueMask.TrimEnd('%')}%";
        else if (isEndsWith)
            return $"%{valueMask.TrimStart('%')}";
        else
            return valueMask;
    }
}