using System;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace HellionChat.Integrations;

// Newtonsoft.Json is used here for IPC compatibility with Honorific, which
// serialises TitleData with it. It's a transitive Dalamud dependency — no
// new NuGet entry needed. The rest of HellionChat uses System.Text.Json.
internal sealed class HonorificService : IDisposable
{
    private const string IpcNamespace = "Honorific";

    // Major version of the Honorific IPC contract we're built against.
    internal const uint ExpectedApiMajor = 3;

    // IPC gates — kept as fields so Dispose can unsubscribe the same instances.
    private readonly ICallGateSubscriber<(uint, uint)> _apiVersion;
    private readonly ICallGateSubscriber<string> _getLocalCharacterTitle;
    private readonly ICallGateSubscriber<string, object> _localCharacterTitleChanged;
    private readonly ICallGateSubscriber<object> _ready;
    private readonly ICallGateSubscriber<object> _disposing;

    private readonly ILogger<HonorificService> _logger;
    private readonly IFramework _framework;
    private bool _versionWarningLogged;

    // Thread: framework only — IPC delivery + ImGui render both run there.
    public HonorificTitleData? CurrentTitle { get; private set; }
    public bool IsAvailable { get; private set; }
    public (uint Major, uint Minor)? DetectedApiVersion { get; private set; }

    public HonorificService(
        IDalamudPluginInterface pluginInterface,
        ILogger<HonorificService> logger,
        IFramework framework
    )
    {
        _framework = framework;
        _logger = logger;

        // Gate objects are cached per-name by Dalamud and safe to register
        // before Honorific loads — they just won't fire until it does.
        // Initial pull is scheduled on the framework thread because plugin
        // constructors run on the loader thread, and Honorific's IPC handlers
        // read ObjectTable.LocalPlayer which throws off the framework thread.
        _apiVersion = pluginInterface.GetIpcSubscriber<(uint, uint)>($"{IpcNamespace}.ApiVersion");
        _getLocalCharacterTitle = pluginInterface.GetIpcSubscriber<string>(
            $"{IpcNamespace}.GetLocalCharacterTitle"
        );
        _localCharacterTitleChanged = pluginInterface.GetIpcSubscriber<string, object>(
            $"{IpcNamespace}.LocalCharacterTitleChanged"
        );
        _ready = pluginInterface.GetIpcSubscriber<object>($"{IpcNamespace}.Ready");
        _disposing = pluginInterface.GetIpcSubscriber<object>($"{IpcNamespace}.Disposing");

        _localCharacterTitleChanged.Subscribe(OnTitleChanged);
        _ready.Subscribe(OnReady);
        _disposing.Subscribe(OnDisposing);

        _framework.RunOnFrameworkThread(TryInitialPull);
    }

    public void Dispose()
    {
        // Wrap each unsubscribe — a missing gate must not block the others.
        // Leaking a subscription keeps this service alive across plugin reloads.
        TryUnsubscribe(() => _localCharacterTitleChanged.Unsubscribe(OnTitleChanged));
        TryUnsubscribe(() => _ready.Unsubscribe(OnReady));
        TryUnsubscribe(() => _disposing.Unsubscribe(OnDisposing));
    }

    // Thread: framework (scheduled from ctor and OnReady).
    private void TryInitialPull()
    {
        try
        {
            var version = _apiVersion.InvokeFunc();
            DetectedApiVersion = version;

            if (!IsApiVersionCompatible(version))
            {
                if (!_versionWarningLogged)
                {
                    _logger.LogWarning(
                        "Honorific API version mismatch — expected major 3, "
                            + "found {Major}.{Minor}. Disabling Honorific integration.",
                        version.Item1,
                        version.Item2
                    );
                    _versionWarningLogged = true;
                }
                IsAvailable = false;
                return;
            }

            IsAvailable = true;
            _versionWarningLogged = false;
            var json = _getLocalCharacterTitle.InvokeFunc();
            CurrentTitle = ParseTitleJson(json);
        }
        catch (Exception ex)
        {
            // Honorific not installed or not yet initialised — Ready will retry.
            _logger.LogDebug(ex, "Honorific not available at HellionChat startup; awaiting Ready.");
            IsAvailable = false;
            CurrentTitle = null;
        }
    }

    // Thread: framework (Dalamud IPC delivery contract).
    private void OnTitleChanged(string json)
    {
        // Skip updates on version mismatch; subscription stays live for reload.
        if (!IsAvailable)
            return;
        CurrentTitle = ParseTitleJson(json);
    }

    // Thread: any (Honorific dispatches NotifyReady from its own thread).
    private void OnReady()
    {
        _framework.RunOnFrameworkThread(TryInitialPull);
    }

    // Thread: framework (IPC delivery contract); idempotent — Disposing fires once.
    private void OnDisposing()
    {
        // Honorific unloading — clear cached state so the header hides next frame.
        // Subscriptions stay registered in case Honorific reloads.
        // CurrentTitle is already nulled by OnTitleChanged before this fires,
        // re-clearing here is belt-and-braces.
        CurrentTitle = null;
        IsAvailable = false;
        DetectedApiVersion = null;
    }

    // Thread: framework (called from Dispose, which runs on the framework
    // cleanup block in Plugin.DisposeAsync).
    private void TryUnsubscribe(Action unsubscribe)
    {
        try
        {
            unsubscribe();
        }
        catch (Exception ex)
        {
            // Warning not Debug — a silent unsubscribe failure leaks a live
            // subscription across plugin reloads.
            _logger.LogWarning(
                ex,
                "Honorific unsubscribe failed (likely API break or gate already gone)."
            );
        }
    }

    internal static HonorificTitleData? ParseTitleJson(string json)
    {
        if (string.IsNullOrEmpty(json))
            return null;

        try
        {
            return JsonConvert.DeserializeObject<HonorificTitleData>(json);
        }
        catch (JsonException)
        {
            return null;
        }
    }

    internal static bool IsApiVersionCompatible((uint Major, uint Minor) apiVersion)
    {
        return apiVersion.Major == ExpectedApiMajor;
    }

    internal static bool ShouldRenderSlot(
        bool toggleEnabled,
        bool isAvailable,
        HonorificTitleData? title
    )
    {
        if (!toggleEnabled)
            return false;
        if (!isAvailable)
            return false;
        if (title is null)
            return false;
        if (title.IsOriginal)
            return false;
        if (string.IsNullOrEmpty(title.Title))
            return false;
        return true;
    }
}
