using LightlessSync.API.Dto.Group;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Models;
using Microsoft.Extensions.Logging;

namespace LightlessSync.PlayerData.Pairs;

/// <summary>
///     keeps pair info for ui and reapplication
/// </summary>
public sealed class PairLedger : DisposableMediatorSubscriberBase
{
    private readonly PairManager _pairManager;
    private readonly PairHandlerRegistry _registry;
    private readonly ILogger<PairLedger> _logger;
    private readonly object _metricsGate = new();
    private CancellationTokenSource? _ensureMetricsCts;

    public PairLedger(
        ILogger<PairLedger> logger,
        LightlessMediator mediator,
        PairManager pairManager,
        PairHandlerRegistry registry) : base(logger, mediator)
    {
        _pairManager = pairManager;
        _registry = registry;
        _logger = logger;

        Mediator.Subscribe<CutsceneEndMessage>(this, _ => ReapplyAll(forced: true));
        Mediator.Subscribe<GposeEndMessage>(this, _ => ReapplyAll());
        Mediator.Subscribe<PenumbraInitializedMessage>(this, _ => ReapplyAll(forced: true));
        Mediator.Subscribe<FileCacheInitializedMessage>(this, _ => ReapplyAll(forced: true));
        Mediator.Subscribe<DisconnectedMessage>(this, _ => Reset());
        Mediator.Subscribe<ConnectedMessage>(this, _ => ScheduleEnsureMetrics(TimeSpan.FromSeconds(2)));
        Mediator.Subscribe<HubReconnectedMessage>(this, _ => ScheduleEnsureMetrics(TimeSpan.FromSeconds(2)));
        Mediator.Subscribe<DalamudLoginMessage>(this, _ => ScheduleEnsureMetrics(TimeSpan.FromSeconds(2)));
        Mediator.Subscribe<VisibilityChange>(this, _ => EnsureMetricsForVisiblePairs());
    }

    public bool IsPairVisible(PairUniqueIdentifier pairIdent)
    {
        var connectionResult = _pairManager.GetPair(pairIdent.UserId);
        if (!connectionResult.Success)
        {
            return false;
        }

        var connection = connectionResult.Value;
        if (connection.Ident is null)
        {
            return false;
        }

        return _registry.IsIdentVisible(connection.Ident);
    }

    public IPairHandlerAdapter? GetHandler(PairUniqueIdentifier pairIdent)
    {
        var connectionResult = _pairManager.GetPair(pairIdent.UserId);
        if (!connectionResult.Success)
        {
            return null;
        }

        var connection = connectionResult.Value;
        if (connection.Ident is null)
        {
            return null;
        }

        return _registry.TryGetHandler(connection.Ident, out var handler) ? handler : null;
    }

    public IReadOnlyList<PairConnection> GetVisiblePairs()
    {
        return _pairManager.GetAllPairs()
            .Select(kv => kv.Value)
            .Where(connection => connection.Ident is not null && _registry.IsIdentVisible(connection.Ident))
            .ToList();
    }

    public IReadOnlyList<GroupFullInfoDto> GetAllGroupInfos()
    {
        return _pairManager.GetAllGroups()
            .Select(kv => kv.Value.GroupFullInfo)
            .ToList();
    }

    public IReadOnlyDictionary<string, Syncshell> GetAllSyncshells()
    {
        return _pairManager.GetAllGroups();
    }

    public void ReapplyAll(bool forced = false)
    {
        if (_logger.IsEnabled(LogLevel.Trace))
        {
            _logger.LogTrace("Reapplying cached data for all handlers (forced: {Forced})", forced);
        }

        _registry.ReapplyAll(forced);
    }

    public void ReapplyPair(PairUniqueIdentifier pairIdent, bool forced = false)
    {
        var connectionResult = _pairManager.GetPair(pairIdent.UserId);
        if (!connectionResult.Success)
        {
            return;
        }

        var connection = connectionResult.Value;
        if (connection.Ident is null)
        {
            return;
        }

        var result = _registry.ApplyLastReceivedData(pairIdent, connection.Ident, forced);
        if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
        {
            _logger.LogDebug("Failed to reapply data for {UserId}: {Error}", pairIdent.UserId, result.Error);
        }
    }

    private void Reset()
    {
        if (_logger.IsEnabled(LogLevel.Trace))
        {
            _logger.LogTrace("Resetting pair handlers after disconnect.");
        }

        CancelScheduledMetrics();
    }

    public IReadOnlyList<PairDisplayEntry> GetAllEntries()
    {
        var groups = _pairManager.GetAllGroups();
        var list = new List<PairDisplayEntry>();
        foreach (var (userId, connection) in _pairManager.GetAllPairs())
        {
            var ident = new PairUniqueIdentifier(userId);
            IPairHandlerAdapter? handler = null;
            if (connection.Ident is not null)
            {
                _registry.TryGetHandler(connection.Ident, out handler);
            }

            var groupInfos = connection.Groups.Keys
                .Select(gid =>
                {
                    if (groups.TryGetValue(gid, out var shell))
                    {
                        return shell.GroupFullInfo;
                    }
                    return null;
                })
                .Where(dto => dto is not null)
                .Cast<GroupFullInfoDto>()
                .ToList();

            list.Add(new PairDisplayEntry(ident, connection, groupInfos, handler));
        }

        return list;
    }

    public bool TryGetEntry(PairUniqueIdentifier ident, out PairDisplayEntry? entry)
    {
        entry = null;
        var connectionResult = _pairManager.GetPair(ident.UserId);
        if (!connectionResult.Success)
        {
            return false;
        }

        var connection = connectionResult.Value;
        var groups = connection.Groups.Keys
            .Select(gid =>
            {
                var groupResult = _pairManager.GetGroup(gid);
                return groupResult.Success ? groupResult.Value.GroupFullInfo : null;
            })
            .Where(dto => dto is not null)
            .Cast<GroupFullInfoDto>()
            .ToList();

        IPairHandlerAdapter? handler = null;
        if (connection.Ident is not null)
        {
            _registry.TryGetHandler(connection.Ident, out handler);
        }

        entry = new PairDisplayEntry(ident, connection, groups, handler);
        return true;
    }

    private void ScheduleEnsureMetrics(TimeSpan? delay = null)
    {
        lock (_metricsGate)
        {
            _ensureMetricsCts?.Cancel();
            var cts = new CancellationTokenSource();
            _ensureMetricsCts = cts;
            _ = Task.Run(async () =>
            {
                try
                {
                    if (delay is { } d && d > TimeSpan.Zero)
                    {
                        await Task.Delay(d, cts.Token).ConfigureAwait(false);
                    }

                    EnsureMetricsForVisiblePairs();
                }
                catch (OperationCanceledException)
                {
                    // ignored
                }
                finally
                {
                    lock (_metricsGate)
                    {
                        if (_ensureMetricsCts == cts)
                        {
                            _ensureMetricsCts = null;
                        }
                    }

                    cts.Dispose();
                }
            });
        }
    }

    private void CancelScheduledMetrics()
    {
        lock (_metricsGate)
        {
            _ensureMetricsCts?.Cancel();
            _ensureMetricsCts = null;
        }
    }

    private void EnsureMetricsForVisiblePairs()
    {
        var handlers = _registry.GetHandlerSnapshot();
        foreach (var handler in handlers)
        {
            if (!handler.IsVisible)
            {
                continue;
            }

            if (handler.LastReceivedCharacterData is null)
            {
                continue;
            }

            if (handler.LastAppliedApproximateVRAMBytes >= 0
                && handler.LastAppliedDataTris >= 0
                && handler.LastAppliedApproximateEffectiveVRAMBytes >= 0)
            {
                continue;
            }

            if (handler.FetchPerformanceMetricsFromCache())
            {
                continue;
            }

            try
            {
                handler.ApplyLastReceivedData(forced: true);
            }
            catch (Exception ex)
            {
                if (_logger.IsEnabled(LogLevel.Debug))
                {
                    _logger.LogDebug(ex, "Failed to ensure performance metrics for {Ident}", handler.Ident);
                }
            }
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            CancelScheduledMetrics();
        }

        base.Dispose(disposing);
    }
}
