using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text.SeStringHandling;
using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto.User;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI;
using LightlessSync.WebAPI;
using Microsoft.Extensions.Logging;

namespace LightlessSync.PlayerData.Pairs;

/// <summary>
///     ui wrapper around a pair connection
/// </summary>
public class Pair
{
    private readonly PairLedger _pairLedger;
    private readonly ILogger<Pair> _logger;
    private readonly LightlessMediator _mediator;
    private readonly ServerConfigurationManager _serverConfigurationManager;
    private readonly Lazy<ApiController> _apiController;

    private const int _lightlessPrefixColor = 708;

    public Pair(
        ILogger<Pair> logger,
        UserFullPairDto userPair,
        PairLedger pairLedger,
        LightlessMediator mediator,
        ServerConfigurationManager serverConfigurationManager,
        Lazy<ApiController> apiController)
    {
        _logger = logger;
        UserPair = userPair;
        _pairLedger = pairLedger;
        _mediator = mediator;
        _serverConfigurationManager = serverConfigurationManager;
        _apiController = apiController;
    }

    private PairUniqueIdentifier PairIdent => UniqueIdent;

    private IPairHandlerAdapter? TryGetHandler()
    {
        return _pairLedger.GetHandler(PairIdent);
    }

    private PairConnection? TryGetConnection()
    {
        return _pairLedger.TryGetEntry(PairIdent, out var entry) && entry is not null
            ? entry.Connection
            : null;
    }

    public bool HasCachedPlayer => TryGetHandler() is not null;
    public IndividualPairStatus IndividualPairStatus => UserPair.IndividualPairStatus;
    public bool IsDirectlyPaired => IndividualPairStatus != IndividualPairStatus.None;
    public bool IsOneSidedPair => IndividualPairStatus == IndividualPairStatus.OneSided;

    public bool IsOnline => TryGetConnection()?.IsOnline ?? false;

    public bool IsPaired => IndividualPairStatus == IndividualPairStatus.Bidirectional || UserPair.Groups.Any();
    public bool IsPaused => UserPair.OwnPermissions.IsPaused();
    public bool IsVisible => _pairLedger.IsPairVisible(PairIdent);
    public CharacterData? LastReceivedCharacterData => TryGetHandler()?.LastReceivedCharacterData;
    public string? PlayerName => TryGetHandler()?.PlayerName ?? UserPair.User.AliasOrUID;
    public long LastAppliedDataBytes => TryGetHandler()?.LastAppliedDataBytes ?? -1;
    public long LastAppliedDataTris => TryGetHandler()?.LastAppliedDataTris ?? -1;
    public long LastAppliedApproximateVRAMBytes => TryGetHandler()?.LastAppliedApproximateVRAMBytes ?? -1;
    public long LastAppliedApproximateEffectiveVRAMBytes => TryGetHandler()?.LastAppliedApproximateEffectiveVRAMBytes ?? -1;
    public string Ident => TryGetHandler()?.Ident ?? TryGetConnection()?.Ident ?? string.Empty;
    public uint PlayerCharacterId => TryGetHandler()?.PlayerCharacterId ?? uint.MaxValue;
    public PairUniqueIdentifier UniqueIdent => new(UserData.UID);

    public UserData UserData => UserPair.User;

    public UserFullPairDto UserPair { get; set; }

    public void AddContextMenu(IMenuOpenedArgs args)
    {
        var handler = TryGetHandler();
        if (handler is null)
        {
            return;
        }

        if (args.Target is not MenuTargetDefault target || target.TargetObjectId != handler.PlayerCharacterId)
        {
            return;
        }

        if (!IsPaused)
        {
            UiSharedService.AddContextMenuItem(args, name: "Open Profile", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () =>
            {
                _mediator.Publish(new ProfileOpenStandaloneMessage(this));
                return Task.CompletedTask;
            });

            UiSharedService.AddContextMenuItem(args, name: "Reapply last data", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () =>
            {
                ApplyLastReceivedData(forced: true);
                return Task.CompletedTask;
            });
        }

        UiSharedService.AddContextMenuItem(args, name: "Change Permissions", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () =>
        {
            _mediator.Publish(new OpenPermissionWindow(this));
            return Task.CompletedTask;
        });

        if (IsPaused)
        {
            UiSharedService.AddContextMenuItem(args, name: "Toggle Unpause State", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () =>
            {
                _ = _apiController.Value.UnpauseAsync(UserData);
                return Task.CompletedTask;
            });
        }
        else
        {
            UiSharedService.AddContextMenuItem(args, name: "Toggle Pause State", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () =>
            {
                _ = _apiController.Value.PauseAsync(UserData);
                return Task.CompletedTask;
            });
        }

        UiSharedService.AddContextMenuItem(args, name: "Cycle Pause State", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () =>
        {
            TriggerCyclePause();
            return Task.CompletedTask;
        });
    }

    public void ApplyData(OnlineUserCharaDataDto data)
    {
        _logger.LogTrace("Character data received for {Uid}; handler will process via registry.", UserData.UID);
    }

    private void TriggerCyclePause()
    {
        _ = _apiController.Value.CyclePauseAsync(this);
    }

    public void ApplyLastReceivedData(bool forced = false)
    {
        var handler = TryGetHandler();
        if (handler is null)
        {
            _logger.LogTrace("ApplyLastReceivedData skipped for {Uid}: handler missing.", UserData.UID);
            return;
        }

        handler.ApplyLastReceivedData(forced);
    }

    public void CreateCachedPlayer(OnlineUserIdentDto? dto = null)
    {
        var handler = TryGetHandler();
        if (handler is null)
        {
            _logger.LogTrace("CreateCachedPlayer skipped for {Uid}: handler unavailable.", UserData.UID);
            return;
        }

        if (!handler.Initialized)
        {
            handler.Initialize();
        }
    }

    public string? GetNote()
    {
        return _serverConfigurationManager.GetNoteForUid(UserData.UID);
    }

    public string GetPlayerNameHash()
    {
        return TryGetHandler()?.PlayerNameHash ?? string.Empty;
    }

    public bool HasAnyConnection()
    {
        return UserPair.Groups.Any() || UserPair.IndividualPairStatus != IndividualPairStatus.None;
    }

    public void MarkOffline(bool wait = true)
    {
        _logger.LogTrace("MarkOffline invoked for {Uid} (wait: {Wait}). New registry handles handler disposal.", UserData.UID, wait);
    }

    public void SetNote(string note)
    {
        _serverConfigurationManager.SetNoteForUid(UserData.UID, note);
    }

    internal void SetIsUploading()
    {
        var handler = TryGetHandler();
        if (handler is null)
        {
            return;
        }

        handler.SetUploading(true);
    }

    public PairDebugInfo GetDebugInfo()
    {
        var handler = TryGetHandler();
        if (handler is null)
            return PairDebugInfo.Empty;

        var now = DateTime.UtcNow;
        var dueAt = handler.VisibilityEvictionDueAtUtc;
        var remainingSeconds = dueAt.HasValue
            ? Math.Max(0, (dueAt.Value - now).TotalSeconds)
            : (double?)null;

        return new PairDebugInfo(
            true,
            handler.Initialized,
            handler.IsVisible,
            handler.ScheduledForDeletion,
            handler.LastDataReceivedAt,
            handler.LastApplyAttemptAt,
            handler.LastSuccessfulApplyAt,
            handler.InvisibleSinceUtc,
            handler.VisibilityEvictionDueAtUtc,
            remainingSeconds,
            handler.LastFailureReason,
            handler.LastBlockingConditions,
            handler.IsApplying,
            handler.IsDownloading,
            handler.PendingDownloadCount,
            handler.ForbiddenDownloadCount,
            handler.PendingModReapply,
            handler.ModApplyDeferred,
            handler.MissingCriticalMods,
            handler.MissingNonCriticalMods,
            handler.MissingForbiddenMods);
    }
}
