using Dalamud.Plugin;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
using System.Linq;

namespace LightlessSync.Interop.Ipc.Framework;

public enum IpcConnectionState
{
    Unknown = 0,
    MissingPlugin = 1,
    VersionMismatch = 2,
    PluginDisabled = 3,
    NotReady = 4,
    Available = 5,
    Error = 6,
}

public sealed record IpcServiceDescriptor(string InternalName, string DisplayName, Version MinimumVersion)
{
    public override string ToString()
        => $"{DisplayName} (>= {MinimumVersion})";
}

public interface IIpcService : IDisposable
{
    IpcServiceDescriptor Descriptor { get; }
    IpcConnectionState State { get; }
    IDalamudPluginInterface PluginInterface { get; }
    bool APIAvailable { get; }
    void CheckAPI();
}

public interface IIpcInterop : IDisposable
{
    string Name { get; }
    void OnConnectionStateChanged(IpcConnectionState state);
}

public abstract class IpcInteropBase : IIpcInterop
{
    protected IpcInteropBase(ILogger logger)
    {
        Logger = logger;
    }

    protected ILogger Logger { get; }

    protected IpcConnectionState State { get; private set; } = IpcConnectionState.Unknown;

    protected bool IsAvailable => State == IpcConnectionState.Available;

    public abstract string Name { get; }

    public void OnConnectionStateChanged(IpcConnectionState state)
    {
        if (State == state)
        {
            return;
        }

        var previous = State;
        State = state;
        HandleStateChange(previous, state);
    }

    protected abstract void HandleStateChange(IpcConnectionState previous, IpcConnectionState current);

    public virtual void Dispose()
    {
    }
}

public abstract class IpcServiceBase : DisposableMediatorSubscriberBase, IIpcService
{
    private readonly List<IIpcInterop> _interops = new();

    protected IpcServiceBase(
        ILogger logger,
        LightlessMediator mediator,
        IDalamudPluginInterface pluginInterface,
        IpcServiceDescriptor descriptor) : base(logger, mediator)
    {
        PluginInterface = pluginInterface;
        Descriptor = descriptor;
    }

    protected IDalamudPluginInterface PluginInterface { get; }

    IDalamudPluginInterface IIpcService.PluginInterface => PluginInterface;

    protected IpcServiceDescriptor Descriptor { get; }

    IpcServiceDescriptor IIpcService.Descriptor => Descriptor;

    public IpcConnectionState State { get; private set; } = IpcConnectionState.Unknown;

    public bool APIAvailable => State == IpcConnectionState.Available;

    public virtual void CheckAPI()
    {
        var newState = EvaluateState();
        UpdateState(newState);
    }

    protected virtual IpcConnectionState EvaluateState()
    {
        try
        {
            var plugin = PluginInterface.InstalledPlugins
                .Where(p => string.Equals(p.InternalName, Descriptor.InternalName, StringComparison.OrdinalIgnoreCase))
                .OrderByDescending(p => p.IsLoaded)
                .FirstOrDefault();

            if (plugin == null)
            {
                return IpcConnectionState.MissingPlugin;
            }

            if (plugin.Version < Descriptor.MinimumVersion)
            {
                return IpcConnectionState.VersionMismatch;
            }

            if (!IsPluginEnabled(plugin))
            {
                return IpcConnectionState.PluginDisabled;
            }

            if (!IsPluginReady())
            {
                return IpcConnectionState.NotReady;
            }

            return IpcConnectionState.Available;
        }
        catch (Exception ex)
        {
            Logger.LogDebug(ex, "Failed to evaluate IPC state for {Service}", Descriptor.DisplayName);
            return IpcConnectionState.Error;
        }
    }

    protected virtual bool IsPluginEnabled(IExposedPlugin plugin)
        => plugin.IsLoaded;

    protected virtual bool IsPluginReady()
        => true;

    protected TInterop RegisterInterop<TInterop>(TInterop interop)
        where TInterop : IIpcInterop
    {
        _interops.Add(interop);
        interop.OnConnectionStateChanged(State);
        return interop;
    }

    private void UpdateState(IpcConnectionState newState)
    {
        if (State == newState)
        {
            return;
        }

        var previous = State;
        State = newState;
        OnConnectionStateChanged(previous, newState);

        foreach (var interop in _interops)
        {
            interop.OnConnectionStateChanged(newState);
        }
    }

    protected virtual void OnConnectionStateChanged(IpcConnectionState previous, IpcConnectionState current)
    {
        Logger.LogTrace("{Service} IPC state transitioned from {Previous} to {Current}", Descriptor.DisplayName, previous, current);
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (!disposing)
        {
            return;
        }

        for (var i = _interops.Count - 1; i >= 0; --i)
        {
            _interops[i].Dispose();
        }

        _interops.Clear();
    }
}
