using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using LightlessSync.LightlessConfiguration;
using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.Services.LightFinder;
using LightlessSync.Services.Mediator;
using LightlessSync.UI;
using LightlessSync.UI.Services;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace LightlessSync.Services;

internal class ContextMenuService : IHostedService
{
    private readonly IContextMenu _contextMenu;
    private readonly IChatGui _chatGui;
    private readonly IDalamudPluginInterface _pluginInterface;
    private readonly IDataManager _gameData;
    private readonly ILogger<ContextMenuService> _logger;
    private readonly DalamudUtilService _dalamudUtil;
    private readonly IClientState _clientState;
    private readonly PairUiService _pairUiService;
    private readonly PairRequestService _pairRequestService;
    private readonly ApiController _apiController;
    private readonly IObjectTable _objectTable;
    private readonly LightlessConfigService _configService;
    private readonly NotificationService _lightlessNotification;
    private readonly LightFinderScannerService _broadcastScannerService;
    private readonly LightFinderService _broadcastService;
    private readonly LightlessProfileManager _lightlessProfileManager;
    private readonly LightlessMediator _mediator;

    private const int _lightlessPrefixColor = 708;

    public ContextMenuService(
        IContextMenu contextMenu,
        IDalamudPluginInterface pluginInterface,
        IDataManager gameData,
        ILogger<ContextMenuService> logger,
        DalamudUtilService dalamudUtil,
        ApiController apiController,
        IObjectTable objectTable,
        LightlessConfigService configService,
        PairRequestService pairRequestService,
        PairUiService pairUiService,
        IClientState clientState,
        LightFinderScannerService broadcastScannerService,
        LightFinderService broadcastService,
        LightlessProfileManager lightlessProfileManager,
        LightlessMediator mediator,
        IChatGui chatGui,
        NotificationService lightlessNotification)
    {
        _contextMenu = contextMenu;
        _pluginInterface = pluginInterface;
        _gameData = gameData;
        _logger = logger;
        _dalamudUtil = dalamudUtil;
        _apiController = apiController;
        _objectTable = objectTable;
        _configService = configService;
        _pairUiService = pairUiService;
        _pairRequestService = pairRequestService;
        _clientState = clientState;
        _broadcastScannerService = broadcastScannerService;
        _broadcastService = broadcastService;
        _lightlessProfileManager = lightlessProfileManager;
        _mediator = mediator;
        _chatGui = chatGui;
        _lightlessNotification = lightlessNotification;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _contextMenu.OnMenuOpened += OnMenuOpened;
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _contextMenu.OnMenuOpened -= OnMenuOpened;
        return Task.CompletedTask;
    }

    public void Enable()
    {
        _contextMenu.OnMenuOpened += OnMenuOpened;
        _logger.LogDebug("Context menu enabled.");
    }

    public void Disable()
    {
        _contextMenu.OnMenuOpened -= OnMenuOpened;
        _logger.LogDebug("Context menu disabled.");
    }

    private void OnMenuOpened(IMenuOpenedArgs args)
    {
        if (!_pluginInterface.UiBuilder.ShouldModifyUi)
            return;

        if (!_configService.Current.EnableRightClickMenus)
        {
            _logger.LogTrace("Right-click menus are disabled in configuration.");
            return;
        }

        if (args.AddonName != null)
        {
            var addonName = args.AddonName;
            _logger.LogTrace("Context menu addon name: {AddonName}", addonName);
            return;
        }

        if (args.Target is not MenuTargetDefault target)
        {
            _logger.LogTrace("Context menu target is not MenuTargetDefault.");
            return;
        }

        _logger.LogTrace("Context menu opened for target: {Target}", target.TargetName ?? "null");

        if (string.IsNullOrEmpty(target.TargetName) || target.TargetObjectId == 0 || target.TargetHomeWorld.RowId == 0)
        {
            _logger.LogTrace("Context menu target has invalid data: Name='{TargetName}', ObjectId={TargetObjectId}, HomeWorldId={TargetHomeWorldId}", target.TargetName, target.TargetObjectId, target.TargetHomeWorld.RowId);
            return;
        }

        IPlayerCharacter? targetData = GetPlayerFromObjectTable(target);
        if (targetData == null || targetData.Address == nint.Zero || _objectTable.LocalPlayer == null)
        {
            _logger.LogTrace("Target player {TargetName}@{World} not found in object table.", target.TargetName, target.TargetHomeWorld.RowId);
            return;
        }

        var snapshot = _pairUiService.GetSnapshot();
        var pair = snapshot.PairsByUid.Values.FirstOrDefault(p =>
            p.PlayerCharacterId != uint.MaxValue &&
            p.PlayerCharacterId == target.TargetObjectId);

        if (pair is not null)
        {
            _logger.LogTrace("Target player {TargetName}@{World} is already paired, adding existing pair context menu.", target.TargetName, target.TargetHomeWorld.RowId);

            pair.AddContextMenu(args);
            if (!pair.IsDirectlyPaired)
            {
                _logger.LogTrace("Target player {TargetName}@{World} is not directly paired, add direct pair menu item", target.TargetName, target.TargetHomeWorld.RowId);
                AddDirectPairMenuItem(args);
            }

            return;
        }

        _logger.LogTrace("Target player {TargetName}@{World} is not paired, adding direct pair request context menu.", target.TargetName, target.TargetHomeWorld.RowId);

        //Check if user is directly paired or is own.
        if (VisibleUserIds.Any(u => u == target.TargetObjectId) || _objectTable.LocalPlayer?.GameObjectId == target.TargetObjectId || !_configService.Current.EnableRightClickMenus)
        {
            _logger.LogTrace("Target player {TargetName}@{World} is already paired or is self, or right-click menus are disabled.", target.TargetName, target.TargetHomeWorld.RowId);
            return;
        }

        if (_clientState.IsPvPExcludingDen || _clientState.IsGPosing)
        {
            _logger.LogTrace("Cannot send pair request to {TargetName}@{World} while in PvP or GPose.", target.TargetName, target.TargetHomeWorld.RowId);
            return;
        }
        
        if (!IsWorldValid(target.TargetHomeWorld.RowId))
        {
            _logger.LogTrace("Target player {TargetName}@{World} is on an invalid world.", target.TargetName, target.TargetHomeWorld.RowId);
            return;
        }

        string? targetHashedCid = null;
        if (_broadcastService.IsBroadcasting)
        {
            targetHashedCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address);
        }

        if (!string.IsNullOrEmpty(targetHashedCid) && CanOpenLightfinderProfile(targetHashedCid))
        {
            var hashedCid = targetHashedCid;
            UiSharedService.AddContextMenuItem(args, name: "Open Lightless Profile", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () => HandleLightfinderProfileSelection(hashedCid));
        }

        AddDirectPairMenuItem(args);
    }

    private void AddDirectPairMenuItem(IMenuOpenedArgs args)
    {
        UiSharedService.AddContextMenuItem(
            args,
            name: "Send Direct Pair Request",
            prefixChar: 'L',
            colorMenuItem: _lightlessPrefixColor,
            onClick: () => HandleSelection(args));
    }

    private HashSet<ulong> VisibleUserIds =>
        [.. _pairUiService.GetSnapshot().PairsByUid.Values
            .Where(p => p.IsVisible && p.PlayerCharacterId != uint.MaxValue)
            .Select(p => (ulong)p.PlayerCharacterId)];

    private void NotifyInChat(string message, NotificationType type = NotificationType.Info)
    {
        if (!_configService.Current.UseLightlessNotifications || (_configService.Current.LightlessPairRequestNotification == NotificationLocation.Chat || _configService.Current.LightlessPairRequestNotification == NotificationLocation.ChatAndLightlessUi))
        {
            var chatMsg = $"[Lightless] {message}";
            if (type == NotificationType.Error)
                _chatGui.PrintError(chatMsg);
            else
                _chatGui.Print(chatMsg);
        }
    }

    private async Task HandleSelection(IMenuArgs args)
    {
        if (args.Target is not MenuTargetDefault target)
            return;
        
        if (!target.TargetHomeWorld.IsValid || !IsWorldValid(target.TargetHomeWorld.RowId))
            return;

        try
        {
            IPlayerCharacter? targetData = GetPlayerFromObjectTable(target);

            if (targetData == null || targetData.Address == nint.Zero)
            {
                _logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, target.TargetHomeWorld.Value.Name);
                return;
            }

            var senderCid = _dalamudUtil.GetCID().ToString().GetHash256();
            var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address);

            _logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid);
            await _apiController.TryPairWithContentId(receiverCid).ConfigureAwait(false);
            if (!string.IsNullOrWhiteSpace(receiverCid))
            {
                _pairRequestService.RemoveRequest(receiverCid);
            }

            // Notify in chat when NotificationService is disabled
            NotifyInChat($"Pair request sent to {target.TargetName}@{target.TargetHomeWorld.Value.Name}.", NotificationType.Info);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error sending pair request.");
        }
    }

    private async Task HandleLightfinderProfileSelection(string hashedCid)
    {
        if (string.IsNullOrWhiteSpace(hashedCid))
            return;

        if (!_broadcastService.IsBroadcasting)
        {
            Notify("Lightfinder inactive", "Enable Lightfinder to open broadcaster profiles.", NotificationType.Warning, 6);
            return;
        }

        if (!_broadcastScannerService.BroadcastCache.TryGetValue(hashedCid, out var entry) || !entry.IsBroadcasting || entry.ExpiryTime <= DateTime.UtcNow)
        {
            Notify("Broadcaster unavailable", "That player is not currently using Lightfinder.", NotificationType.Info, 5);
            return;
        }

        var result = await _lightlessProfileManager.GetLightfinderProfileAsync(hashedCid).ConfigureAwait(false);
        if (result == null)
        {
            Notify("Profile unavailable", "Unable to load Lightless profile for that player.", NotificationType.Error, 6);
            return;
        }

        _mediator.Publish(new OpenLightfinderProfileMessage(result.Value.User, result.Value.ProfileData, hashedCid));
    }

    private void Notify(string title, string message, NotificationType type, double durationSeconds)
    {
        _mediator.Publish(new NotificationMessage(title, message, type, TimeSpan.FromSeconds(durationSeconds)));
    }

    private bool CanOpenLightfinderProfile(string hashedCid)
    {
        if (!_broadcastService.IsBroadcasting)
            return false;

        if (!_broadcastScannerService.BroadcastCache.TryGetValue(hashedCid, out var entry))
            return false;

        return entry.IsBroadcasting && entry.ExpiryTime > DateTime.UtcNow;
    }

    private IPlayerCharacter? GetPlayerFromObjectTable(MenuTargetDefault target)
    {
        return _objectTable
            .OfType<IPlayerCharacter>()
            .FirstOrDefault(p =>
                string.Equals(p.Name.TextValue, target.TargetName, StringComparison.OrdinalIgnoreCase) &&
                p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
    }

    private bool IsWorldValid(uint worldId)
    {
        return _dalamudUtil.WorldData.Value.ContainsKey((ushort)worldId);
    }
}
