using System.Collections.ObjectModel;
using LightlessSync.API.Dto.Group;
using LightlessSync.PlayerData.Factories;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Models;
using Microsoft.Extensions.Logging;

namespace LightlessSync.UI.Services;

public sealed class PairUiService : DisposableMediatorSubscriberBase
{
    private readonly PairLedger _pairLedger;
    private readonly PairFactory _pairFactory;
    private readonly PairManager _pairManager;

    private readonly object _snapshotGate = new();
    private PairUiSnapshot _snapshot = PairUiSnapshot.Empty;
    private Pair? _lastAddedPair;
    private bool _needsRefresh = true;

    public PairUiService(
        ILogger<PairUiService> logger,
        LightlessMediator mediator,
        PairLedger pairLedger,
        PairFactory pairFactory,
        PairManager pairManager) : base(logger, mediator)
    {
        _pairLedger = pairLedger;
        _pairFactory = pairFactory;
        _pairManager = pairManager;

        Mediator.Subscribe<PairDataChangedMessage>(this, _ => MarkDirty());
        Mediator.Subscribe<GroupCollectionChangedMessage>(this, _ => MarkDirty());
        Mediator.Subscribe<VisibilityChange>(this, _ => MarkDirty());

        EnsureSnapshot();
    }

    public PairUiSnapshot GetSnapshot()
    {
        EnsureSnapshot();
        lock (_snapshotGate)
        {
            return _snapshot;
        }
    }

    public Pair? GetLastAddedPair()
    {
        EnsureSnapshot();
        lock (_snapshotGate)
        {
            return _lastAddedPair;
        }
    }

    public void ClearLastAddedPair()
    {
        _pairManager.ClearLastAddedUser();
        lock (_snapshotGate)
        {
            _lastAddedPair = null;
        }
    }

    private void MarkDirty()
    {
        lock (_snapshotGate)
        {
            _needsRefresh = true;
        }
    }

    private void EnsureSnapshot()
    {
        bool shouldBuild;
        lock (_snapshotGate)
        {
            shouldBuild = _needsRefresh;
            if (shouldBuild)
            {
                _needsRefresh = false;
            }
        }

        if (!shouldBuild)
        {
            return;
        }

        PairUiSnapshot snapshot;
        Pair? lastAddedPair;
        try
        {
            (snapshot, lastAddedPair) = BuildSnapshot();
        }
        catch
        {
            lock (_snapshotGate)
            {
                _needsRefresh = true;
            }

            throw;
        }

        lock (_snapshotGate)
        {
            _snapshot = snapshot;
            _lastAddedPair = lastAddedPair;
        }

        Mediator.Publish(new PairUiUpdatedMessage(snapshot));
    }

    private (PairUiSnapshot Snapshot, Pair? LastAddedPair) BuildSnapshot()
    {
        var entries = _pairLedger.GetAllEntries();
        var pairByUid = new Dictionary<string, Pair>(StringComparer.Ordinal);

        var directPairsList = new List<Pair>();
        var groupPairsTemp = new Dictionary<GroupFullInfoDto, List<Pair>>();
        var pairsWithGroupsTemp = new Dictionary<Pair, List<GroupFullInfoDto>>();

        foreach (var entry in entries)
        {
            var pair = _pairFactory.Create(entry);
            if (pair is null)
            {
                continue;
            }

            pairByUid[entry.Ident.UserId] = pair;

            if (entry.IsDirectlyPaired)
            {
                directPairsList.Add(pair);
            }

            var uniqueGroups = new HashSet<string>(StringComparer.Ordinal);
            var groupList = new List<GroupFullInfoDto>();
            foreach (var group in entry.Groups)
            {
                if (!uniqueGroups.Add(group.Group.GID))
                {
                    continue;
                }

                if (!groupPairsTemp.TryGetValue(group, out var members))
                {
                    members = new List<Pair>();
                    groupPairsTemp[group] = members;
                }

                members.Add(pair);
                groupList.Add(group);
            }

            pairsWithGroupsTemp[pair] = groupList;
        }

        var allGroupsList = _pairLedger.GetAllSyncshells()
            .Values
            .Select(s => s.GroupFullInfo)
            .ToList();

        foreach (var group in allGroupsList)
        {
            if (!groupPairsTemp.ContainsKey(group))
            {
                groupPairsTemp[group] = new List<Pair>();
            }
        }

        var directPairs = new ReadOnlyCollection<Pair>(directPairsList);

        var groupPairsFinal = new Dictionary<GroupFullInfoDto, IReadOnlyList<Pair>>();
        foreach (var (group, members) in groupPairsTemp)
        {
            groupPairsFinal[group] = new ReadOnlyCollection<Pair>(members);
        }

        var pairsWithGroupsFinal = new Dictionary<Pair, IReadOnlyList<GroupFullInfoDto>>();
        foreach (var (pair, groups) in pairsWithGroupsTemp)
        {
            pairsWithGroupsFinal[pair] = new ReadOnlyCollection<GroupFullInfoDto>(groups);
        }

        var groupsReadOnly = new ReadOnlyCollection<GroupFullInfoDto>(allGroupsList);
        var pairsByUidReadOnly = new ReadOnlyDictionary<string, Pair>(pairByUid);
        var groupsByGidReadOnly = new ReadOnlyDictionary<string, GroupFullInfoDto>(allGroupsList.ToDictionary(g => g.Group.GID, g => g, StringComparer.Ordinal));

        Pair? lastAddedPair = null;
        var lastAdded = _pairManager.GetLastAddedUser();
        if (lastAdded is not null)
        {
            if (!pairByUid.TryGetValue(lastAdded.User.UID, out lastAddedPair))
            {
                var groups = lastAdded.Groups.Keys
                    .Select(gid =>
                    {
                        var result = _pairManager.GetGroup(gid);
                        return result.Success ? result.Value.GroupFullInfo : null;
                    })
                    .Where(g => g is not null)
                    .Cast<GroupFullInfoDto>()
                    .ToList();

                var entry = new PairDisplayEntry(new PairUniqueIdentifier(lastAdded.User.UID), lastAdded, groups, null);
                lastAddedPair = _pairFactory.Create(entry);
            }
        }

        var snapshot = new PairUiSnapshot(
            pairsByUidReadOnly,
            directPairs,
            new ReadOnlyDictionary<GroupFullInfoDto, IReadOnlyList<Pair>>(groupPairsFinal),
            new ReadOnlyDictionary<Pair, IReadOnlyList<GroupFullInfoDto>>(pairsWithGroupsFinal),
            groupsByGidReadOnly,
            groupsReadOnly);

        return (snapshot, lastAddedPair);
    }
}
