using Dalamud.Game.Gui.Dtr;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin.Services;
using LightlessSync.LightlessConfiguration;
using LightlessSync.LightlessConfiguration.Configurations;
using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using LightlessSync.WebAPI.SignalR.Utils;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Runtime.InteropServices;
using System.Text;
using LightlessSync.UI.Services;
using static LightlessSync.Services.PairRequestService;
using LightlessSync.Services.LightFinder;

namespace LightlessSync.UI;

public sealed class DtrEntry : IDisposable, IHostedService
{
    private static readonly TimeSpan _localHashedCidCacheDuration = TimeSpan.FromMinutes(2);
    private static readonly TimeSpan _localHashedCidErrorCooldown = TimeSpan.FromMinutes(1);

    private readonly ApiController _apiController;
    private readonly ServerConfigurationManager _serverManager;
    private readonly CancellationTokenSource _cancellationTokenSource = new();
    private readonly ConfigurationServiceBase<LightlessConfig> _configService;
    private readonly IDtrBar _dtrBar;
    private readonly Lazy<IDtrBarEntry> _statusEntry;
    private readonly Lazy<IDtrBarEntry> _lightfinderEntry;
    private readonly ILogger<DtrEntry> _logger;
    private readonly LightFinderService _broadcastService;
    private readonly LightFinderScannerService _broadcastScannerService;
    private readonly LightlessMediator _lightlessMediator;
    private readonly PairUiService _pairUiService;
    private readonly PairRequestService _pairRequestService;
    private readonly DalamudUtilService _dalamudUtilService;
    private Task? _runTask;
    private string? _statusText;
    private string? _statusTooltip;
    private Colors _statusColors;
    private string? _lightfinderText;
    private string? _lightfinderTooltip;
    private Colors _lightfinderColors;
    private readonly object _localHashedCidLock = new();
    private string? _localHashedCid;
    private DateTime _localHashedCidFetchedAt = DateTime.MinValue;
    private DateTime _localHashedCidNextErrorLog = DateTime.MinValue;
    private DateTime _pairRequestNextErrorLog = DateTime.MinValue;
    private int _localHashedCidRefreshActive;

    public DtrEntry(
        ILogger<DtrEntry> logger,
        IDtrBar dtrBar,
        ConfigurationServiceBase<LightlessConfig> configService,
        LightlessMediator lightlessMediator,
        PairUiService pairUiService,
        PairRequestService pairRequestService,
        ApiController apiController,
        ServerConfigurationManager serverManager,
        LightFinderService broadcastService,
        LightFinderScannerService broadcastScannerService,
        DalamudUtilService dalamudUtilService)
    {
        _logger = logger;
        _dtrBar = dtrBar;
        _statusEntry = new(CreateStatusEntry);
        _lightfinderEntry = new(CreateLightfinderEntry);
        _configService = configService;
        _lightlessMediator = lightlessMediator;
        _pairUiService = pairUiService;
        _pairRequestService = pairRequestService;
        _apiController = apiController;
        _serverManager = serverManager;
        _broadcastService = broadcastService;
        _broadcastScannerService = broadcastScannerService;
        _dalamudUtilService = dalamudUtilService;
    }

    public void Dispose()
    {
        if (_statusEntry.IsValueCreated)
        {
            _logger.LogDebug("Disposing DtrEntry");
            Clear();
            _statusEntry.Value.Remove();
        }
        if (_lightfinderEntry.IsValueCreated)
            _lightfinderEntry.Value.Remove();
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting DtrEntry");
        _runTask = Task.Run(RunAsync, _cancellationTokenSource.Token);
        _logger.LogInformation("Started DtrEntry");
        return Task.CompletedTask;
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
        try
        {
            await _runTask!.ConfigureAwait(false);
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Lightfinder operation was canceled.");
        }
        finally
        {
            _cancellationTokenSource.Dispose();
        }
    }

    private void Clear()
    {
        HideStatusEntry();
        HideLightfinderEntry();
    }

    private void HideStatusEntry()
    {
        if (_statusEntry.IsValueCreated && _statusEntry.Value.Shown)
        {
            _logger.LogInformation("Hiding status entry");
            _statusEntry.Value.Shown = false;
        }

        _statusText = null;
        _statusTooltip = null;
        _statusColors = default;
    }

    private void HideLightfinderEntry()
    {
        if (_lightfinderEntry.IsValueCreated && _lightfinderEntry.Value.Shown)
        {
            _logger.LogInformation("Hiding Lightfinder entry");
            _lightfinderEntry.Value.Shown = false;
        }

        _lightfinderText = null;
        _lightfinderTooltip = null;
        _lightfinderColors = default;
    }

    private IDtrBarEntry CreateStatusEntry()
    {
        _logger.LogTrace("Creating status DtrBar entry");
        var entry = _dtrBar.Get("Lightless Sync");
        entry.OnClick = interactionEvent => OnStatusEntryClick(interactionEvent);

        return entry;
    }

    private IDtrBarEntry CreateLightfinderEntry()
    {
        _logger.LogTrace("Creating Lightfinder DtrBar entry");
        var entry = _dtrBar.Get("Lightfinder");
        entry.OnClick = interactionEvent => OnLightfinderEntryClick(interactionEvent);
        return entry;
    }

    private void OnStatusEntryClick(DtrInteractionEvent interactionEvent)
    {
        if (interactionEvent.ClickType.Equals(MouseClickType.Left))
        {
            if (interactionEvent.ModifierKeys.HasFlag(ClickModifierKeys.Shift))
            {
                _lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi)));
            }
            else
            {
                _lightlessMediator.Publish(new UiToggleMessage(typeof(CompactUi)));
            }
            return;
        }

        if (interactionEvent.ClickType.Equals(MouseClickType.Right))
        {
            bool isConnectingOrConnected = _apiController.ServerState is ServerState.Connected or ServerState.Connecting or ServerState.Reconnecting;

            if (_apiController.ServerState is not (ServerState.Reconnecting or ServerState.Disconnecting))
            {
                if (isConnectingOrConnected && !_serverManager.CurrentServer.FullPause)
                {
                    _serverManager.CurrentServer.FullPause = true;
                    _serverManager.Save();
                }
                else if (!isConnectingOrConnected && _serverManager.CurrentServer.FullPause)
                {
                    _serverManager.CurrentServer.FullPause = false;
                    _serverManager.Save();
                }

                _ = _apiController.CreateConnectionsAsync();
            }
        }
    }

    private void OnLightfinderEntryClick(DtrInteractionEvent interactionEvent)
    {
        if (!_configService.Current.ShowLightfinderInDtr)
            return;

        if (interactionEvent.ClickType.Equals(MouseClickType.Left))
        {
            _broadcastService.ToggleBroadcast();
        }
    }

    private async Task RunAsync()
    {
        while (!_cancellationTokenSource.IsCancellationRequested)
        {
            await Task.Delay(1000, _cancellationTokenSource.Token).ConfigureAwait(false);

            Update();
        }
    }

    private void Update()
    {
        var config = _configService.Current;

        if (!config.HasValidSetup())
        {
            HideStatusEntry();
            HideLightfinderEntry();
            return;
        }

        if (config.EnableDtrEntry)
            UpdateStatusEntry(config);
        else
            HideStatusEntry();

        if (config.ShowLightfinderInDtr)
            UpdateLightfinderEntry(config);
        else
            HideLightfinderEntry();
    }

    private void UpdateStatusEntry(LightlessConfig config)
    {
        string text;
        string tooltip;
        Colors colors;

        if (_apiController.IsConnected)
        {
            var snapshot = _pairUiService.GetSnapshot();
            var visiblePairsQuery = snapshot.PairsByUid.Values.Where(x => x.IsVisible && !x.IsPaused);
            var pairCount = visiblePairsQuery.Count();
            text = $"\uE044 {pairCount}";
            if (pairCount > 0)
            {
                var preferNote = config.PreferNoteInDtrTooltip;
                var showUid = config.ShowUidInDtrTooltip;

                IEnumerable<string> visiblePairs = showUid
                    ? visiblePairsQuery.Select(x => string.Format("{0} ({1})", preferNote ? x.GetNote() ?? x.PlayerName : x.PlayerName, x.UserData.AliasOrUID))
                    : visiblePairsQuery.Select(x => string.Format("{0}", preferNote ? x.GetNote() ?? x.PlayerName : x.PlayerName));

                tooltip = $"Lightless Sync: Connected{Environment.NewLine}----------{Environment.NewLine}{string.Join(Environment.NewLine, visiblePairs)}";
                colors = config.DtrColorsPairsInRange;
            }
            else
            {
                tooltip = "Lightless Sync: Connected";
                colors = config.DtrColorsDefault;
            }
        }
        else
        {
            text = "\uE044 \uE04C";
            tooltip = "Lightless Sync: Not Connected";
            colors = config.DtrColorsNotConnected;
        }

        if (!config.UseColorsInDtr)
            colors = default;

        var statusEntry = _statusEntry.Value;
        if (!statusEntry.Shown)
        {
            _logger.LogInformation("Showing status entry");
            statusEntry.Shown = true;
        }

        bool statusNeedsUpdate =
            !string.Equals(text, _statusText, StringComparison.Ordinal) ||
            !string.Equals(tooltip, _statusTooltip, StringComparison.Ordinal) ||
            colors != _statusColors;

        if (statusNeedsUpdate)
        {
            statusEntry.Text = BuildColoredSeString(text, colors);
            statusEntry.Tooltip = tooltip;
            _statusText = text;
            _statusTooltip = tooltip;
            _statusColors = colors;
        }
    }

    private void UpdateLightfinderEntry(LightlessConfig config)
    {
        var lightfinderEntry = _lightfinderEntry.Value;
        if (!lightfinderEntry.Shown)
        {
            _logger.LogInformation("Showing Lightfinder entry");
            lightfinderEntry.Shown = true;
        }

        var indicator = BuildLightfinderIndicator();
        var lightfinderText = indicator.Text ?? string.Empty;
        var lightfinderColors = config.UseLightfinderColorsInDtr ? indicator.Colors : default;
        var lightfinderTooltip = BuildLightfinderTooltip(indicator.Tooltip);

        bool lightfinderNeedsUpdate =
            !string.Equals(lightfinderText, _lightfinderText, StringComparison.Ordinal) ||
            !string.Equals(lightfinderTooltip, _lightfinderTooltip, StringComparison.Ordinal) ||
            lightfinderColors != _lightfinderColors;

        if (lightfinderNeedsUpdate)
        {
            lightfinderEntry.Text = BuildColoredSeString(lightfinderText, lightfinderColors);
            lightfinderEntry.Tooltip = lightfinderTooltip;
            _lightfinderText = lightfinderText;
            _lightfinderTooltip = lightfinderTooltip;
            _lightfinderColors = lightfinderColors;
        }
    }

    private string? GetLocalHashedCid()
    {
        var now = DateTime.UtcNow;
        lock (_localHashedCidLock)
        {
            if (_localHashedCid is not null && now - _localHashedCidFetchedAt < _localHashedCidCacheDuration)
            {
                return _localHashedCid;
            }
        }

        QueueLocalHashedCidRefresh();

        lock (_localHashedCidLock)
        {
            return _localHashedCid;
        }
    }

    private void QueueLocalHashedCidRefresh()
    {
        if (Interlocked.Exchange(ref _localHashedCidRefreshActive, 1) != 0)
        {
            return;
        }

        _ = Task.Run(async () =>
        {
            try
            {
                var cid = _dalamudUtilService.GetCID();
				var hashedCid = cid.ToString().GetHash256();
                lock (_localHashedCidLock)
                {
                    _localHashedCid = hashedCid;
                    _localHashedCidFetchedAt = DateTime.UtcNow;
                }
            }
            catch (Exception ex)
            {
                var now = DateTime.UtcNow;
                lock (_localHashedCidLock)
                {
                    if (now >= _localHashedCidNextErrorLog)
                    {
                        _logger.LogDebug(ex, "Failed to refresh local hashed CID for Lightfinder DTR entry.");
                        _localHashedCidNextErrorLog = now + _localHashedCidErrorCooldown;
                    }

                    _localHashedCid = null;
                    _localHashedCidFetchedAt = now;
                }
            }
            finally
            {
                Interlocked.Exchange(ref _localHashedCidRefreshActive, 0);
            }
        });
    }

    private List<string> GetNearbyBroadcasts()
    {
        try
        {
            var localHashedCid = GetLocalHashedCid();
            return [.. _broadcastScannerService
                .GetActiveBroadcasts(string.IsNullOrEmpty(localHashedCid) ? null : localHashedCid)
                .Select(b => _dalamudUtilService.FindPlayerByNameHash(b.Key).Name)];
        }
        catch (Exception ex)
        {
            var now = DateTime.UtcNow;

            if (now >= _pairRequestNextErrorLog)
            {
                _logger.LogDebug(ex, "Failed to retrieve nearby broadcasts for Lightfinder DTR entry.");
                _pairRequestNextErrorLog = now + _localHashedCidErrorCooldown;
            }

            return [];
        }
    }

    private IReadOnlyList<PairRequestDisplay> GetPendingPairRequest()
    {
        try
        {
            return _pairRequestService.GetActiveRequests();
        }
        catch (Exception ex)
        {
            var now = DateTime.UtcNow;

            if (now >= _pairRequestNextErrorLog)
            {
                _logger.LogDebug(ex, "Failed to retrieve pair request count for Lightfinder DTR entry.");
                _pairRequestNextErrorLog = now + _localHashedCidErrorCooldown;
            }

            return [];
        }
    }

    private (string Text, Colors Colors, string Tooltip) BuildLightfinderIndicator()
    {
        var config = _configService.Current;
        const string icon = "\uE048";
        if (!_broadcastService.IsLightFinderAvailable)
        {
            return ($"{icon} --", SwapColorChannels(config.DtrColorsLightfinderUnavailable), "Lightfinder - Unavailable on this server.");
        }

        if (_broadcastService.IsBroadcasting)
        {
            switch (config.LightfinderDtrDisplayMode)
            {
                case LightfinderDtrDisplayMode.PendingPairRequests:
                {
                    return FormatTooltip("Pending pair requests", GetPendingPairRequest().Select(x => x.DisplayName), icon, SwapColorChannels(config.DtrColorsLightfinderEnabled));
                }
                default:
                {
                    return FormatTooltip("Nearby Lightfinder users", GetNearbyBroadcasts(), icon, SwapColorChannels(config.DtrColorsLightfinderEnabled));
                }
            }
        }

        var tooltip = new StringBuilder("Lightfinder - Disabled");
        var colors = SwapColorChannels(config.DtrColorsLightfinderDisabled);
        if (_broadcastService.RemainingCooldown is { } cooldown && cooldown > TimeSpan.Zero)
        {
            tooltip.AppendLine();
            tooltip.Append("Cooldown: ").Append(Math.Ceiling(cooldown.TotalSeconds)).Append("s");
            colors = SwapColorChannels(config.DtrColorsLightfinderCooldown);
        }

        return ($"{icon} OFF", colors, tooltip.ToString());
    }

    private static (string, Colors, string) FormatTooltip(string title, IEnumerable<string> names, string icon, Colors color)
    {
        var list = names.Where(x => !string.IsNullOrEmpty(x)).ToList();
        var tooltip = new StringBuilder()
            .Append($"Lightfinder - Enabled{Environment.NewLine}")
            .Append($"{title}: {list.Count}{Environment.NewLine}")
            .AppendJoin(Environment.NewLine, list)
            .ToString();

        return ($"{icon} {list.Count}", color, tooltip);
    }

    private static string BuildLightfinderTooltip(string baseTooltip)
    {
        var builder = new StringBuilder();
        if (!string.IsNullOrWhiteSpace(baseTooltip))
            builder.Append(baseTooltip.TrimEnd());
        else
            builder.Append("Lightfinder status unavailable.");

        return builder.ToString().TrimEnd();
    }

    private static void AppendColoredSegment(SeStringBuilder builder, string? text, Colors colors)
    {
        if (string.IsNullOrEmpty(text))
            return;

        if (colors.Foreground != default)
            builder.Add(BuildColorStartPayload(_colorTypeForeground, colors.Foreground));
        if (colors.Glow != default)
            builder.Add(BuildColorStartPayload(_colorTypeGlow, colors.Glow));

        builder.AddText(text);

        if (colors.Glow != default)
            builder.Add(BuildColorEndPayload(_colorTypeGlow));
        if (colors.Foreground != default)
            builder.Add(BuildColorEndPayload(_colorTypeForeground));
    }

    #region Colored SeString
    private const byte _colorTypeForeground = 0x13;
    private const byte _colorTypeGlow = 0x14;

    internal static Colors SwapColorChannels(Colors colors)
        => new(SwapColorComponent(colors.Foreground), SwapColorComponent(colors.Glow));

    private static uint SwapColorComponent(uint color)
    {
        if (color == 0)
            return 0;

        return ((color & 0xFFu) << 16) | (color & 0xFF00u) | ((color >> 16) & 0xFFu);
    }

    private static SeString BuildColoredSeString(string text, Colors colors)
    {
        var ssb = new SeStringBuilder();
        AppendColoredSegment(ssb, text, colors);
        return ssb.Build();
    }

    private static RawPayload BuildColorStartPayload(byte colorType, uint color)
        => new(unchecked([
            0x02,
            colorType,
            0x05,
            0xF6,
            byte.Max((byte)color, (byte)0x01),
            byte.Max((byte)(color >> 8), (byte)0x01),
            byte.Max((byte)(color >> 16), (byte)0x01),
            0x03
        ]));

    private static RawPayload BuildColorEndPayload(byte colorType)
        => new([0x02, colorType, 0x02, 0xEC, 0x03]);

    [StructLayout(LayoutKind.Sequential)]
    public readonly record struct Colors(uint Foreground = default, uint Glow = default);
    #endregion
}