using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Services;
using Microsoft.Extensions.Logging;

namespace LightlessSync.Services;

public sealed class PairRequestService : DisposableMediatorSubscriberBase
{
    private readonly DalamudUtilService _dalamudUtil;
    private readonly PairUiService _pairUiService;
    private readonly Lazy<WebAPI.ApiController> _apiController;
    private readonly Lock _syncRoot = new();
    private readonly List<PairRequestEntry> _requests = [];
    private readonly Dictionary<string, string> _displayNameCache = new(StringComparer.Ordinal);

    private static readonly TimeSpan _expiration = TimeSpan.FromMinutes(5);

    public PairRequestService(
        ILogger<PairRequestService> logger, 
        LightlessMediator mediator, 
        DalamudUtilService dalamudUtil, 
        PairUiService pairUiService, 
        Lazy<WebAPI.ApiController> apiController)
        : base(logger, mediator)
    {
        _dalamudUtil = dalamudUtil;
        _pairUiService = pairUiService;
        _apiController = apiController;

        Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, _ =>
        {
            bool removed;
            lock (_syncRoot)
            {
                removed = CleanupExpiredUnsafe();
            }

            if (removed)
            {
                Mediator.Publish(new PairRequestsUpdatedMessage());
            }
        });
    }

    public PairRequestDisplay RegisterIncomingRequest(string hashedCid, string messageTemplate)
    {
        if (string.IsNullOrWhiteSpace(hashedCid))
        {
            hashedCid = string.Empty;
        }

        messageTemplate ??= string.Empty;

        PairRequestEntry entry = new(hashedCid, messageTemplate, DateTime.UtcNow);
        lock (_syncRoot)
        {
            CleanupExpiredUnsafe();
            var index = _requests.FindIndex(r => string.Equals(r.HashedCid, hashedCid, StringComparison.Ordinal));
            if (index >= 0)
            {
                _requests[index] = entry;
            }
            else
            {
                _requests.Add(entry);
            }
        }

        var display = _dalamudUtil.IsOnFrameworkThread
            ? ToDisplay(entry)
            : _dalamudUtil.RunOnFrameworkThread(() => ToDisplay(entry)).GetAwaiter().GetResult();

        Mediator.Publish(new PairRequestsUpdatedMessage());
        return display;
    }

    public IReadOnlyList<PairRequestDisplay> GetActiveRequests()
    {
        List<PairRequestEntry> entries;
        lock (_syncRoot)
        {
            CleanupExpiredUnsafe();
            entries = _requests
                .OrderByDescending(r => r.ReceivedAt)
                .ToList();
        }

        return _dalamudUtil.IsOnFrameworkThread
            ? entries.Select(ToDisplay).ToList()
            : _dalamudUtil.RunOnFrameworkThread(() => entries.Select(ToDisplay).ToList()).GetAwaiter().GetResult();
    }

    public bool RemoveRequest(string hashedCid)
    {
        bool removed;
        lock (_syncRoot)
        {
            removed = _requests.RemoveAll(r => string.Equals(r.HashedCid, hashedCid, StringComparison.Ordinal)) > 0;
            if (removed)
            {
                _displayNameCache.Remove(hashedCid);
            }
        }

        if (removed)
        {
            Mediator.Publish(new PairRequestsUpdatedMessage());
        }

        return removed;
    }

    public bool HasPendingRequests()
    {
        lock (_syncRoot)
        {
            CleanupExpiredUnsafe();
            return _requests.Count > 0;
        }
    }

    private PairRequestDisplay ToDisplay(PairRequestEntry entry)
    {
        var displayName = ResolveDisplayName(entry.HashedCid);
        var message = FormatMessage(entry.MessageTemplate, displayName);
        return new PairRequestDisplay(entry.HashedCid, displayName, message, entry.ReceivedAt);
    }

    private string ResolveDisplayName(string hashedCid)
    {
        if (string.IsNullOrWhiteSpace(hashedCid))
        {
            return string.Empty;
        }

        if (TryGetCachedDisplayName(hashedCid, out var cached))
        {
            return cached;
        }

        var resolved = ResolveDisplayNameInternal(hashedCid);
        if (!string.IsNullOrWhiteSpace(resolved))
        {
            CacheDisplayName(hashedCid, resolved);
            return resolved;
        }

        return string.Empty;
    }

    private string ResolveDisplayNameInternal(string hashedCid)
    {
        var (name, address) = _dalamudUtil.FindPlayerByNameHash(hashedCid);
        if (!string.IsNullOrWhiteSpace(name))
        {
            var worldName = _dalamudUtil.GetWorldNameFromPlayerAddress(address);
            return !string.IsNullOrWhiteSpace(worldName)
                ? $"{name} @ {worldName}"
                : name;
        }

        var snapshot = _pairUiService.GetSnapshot();
        var pair = snapshot.PairsByUid.Values
            .Where(p => !string.IsNullOrEmpty(p.GetPlayerNameHash()))
            .FirstOrDefault(p => string.Equals(p.Ident, hashedCid, StringComparison.Ordinal));

        if (pair != null)
        {
            if (!string.IsNullOrWhiteSpace(pair.PlayerName))
            {
                return pair.PlayerName;
            }

            if (!string.IsNullOrWhiteSpace(pair.UserData.AliasOrUID))
            {
                return pair.UserData.AliasOrUID;
            }
        }

        return string.Empty;
    }

    private static string FormatMessage(string template, string displayName)
    {
        var safeName = string.IsNullOrWhiteSpace(displayName) ? "Someone" : displayName;
        template ??= string.Empty;
        const string placeholder = "{DisplayName}";

        if (!string.IsNullOrEmpty(template) && template.Contains(placeholder, StringComparison.Ordinal))
        {
            return template.Replace(placeholder, safeName, StringComparison.Ordinal);
        }

        if (!string.IsNullOrWhiteSpace(template))
        {
            return $"{safeName}: {template}";
        }

        return $"{safeName} sent you a pair request.";
    }

    private bool CleanupExpiredUnsafe()
    {
        if (_requests.Count == 0)
        {
            return false;
        }

        var now = DateTime.UtcNow;
        var removedAny = false;
        for (var i = _requests.Count - 1; i >= 0; i--)
        {
            var entry = _requests[i];
            if (now - entry.ReceivedAt <= _expiration)
            {
                continue;
            }

            _displayNameCache.Remove(entry.HashedCid);
            _requests.RemoveAt(i);
            removedAny = true;
        }

        return removedAny;
    }

    public void AcceptPairRequest(string hashedCid, string displayName)
    {
        _ = Task.Run(async () =>
        {
            try
            {
                await _apiController.Value.TryPairWithContentId(hashedCid).ConfigureAwait(false);
                RemoveRequest(hashedCid);

                var displayText = string.IsNullOrEmpty(displayName) ? hashedCid : displayName;
                Mediator.Publish(new NotificationMessage(
                    "Pair request accepted",
                    $"Sent a pair request back to {displayText}.",
                    NotificationType.Info,
                    TimeSpan.FromSeconds(3)));
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Failed to accept pair request for {HashedCid}", hashedCid);
                Mediator.Publish(new NotificationMessage(
                    "Failed to Accept Pair Request",
                    ex.Message,
                    NotificationType.Error,
                    TimeSpan.FromSeconds(5)));
            }
        });
    }

    public void DeclinePairRequest(string hashedCid, string displayName)
    {
        RemoveRequest(hashedCid);
        Mediator.Publish(new NotificationMessage("Pair request declined",
            "Declined " + displayName + "'s pending pair request.",
            NotificationType.Info,
            TimeSpan.FromSeconds(3)));
        Logger.LogDebug("Declined pair request from {HashedCid}", hashedCid);
    }

    private record struct PairRequestEntry(string HashedCid, string MessageTemplate, DateTime ReceivedAt);

    public readonly record struct PairRequestDisplay(string HashedCid, string DisplayName, string Message, DateTime ReceivedAt);

    private bool TryGetCachedDisplayName(string hashedCid, out string displayName)
    {
        lock (_syncRoot)
        {
            if (!string.IsNullOrWhiteSpace(hashedCid) && _displayNameCache.TryGetValue(hashedCid, out var cached))
            {
                displayName = cached;
                return true;
            }
        }

        displayName = string.Empty;
        return false;
    }

    private void CacheDisplayName(string hashedCid, string displayName)
    {
        if (string.IsNullOrWhiteSpace(hashedCid) || string.IsNullOrWhiteSpace(displayName) || string.Equals(hashedCid, displayName, StringComparison.Ordinal))
        {
            return;
        }

        lock (_syncRoot)
        {
            _displayNameCache[hashedCid] = displayName;
        }
    }
}
