using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.DAL.Cache.Services.Base;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.Models.Job;
using PARR.DAL.Models.Unit;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;

namespace PARR.DAL.DomainServices.Implementations
{
    internal class UnitFilterService : IUnitFilterService
    {
#if DEBUG
        private readonly Guid targetUnitId = Guid.Parse("87fc4c36-1ea8-4163-983f-1605fee1de99");
#endif

        private readonly ILogger<UnitFilterService> logger;
        private readonly IJobService jobService;
        private readonly IUnitService unitService;
        private readonly IUnitInUnitService unitInUnitService;
        private readonly IRedisCacheService cacheService;
        private readonly IUnitInValueService unitInValueService;

        public UnitFilterService(
            ILogger<UnitFilterService> logger,
            IJobService jobService,
            IUnitService unitService,
            IUnitInUnitService unitInUnitService,
            IUnitInValueService unitInValueService,
            IRedisCacheService cacheService
            )
        {
            this.logger = logger;
            this.jobService = jobService;
            this.unitService = unitService;
            this.unitInUnitService = unitInUnitService;
            this.cacheService = cacheService;
            this.unitInValueService = unitInValueService;
        }

        #region вложенные классы

        private class UnitInfoDto
        {
            public Guid Id { get; set; }
            public required string Name { get; set; }
        }

        private class UnitValueDto
        {
            public Guid FieldId { get; set; }
            public string? Value { get; set; }
        }

        private class RelatedUnitDto
        {
            public Guid UnitId { get; set; }
            public string Name { get; set; } = null!;
            public List<UnitValueDto> Values { get; set; } = new();
        }

        private class UnitDto
        {
            public Guid Id { get; set; }
            public string Name { get; set; } = null!;
            public List<UnitValueDto> Values { get; set; } = new();
            public List<RelatedUnitDto> Parents { get; set; } = new();
            public List<RelatedUnitDto> Children { get; set; } = new();
        }


        public class CachedUnitIds
        {
            public List<Guid> UnitIds { get; set; } = new();
            public DateTimeOffset Timestamp { get; set; }
            public string? Source { get; set; } = "UnitFilterService";
            public int Version { get; set; } = 1;
        }

        #endregion

        public async Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(Guid jobId, int? takeCount = null)
        {
            var job = await jobService
                .Get().AsNoTracking()
                .Include(j => j.UnitFilters).ThenInclude(uf => uf.FieldFilters)
                .Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters)
                .Include(j => j.Group).ThenInclude(g => g!.GroupType)
                .FirstOrDefaultAsync(j => j.Id == jobId);

            return job == null ? null : await GetUnitsIdByJobFilterAsync(job, takeCount);
        }

        public async Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(Job job, int? takeCount = null)
        {
            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 maxCount = takeCount ?? int.MaxValue;
            var collectedIds = new HashSet<Guid>();
            var filterNumber = 0;

            foreach (var filter in job.UnitFilters)
            {
                filterNumber++;
                if (collectedIds.Count >= maxCount) break;
                var remaining = maxCount - collectedIds.Count;
                if (remaining <= 0) break;

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

                    // 1️ Найти ID юнитов по UnitFilter (Name LIKE)
                    var initialUnitIds = await GetUnitIdsFromCacheOrDbAsync(filter);

                    logger.LogDebug("Базовый фильтр по Name '{NameFilter}' дал {Count} юнитов", filter.UnitFilter, initialUnitIds.Count);

#if DEBUG
                    //  Отладка: проверить, есть ли юнит в initialUnitIds
                    if (initialUnitIds.Contains(targetUnitId))
                    {
                        logger.LogDebug("Юнит {TargetUnitId} найден в initialUnitIds.", targetUnitId);
                    }
                    else
                    {
                        logger.LogDebug("Юнит {TargetUnitId} НЕ найден в initialUnitIds.", targetUnitId);
                        //continue; // ❌ юнит отсеялся на этом этапе
                    }
#endif

                    if (!initialUnitIds.Any())
                    {
                        logger.LogDebug("Фильтр #{Index}: 0 юнитов после UnitFilter. Пропускаем.", filterNumber);
                        continue;
                    }

                    // 2️ Загрузить UnitValues для этих юнитов (только FieldId из FieldFilter)
                    var fieldFilterFieldIds = filter.FieldFilters.Select(f => f.FieldId).ToHashSet();
                    var unitValues = fieldFilterFieldIds.Any()
                        ? await unitInValueService.GetByUnitIdsAndFieldIdsAsync(initialUnitIds, fieldFilterFieldIds)
                        : new List<UnitInValue>();

                    // 3️ Загрузить связи
                    var parentRelFilters = filter.RelationshipFilters.Where(rf => rf.IsParent).ToList();
                    var childRelFilters = filter.RelationshipFilters.Where(rf => !rf.IsParent).ToList();

                    var parentLinks = await unitInUnitService.GetParentLinksByChildIdsAsync(initialUnitIds);

                    var childLinks = await unitInUnitService.GetChildLinksByParentIdsAsync(initialUnitIds);

                    // 4️ ID родителей и детей
                    var parentUnitIds = parentLinks.Select(l => l.ParentUnitId).ToHashSet();
                    var childUnitIds = childLinks.Select(l => l.ChildUnitId).ToHashSet();

                    // 5️ Загрузить родительские и дочерние юниты (Id + Name)
                    var parentUnits = parentUnitIds.Any()
                        ? await unitService.Get().AsNoTracking()
                            .Where(u => parentUnitIds.Contains(u.Id))
                            .Select(u => new UnitInfoDto { Id = u.Id, Name = u.Name })
                            .ToListAsync()
                        : new List<UnitInfoDto>();

                    var childUnits = childUnitIds.Any()
                        ? await unitService.Get().AsNoTracking()
                            .Where(u => childUnitIds.Contains(u.Id))
                            .Select(u => new UnitInfoDto { Id = u.Id, Name = u.Name })
                            .ToListAsync()
                        : new List<UnitInfoDto>();

                    // 6️ UnitValues для родителей и детей (только FieldId из RelationshipFilter)
                    var relFilterFieldIds = filter.RelationshipFilters.Select(f => f.FieldId).ToHashSet();

                    var parentUnitValues = relFilterFieldIds.Any() && parentUnitIds.Any()
                        ? await unitInValueService.GetByUnitIdsAndFieldIdsAsync(parentUnitIds, relFilterFieldIds)
                        : new List<UnitInValue>();

                    var childUnitValues = relFilterFieldIds.Any() && childUnitIds.Any()
                        ? await unitInValueService.GetByUnitIdsAndFieldIdsAsync(childUnitIds, relFilterFieldIds)
                        : new List<UnitInValue>();

                    // 7️ Сборка DTO
                    var unitMap = initialUnitIds.ToDictionary(id => id, id => new UnitDto { Id = id });

                    // UnitValues → Unit
                    foreach (var uv in unitValues)
                    {
                        if (unitMap.TryGetValue(uv.UnitId, out var dto))
                        {
                            dto.Values.Add(new UnitValueDto { FieldId = uv.FieldId, Value = uv.Value?.Value });
                        }
                    }

                    var parentIdMap = parentUnits.ToDictionary(u => u.Id, u => u.Name);
                    var childIdMap = childUnits.ToDictionary(u => u.Id, u => u.Name);

                    // ParentLinks → Parent
                    foreach (var link in parentLinks)
                    {
                        if (unitMap.TryGetValue(link.ChildUnitId, out var dto))
                        {
                            dto.Parents.Add(new RelatedUnitDto
                            {
                                UnitId = link.ParentUnitId,
                                Name = parentIdMap.GetValueOrDefault(link.ParentUnitId, "")
                            });
                        }
                    }

                    // ChildLinks → Child
                    foreach (var link in childLinks)
                    {
                        if (unitMap.TryGetValue(link.ParentUnitId, out var dto))
                        {
                            dto.Children.Add(new RelatedUnitDto
                            {
                                UnitId = link.ChildUnitId,
                                Name = childIdMap.GetValueOrDefault(link.ChildUnitId, "")
                            });
                        }
                    }

                    // Привязка значений к родителям
                    var parentValuesMap = parentUnitValues
                        .GroupBy(v => v.UnitId)
                        .ToDictionary(g => g.Key, g => g.Select(v => new UnitValueDto { FieldId = v.FieldId, Value = v.Value?.Value }).ToList());

                    foreach (var dto in unitMap.Values)
                    {
                        foreach (var parent in dto.Parents)
                        {
                            if (parentValuesMap.TryGetValue(parent.UnitId, out var values))
                            {
                                parent.Values.AddRange(values);
                            }
                        }
                    }

                    // Привязка значений к детям
                    var childValuesMap = childUnitValues
                        .GroupBy(v => v.UnitId)
                        .ToDictionary(g => g.Key, g => g.Select(v => new UnitValueDto { FieldId = v.FieldId, Value = v.Value?.Value }).ToList());

                    foreach (var dto in unitMap.Values)
                    {
                        foreach (var child in dto.Children)
                        {
                            if (childValuesMap.TryGetValue(child.UnitId, out var values))
                            {
                                child.Values.AddRange(values);
                            }
                        }
                    }

                    // 8️ Применение фильтров
                    var candidateUnits = unitMap.Values.AsQueryable();

                    foreach (var fieldFilter in filter.FieldFilters)
                    {
                        candidateUnits = ApplyFieldFilter(candidateUnits, fieldFilter);
                    }

#if DEBUG
                    //  Отладка: проверить, есть ли юнит в candidateUnits после FieldFilter
                    var candidateListAfterFieldFilter = candidateUnits.ToList();
                    if (candidateListAfterFieldFilter.Any(u => u.Id == targetUnitId))
                    {
                        logger.LogDebug("Юнит {TargetUnitId} найден в candidateUnits после FieldFilter.", targetUnitId);
                    }
                    else
                    {
                        logger.LogDebug("Юнит {TargetUnitId} НЕ найден в candidateUnits после FieldFilter.", targetUnitId);
                    }

                    logger.LogDebug("После FieldFilter осталось {Count} юнитов", candidateListAfterFieldFilter.Count);
#endif

                    foreach (var relFilter in filter.RelationshipFilters)
                    {
                        candidateUnits = ApplyRelationshipFilterToQuery(candidateUnits, relFilter);
                    }

#if DEBUG
                    //  Отладка: проверить, есть ли юнит в candidateUnits после RelationshipFilter
                    var candidateListAfterRelFilter = candidateUnits.ToList();
                    if (candidateListAfterRelFilter.Any(u => u.Id == targetUnitId))
                    {
                        logger.LogDebug("Юнит {TargetUnitId} найден в candidateUnits после RelationshipFilter.", targetUnitId);
                    }
                    else
                    {
                        logger.LogDebug("Юнит {TargetUnitId} НЕ найден в candidateUnits после RelationshipFilter.", targetUnitId);
                    }

                    logger.LogDebug("После RelationshipFilter осталось {Count} юнитов", candidateListAfterRelFilter.Count);
#endif

                    // 9️ Umbrella-фильтр
                    var finalUnits = candidateUnits.AsEnumerable();
                    if (job.Group.GroupType.Code == JobGroupTypesEnum.Umbrella)
                    {
                        finalUnits = ApplyRelationshipCountFilter(finalUnits, job, filter.RelationshipFilters);
                        logger.LogDebug("После Umbrella-фильтра осталось {Count} юнитов", finalUnits.Count());

#if DEBUG
                        //  Отладка: проверить, есть ли юнит в finalUnits после Umbrella
                        if (finalUnits.Any(u => u.Id == targetUnitId))
                        {
                            logger.LogDebug("Юнит {TargetUnitId} найден в finalUnits после Umbrella-фильтра.", targetUnitId);
                        }
                        else
                        {
                            logger.LogDebug("Юнит {TargetUnitId} НЕ найден в finalUnits после Umbrella-фильтра.", targetUnitId);
                            //continue; // ❌ юнит отсеялся на этом этапе
                        }
#endif
                    }

                    // 10 Взять ID
                    var newIds = finalUnits
                        .Select(u => u.Id)
                        .Take(remaining)
                        .ToList();

#if DEBUG
                    //  Отладка: проверить, есть ли юнит в newIds
                    if (newIds.Contains(targetUnitId))
                    {
                        logger.LogDebug("Юнит {TargetUnitId} найден в newIds.", targetUnitId);
                    }
                    else
                    {
                        logger.LogDebug("Юнит {TargetUnitId} НЕ найден в newIds.", targetUnitId);
                        // ❌ юнит отсеялся на этапе Take(remaining), если его не было в finalUnits
                    }
#endif

                    collectedIds.UnionWith(newIds);
                    logger.LogDebug("Фильтр #{Index}: найдено {Count} Unit'ов. Всего: {Total}",
                        filterNumber, newIds.Count, collectedIds.Count);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "Ошибка при обработке фильтра {FilterId} (#{Index}) для Job {JobId}",
                        filter.Id, filterNumber, job.Id);
                }
            }

            var result = collectedIds.Take(maxCount).ToList();
            logger.LogInformation("Job {JobId}: из {FilterCount} фильтров получено {UnitCount} уникальных Unit'ов",
                job.Id, job.UnitFilters.Count, result.Count);

#if DEBUG
            //  Отладка: проверить, есть ли юнит в result
            if (result.Contains(targetUnitId))
            {
                logger.LogDebug("Юнит {TargetUnitId} найден в финальном результате.", targetUnitId);
            }
            else
            {
                logger.LogDebug("Юнит {TargetUnitId} НЕ найден в финальном результате.", targetUnitId);
            }
#endif

            return result;
        }

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

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

            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 unitInUnitService.GetByChildIdAsync(unitId);
                var childLinks = await unitInUnitService.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 unitInValueService.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)
                {
                    //  Изменяем на `All` — все фильтры должны подходить
                    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()) // Нет значений по полю
                        {
                            //  Если нет значений, и IsInverse = false → не подходит
                            //  Если нет значений, и IsInverse = true → подходит
                            return rf.IsInverse;
                        }

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

                        //  Учитываем IsInverse
                        if (rf.IsInverse)
                            hasMatch = !hasMatch;

                        return hasMatch;
                    });

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

                if (matchingUnitIds.Any())
                {
                    var names = await unitService.Get().AsNoTracking()
                        .Where(u => matchingUnitIds.Contains(u.Id))
                        .Select(u => u.Name)
                        .ToListAsync();
                    result.UnionWith(names);
                }
            }

            return result.ToList();
        }

        #region вспомогательные методы

        private IQueryable<UnitDto> ApplyFieldFilter(IQueryable<UnitDto> query, JobFieldFilter fieldFilter)
        {
            var valueMask = fieldFilter.ValueMask?.Trim();
            if (string.IsNullOrEmpty(valueMask))
                return query;

            logger.LogDebug("Применяем FieldFilterDto: FieldId={FieldId}, ValueMask={ValueMask}, IsInverse={IsInverse}", fieldFilter.FieldId, valueMask, fieldFilter.IsInverse);

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

            return query.AsEnumerable().Where(dto =>
            {
                var hasMatch = dto.Values.Any(v =>
                    v.FieldId == fieldFilter.FieldId &&
                    v.Value != null &&
                    (isStartsWith && isEndsWith ? v.Value.Contains(valueMask.Trim('%'), StringComparison.OrdinalIgnoreCase) :
                     isStartsWith ? v.Value.StartsWith(valueMask.TrimEnd('%'), StringComparison.OrdinalIgnoreCase) :
                     isEndsWith ? v.Value.EndsWith(valueMask.TrimStart('%'), StringComparison.OrdinalIgnoreCase) :
                     v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
                );

                //  Правильная логика: если IsInverse = true, то юнит подходит, если НЕ проходит фильтр
                return fieldFilter.IsInverse ? !hasMatch : hasMatch;
            }).AsQueryable();
        }

        private IQueryable<UnitDto> ApplyRelationshipFilterToQuery(IQueryable<UnitDto> query, JobRelationshipFilter relFilter)
        {
            var valueMask = relFilter.ValueMask?.Trim();
            if (string.IsNullOrWhiteSpace(valueMask))
                return query;

            logger.LogDebug("Применяем RelationshipFilterDto: IsParent={IsParent}, IsInverse={IsInverse}, IsFullMatch={IsFullMatch}, FieldId={FieldId}, ValueMask={ValueMask}",
                relFilter.IsParent, relFilter.IsInverse, relFilter.IsFullMatch, relFilter.FieldId, valueMask);

            var isParent = relFilter.IsParent;
            var isInverse = relFilter.IsInverse;
            var isFullMatch = relFilter.IsFullMatch;
            var fieldId = relFilter.FieldId;

            //  Используем Trim('%') для всех случаев
            var trimmedMask = valueMask.Trim('%');

            return query.AsEnumerable().Where(dto =>
            {
#if DEBUG
                //  Отладка: проверить, это нужный юнит
                if (dto.Id == targetUnitId)
                {
                    logger.LogDebug("Проверяем юнит {TargetUnitId} с {ParentCount} родителями и {ChildCount} детьми.",
                        targetUnitId, dto.Parents.Count, dto.Children.Count);
                }
#endif

                var links = isParent ? dto.Parents : dto.Children;

                if (links == null || !links.Any())
                {
#if DEBUG
                    //  Отладка: юнит не имеет связей
                    if (dto.Id == targetUnitId)
                    {
                        logger.LogDebug("Юнит {TargetUnitId}: нет связей ({Direction}), результат фильтра: {Result}",
                            targetUnitId, isParent ? "Parent" : "Child", isFullMatch && isInverse);
                    }
#endif

                    // Для IsFullMatch/IsInverse — "все подходят", т.е. true
                    // Для остальных — false
                    return isFullMatch && isInverse;
                }

#if DEBUG
                //  Отладка: проверить, какие связи у юнита
                if (dto.Id == targetUnitId)
                {
                    logger.LogDebug("Юнит {TargetUnitId}: {Count} связей ({Direction}).", targetUnitId, links.Count, isParent ? "Parent" : "Child");
                    foreach (var link in links)
                    {
                        logger.LogDebug("  Связь: UnitId={LinkUnitId}, Name={LinkName}, Values=[{Values}]", link.UnitId, link.Name, string.Join(", ", link.Values.Select(v => $"{v.FieldId}={v.Value}")));
                    }
                }
#endif

                if (!isFullMatch)
                {
                    //  Если IsFullMatch = false — проверяем, есть ли хотя бы одна подходящая связь
                    var hasMatchingLinks = links.Any(link =>
                    {
                        var values = link.Values.Where(v => v.FieldId == fieldId).ToList();

                        if (!values.Any())
                        {
                            //  Если нет значений по полю — связь не подходит, если IsInverse = false
                            //  Если нет значений по полю — связь подходит, если IsInverse = true
                            return isInverse;
                        }

                        var hasMatch = values.Any(v =>
                            v.Value != null &&
                            //  Проверка с Trim('%')
                            (valueMask.StartsWith("%") && valueMask.EndsWith("%") ? v.Value.Contains(trimmedMask, StringComparison.OrdinalIgnoreCase) :
                             valueMask.StartsWith("%") ? v.Value.EndsWith(trimmedMask, StringComparison.OrdinalIgnoreCase) :
                             valueMask.EndsWith("%") ? v.Value.StartsWith(trimmedMask, StringComparison.OrdinalIgnoreCase) :
                             string.Equals(v.Value, valueMask, StringComparison.OrdinalIgnoreCase))
                        );

                        //  Учитываем IsInverse
                        return isInverse ? !hasMatch : hasMatch;
                    });

#if DEBUG
                    //  Отладка: результат hasMatchingLinks
                    if (dto.Id == targetUnitId)
                    {
                        logger.LogDebug("  hasMatchingLinks = {HasMatchingLinks}, isInverse = {IsInverse}", hasMatchingLinks, isInverse);
                    }
#endif

                    return hasMatchingLinks;
                }
                else
                {
                    //  Если IsFullMatch = true — все связи должны подходить
                    var allMatch = links.All(link =>
                    {
                        var values = link.Values.Where(v => v.FieldId == fieldId).ToList();

                        if (!values.Any())
                        {
                            //  Если нет значений по полю — связь не подходит, если IsInverse = false
                            //  Если нет значений по полю — связь подходит, если IsInverse = true
                            return isInverse;
                        }

                        var hasMatch = values.Any(v =>
                            v.Value != null &&
                            //  Проверка с Trim('%')
                            (valueMask.StartsWith("%") && valueMask.EndsWith("%") ? v.Value.Contains(trimmedMask, StringComparison.OrdinalIgnoreCase) :
                             valueMask.StartsWith("%") ? v.Value.EndsWith(trimmedMask, StringComparison.OrdinalIgnoreCase) :
                             valueMask.EndsWith("%") ? v.Value.StartsWith(trimmedMask, StringComparison.OrdinalIgnoreCase) :
                             string.Equals(v.Value, valueMask, StringComparison.OrdinalIgnoreCase))
                        );

                        //  Учитываем IsInverse
                        return isInverse ? !hasMatch : hasMatch;
                    });

#if DEBUG
                    //  Отладка: результат allMatch
                    if (dto.Id == targetUnitId)
                    {
                        logger.LogDebug("  allMatch = {AllMatch}, isFullMatch = {IsFullMatch}", allMatch, isFullMatch);
                    }
#endif

                    return allMatch;
                }
            }).AsQueryable();
        }

        private IEnumerable<UnitDto> ApplyRelationshipCountFilter(
            IEnumerable<UnitDto> units,
            Job job,
            IEnumerable<JobRelationshipFilter> relationshipFilters)
        {
            var min = job.MinValueRelationships ?? 0;
            var max = job.MaxValueRelationships ?? int.MaxValue;
            var isParentDirection = job.IsParentRelationships == true;

            if (min == 0 && max == int.MaxValue)
            {
                logger.LogDebug("RelationshipCountFilter: Min и Max не заданы — пропускаем фильтр.");
                return units;
            }

            logger.LogDebug("RelationshipCountFilter: Min={Min}, Max={Max}, IsParent={IsParent}", min, max, isParentDirection);

            // Разделяем фильтры по направлениям
            var parentFilters = relationshipFilters
                .Where(rf => rf.IsParent && !string.IsNullOrWhiteSpace(rf.ValueMask))
                .ToList();

            var childFilters = relationshipFilters
                .Where(rf => !rf.IsParent && !string.IsNullOrWhiteSpace(rf.ValueMask))
                .ToList();

            bool hasParentFieldFilters = parentFilters.Count > 0;
            bool hasChildFieldFilters = childFilters.Count > 0;

            logger.LogDebug("RelationshipCountFilter: Найдено {ParentCount} фильтров по родителям и {ChildCount} фильтров по детям", parentFilters.Count, childFilters.Count);

            return units.Where(dto =>
            {
                int matchingCount = 0;

                if (isParentDirection) // Считаем родительские связи
                {
                    var links = dto.Parents;

                    if (links == null || !links.Any())
                    {
                        var result = min == 0;
                        logger.LogDebug("UnitId {UnitId}: родительских связей нет (null или пусто). Min={Min}, результат фильтра: {Result}", dto.Id, min, result);
                        return result;
                    }

                    logger.LogDebug("UnitId {UnitId}: {Count} родительских связей до фильтрации", dto.Id, links.Count);

                    if (hasParentFieldFilters)
                    {
                        // Есть фильтры по родителям → считаем только подходящие
                        foreach (var link in links)
                        {
                            // Проверяем, проходит ли связь все фильтры (логика как в ApplyRelationshipFilterToQuery)
                            bool passesAllFilters = true;

                            foreach (var f in parentFilters)
                            {
                                var values = link.Values.Where(v => v.FieldId == f.FieldId).ToList();

                                if (!values.Any())
                                {
                                    // Если нет значений по полю — связь не подходит, если IsInverse = false
                                    // Если нет значений по полю — связь подходит, если IsInverse = true
                                    if (!f.IsInverse)
                                    {
                                        passesAllFilters = false;
                                        break;
                                    }
                                    continue;
                                }

                                var hasMatch = values.Any(v =>
                                    v.Value != null &&
                                    // Проверка в зависимости от маски
                                    (f.ValueMask.StartsWith("%") && f.ValueMask.EndsWith("%") ? v.Value.Contains(f.ValueMask.Trim('%'), StringComparison.OrdinalIgnoreCase) :
                                     f.ValueMask.StartsWith("%") ? v.Value.EndsWith(f.ValueMask.TrimStart('%'), StringComparison.OrdinalIgnoreCase) :
                                     f.ValueMask.EndsWith("%") ? v.Value.StartsWith(f.ValueMask.TrimEnd('%'), StringComparison.OrdinalIgnoreCase) :
                                     string.Equals(v.Value, f.ValueMask, StringComparison.OrdinalIgnoreCase))
                                );

                                // Учитываем IsInverse
                                if (f.IsInverse)
                                    hasMatch = !hasMatch;

                                if (!hasMatch)
                                {
                                    passesAllFilters = false;
                                    break;
                                }
                            }

                            if (passesAllFilters)
                                matchingCount++;

                            if (matchingCount > max)
                            {
                                logger.LogDebug("UnitId {UnitId}: matchingCount ({Count}) > max ({Max}) — прерываем подсчёт", dto.Id, matchingCount, max);
                                break;
                            }
                        }
                    }
                    else
                    {
                        // Нет фильтров по родителям → считаем общее количество связей (без учёта значений)
                        matchingCount = links.Count;
                        logger.LogDebug("UnitId {UnitId}: нет фильтров по родителям — matchingCount = links.Count = {Count}", dto.Id, matchingCount);
                    }
                }
                else // Считаем дочерние связи
                {
                    var links = dto.Children;

                    if (links == null || !links.Any())
                    {
                        var result = min == 0;
                        logger.LogDebug("UnitId {UnitId}: дочерних связей нет (null или пусто). Min={Min}, результат фильтра: {Result}", dto.Id, min, result);
                        return result;
                    }

                    logger.LogDebug("UnitId {UnitId}: {Count} дочерних связей до фильтрации", dto.Id, links.Count);

                    if (hasChildFieldFilters)
                    {
                        // Есть фильтры по детям → считаем только подходящие
                        foreach (var link in links)
                        {
                            // Проверяем, проходит ли связь все фильтры (логика как в ApplyRelationshipFilterToQuery)
                            bool passesAllFilters = true;

                            foreach (var f in childFilters)
                            {
                                var values = link.Values.Where(v => v.FieldId == f.FieldId).ToList();

                                if (!values.Any())
                                {
                                    // Если нет значений по полю — связь не подходит, если IsInverse = false
                                    // Если нет значений по полю — связь подходит, если IsInverse = true
                                    if (!f.IsInverse)
                                    {
                                        passesAllFilters = false;
                                        break;
                                    }
                                    continue;
                                }

                                var hasMatch = values.Any(v =>
                                    v.Value != null &&
                                    // Проверка в зависимости от маски
                                    (f.ValueMask.StartsWith("%") && f.ValueMask.EndsWith("%") ? v.Value.Contains(f.ValueMask.Trim('%'), StringComparison.OrdinalIgnoreCase) :
                                     f.ValueMask.StartsWith("%") ? v.Value.EndsWith(f.ValueMask.TrimStart('%'), StringComparison.OrdinalIgnoreCase) :
                                     f.ValueMask.EndsWith("%") ? v.Value.StartsWith(f.ValueMask.TrimEnd('%'), StringComparison.OrdinalIgnoreCase) :
                                     string.Equals(v.Value, f.ValueMask, StringComparison.OrdinalIgnoreCase))
                                );

                                // Учитываем IsInverse
                                if (f.IsInverse)
                                    hasMatch = !hasMatch;

                                if (!hasMatch)
                                {
                                    passesAllFilters = false;
                                    break;
                                }
                            }

                            if (passesAllFilters)
                                matchingCount++;

                            if (matchingCount > max)
                            {
                                logger.LogDebug("UnitId {UnitId}: matchingCount ({Count}) > max ({Max}) — прерываем подсчёт", dto.Id, matchingCount, max);
                                break;
                            }
                        }
                    }
                    else
                    {
                        // Нет фильтров по детям → считаем общее количество связей (без учёта значений)
                        matchingCount = links.Count;
                        logger.LogDebug("UnitId {UnitId}: нет фильтров по детям — matchingCount = links.Count = {Count}", dto.Id, matchingCount);
                    }
                }

                var finalResult = matchingCount >= min && matchingCount <= max;
                logger.LogDebug("UnitId {UnitId}: matchingCount={Count}, Min={Min}, Max={Max}, результат фильтра: {Result}", dto.Id, matchingCount, min, max, finalResult);

                return finalResult;
            });
        }

        private async Task<List<Guid>> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter)
        {
            var cacheKey = $"uf_ids_{ComputeHash(filter.UnitFilter)}";

            var cachedData = await cacheService.GetCachedDataAsync<CachedUnitIds>(cacheKey);
            if (cachedData != null)
            {
                logger.LogDebug("Кэш попал для UnitFilter '{Name}': {Count} юнитов", filter.UnitFilter, cachedData.UnitIds.Count);
                return cachedData.UnitIds;
            }

            logger.LogDebug("Кэш промахнут для UnitFilter '{Name}'. Запрашиваем из БД.", filter.UnitFilter);

            var initialUnitIds = await unitService.Get().AsNoTracking()
                .Where(unit => EF.Functions.Like(unit.Name, filter.UnitFilter))
                .Select(u => u.Id)
                .ToListAsync();

            logger.LogDebug("Загружено {Count} юнитов из БД для UnitFilter '{Name}'", initialUnitIds.Count, filter.UnitFilter);

            var toCache = new CachedUnitIds
            {
                UnitIds = initialUnitIds,
                Timestamp = DateTimeOffset.UtcNow,
                Source = GetType().Name,
                Version = 1
            };

            await cacheService.SetCachedDataAsync(cacheKey, toCache, TimeSpan.FromHours(1));

            return initialUnitIds;
        }

        private static string ComputeHash(string input)
        {
            using var sha256 = System.Security.Cryptography.SHA256.Create();
            var hashedBytes = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
            return Convert.ToBase64String(hashedBytes).Replace('+', '-').Replace('/', '_').Substring(0, 16);
        }

        #endregion
    }
}