using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.AIHITRelationshipsSyncer.Models;
using PARR.AIHITRelationshipsSyncer.Services.Interfaces;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Domain.Entities.Unit;
using PARR.Domain.Settings;

namespace PARR.AIHITRelationshipsSyncer.Services.Implementations;

internal class RelationshipsSyncService : IRelationshipsSyncService
{
    private readonly ILogger<RelationshipsSyncService> _logger;
    private readonly IUnitRepository _unitRepository;
    private readonly SettingsFromDb _settingsFromDb;

    public RelationshipsSyncService(
        ILogger<RelationshipsSyncService> logger,
        IUnitRepository unitRepository,
        SettingsFromDb settingsFromDb)
    {
        _logger = logger;
        _unitRepository = unitRepository;
        _settingsFromDb = settingsFromDb;
    }

    public async Task SyncAsync(List<AihitData> aihitData)
    {
        var validPairs = ValidateInput(aihitData);

        if (validPairs.Count == 0)
            return;

        if (!await PassSafeguardCheckAsync(validPairs.Count))
            return;

        var unitNames = ExtractUnitNames(validPairs);
        var unitsByName = await GetOrCreateUnitsAsync(unitNames);

        var (toRemove, toAdd) = await GetDeltaAsync(validPairs);

        // Удаление и добавление накапливаются в одном ChangeTracker.
        // CommitAsync сохраняет всё атомарно через SaveChangesAsync.
        await RemoveRelationshipsAsync(toRemove);
        await AddRelationshipsAsync(toAdd, unitsByName);
        await CommitChangesAsync(toRemove.Count, toAdd.Count);
    }

    private List<(string ParentName, string ChildName)> ValidateInput(List<AihitData> data)
    {
        if (data == null || data.Count == 0)
        {
            _logger.LogWarning("Получены пустые данные из АИХ ИТ. Синхронизация пропущена");
            return new List<(string, string)>();
        }

        var validPairs = data
            .Where(item => !string.IsNullOrWhiteSpace(item.ParentName) && !string.IsNullOrWhiteSpace(item.ChildName))
            .Where(item => item.ParentName != item.ChildName)
            .Select(item => (
                ParentName: item.ParentName!.Trim().ToUpperInvariant(),
                ChildName: item.ChildName!.Trim().ToUpperInvariant()
            ))
            .Distinct()
            .ToList();

        if (validPairs.Count == 0)
        {
            _logger.LogWarning("После нормализации не осталось валидных связей. Синхронизация пропущена");
            return new List<(string, string)>();
        }

        _logger.LogInformation("Получено {Count} валидных связей", validPairs.Count);
        return validPairs;
    }

    private async Task<bool> PassSafeguardCheckAsync(int incomingCount)
    {
        var previousCount = await _unitRepository.Get()
            .SelectMany(u => u.ChildUnits)
            .CountAsync();

        if (previousCount == 0)
            return true;

        var currentPercentage = Math.Round((decimal)incomingCount / previousCount * 100, 1, MidpointRounding.AwayFromZero);

        if (currentPercentage < _settingsFromDb.MinRelationshipsThresholdPct)
        {
            _logger.LogWarning(
                "Синхронизация отменена: количество полученных связей ниже порогового значения. " +
                "Получено: {CurrentCount} ({CurrentPercentage:F1}%), порог: {Threshold}% от прошлого объема ({PreviousCount})",
                incomingCount, currentPercentage, _settingsFromDb.MinRelationshipsThresholdPct, previousCount);
            return false;
        }

        return true;
    }

    private static List<string> ExtractUnitNames(List<(string ParentName, string ChildName)> pairs)
    {
        return pairs
            .SelectMany(pair => new[] { pair.ParentName, pair.ChildName })
            .Distinct()
            .ToList();
    }

    private async Task<Dictionary<string, Unit>> GetOrCreateUnitsAsync(List<string> names)
    {
        var existingUnits = await _unitRepository.Get()
            .AsNoTracking()
            .ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);

        var missingNames = names.Except(existingUnits.Keys).ToList();

        if (missingNames.Count == 0)
            return existingUnits;

        _logger.LogInformation("Создание отсутствующих юнитов: {Count}", missingNames.Count);

        foreach (var name in missingNames)
        {
            var unit = new Unit
            {
                Id = Guid.NewGuid(),
                Name = name,
                DateCreated = DateTimeOffset.UtcNow
            };

            if (!await _unitRepository.CreateAsync(unit))
            {
                _logger.LogError("Не удалось создать юнит: {Name}", name);
                continue;
            }

            existingUnits[name] = unit;
            _logger.LogDebug("Создан юнит: {Name}", name);
        }

        await _unitRepository.CommitAsync();
        return existingUnits;
    }

    private async Task<(List<ExistingRelationship> ToRemove, List<(string ParentName, string ChildName)> ToAdd)> GetDeltaAsync(
        List<(string ParentName, string ChildName)> sourcePairs)
    {
        var relevantParentNames = sourcePairs.Select(p => p.ParentName).Distinct().ToList();

        var existingForParents = await _unitRepository.Get()
            .AsNoTracking()
            .Include(u => u.ChildUnits).ThenInclude(r => r.ChildUnit)
            .Where(u => relevantParentNames.Contains(u.Name))
            .SelectMany(u => u.ChildUnits, (parent, relationship) => new ExistingRelationship
            {
                ParentName = parent.Name,
                ChildName = relationship.ChildUnit!.Name,
                Relationship = relationship
            })
            .ToListAsync();

        var sourceSet = new HashSet<(string, string)>(sourcePairs);
        var existingSet = new HashSet<(string, string)>(
            existingForParents.Select(x => (x.ParentName, x.ChildName)));

        var toRemove = existingForParents
            .Where(r => !sourceSet.Contains((r.ParentName, r.ChildName)))
            .ToList();

        var toAdd = sourcePairs
            .Where(p => !existingSet.Contains(p))
            .ToList();

        _logger.LogInformation("Дельта: удаление {ToRemove}, добавление {ToAdd}", toRemove.Count, toAdd.Count);
        return (toRemove, toAdd);
    }

    private async Task RemoveRelationshipsAsync(List<ExistingRelationship> toRemove)
    {
        if (toRemove.Count == 0)
            return;

        var parentNames = toRemove.Select(r => r.ParentName).Distinct().ToList();

        var parents = await _unitRepository.Get()
            .Include(u => u.ChildUnits)
            .Where(u => parentNames.Contains(u.Name))
            .ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);

        foreach (var relationship in toRemove)
        {
            if (!parents.TryGetValue(relationship.ParentName, out var parent))
            {
                _logger.LogWarning("Родитель '{Parent}' не найден при удалении связи с '{Child}'",
                    relationship.ParentName, relationship.ChildName);
                continue;
            }

            var entityToRemove = parent.ChildUnits
                .FirstOrDefault(r => r.ChildUnitId == relationship.Relationship.ChildUnitId);

            if (entityToRemove != null)
                parent.ChildUnits.Remove(entityToRemove);
        }
    }

    private async Task AddRelationshipsAsync(
        List<(string ParentName, string ChildName)> toAdd,
        Dictionary<string, Unit> unitsByName)
    {
        if (toAdd.Count == 0)
            return;

        var parentNames = toAdd.Select(x => x.ParentName).Distinct().ToList();

        var parents = await _unitRepository.Get()
            .Include(u => u.ChildUnits)
            .Where(u => parentNames.Contains(u.Name))
            .ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);

        foreach (var (parentName, childName) in toAdd)
        {
            if (!parents.TryGetValue(parentName, out var parent) ||
                !unitsByName.TryGetValue(childName, out var child))
            {
                _logger.LogWarning("Пропущена связь '{Parent}' - '{Child}': юнит не найден", parentName, childName);
                continue;
            }

            if (parent.ChildUnits.Any(r => r.ChildUnitId == child.Id))
                continue;

            parent.ChildUnits.Add(new UnitInUnit
            {
                ParentUnitId = parent.Id,
                ChildUnitId = child.Id,
                DateCreated = DateTimeOffset.UtcNow
            });
        }
    }

    private async Task CommitChangesAsync(int removedCount, int addedCount)
    {
        if (removedCount == 0 && addedCount == 0)
        {
            _logger.LogInformation("Изменений не обнаружено");
            return;
        }

        if (!await _unitRepository.CommitAsync())
            throw new InvalidOperationException("Не удалось сохранить изменения связей в БД");

        _logger.LogInformation("Синхронизация завершена. Удалено: {Removed}, добавлено: {Added}", removedCount, addedCount);
    }

    private sealed class ExistingRelationship
    {
        public string ParentName { get; init; } = null!;
        public string ChildName { get; init; } = null!;
        public UnitInUnit Relationship { get; init; } = null!;
    }
}