using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

using Jint;
using Jint.Native.Function;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using Serein.Core.Models.Output;
using Serein.Core.Models.Plugins.Info;
using Serein.Core.Services.Plugins.Js;
using Serein.Core.Utils.Extensions;

using JsConsole = Serein.Core.Services.Plugins.Js.Properties.Console;

namespace Serein.Core.Models.Plugins.Js;

public class JsPlugin : IPlugin
{
    public string FileName { get; }
    public PluginInfo Info { get; }
    public JsPluginConfig Config { get; }
    public Engine Engine { get; }
    public ScriptInstance ScriptInstance { get; }
    public JsConsole Console { get; }
    public TimerFactory TimerFactory { get; }

    public CancellationToken CancellationToken => _cancellationTokenSource.Token;
    public IReadOnlyDictionary<Event, Function> EventHandlers => _eventHandlers;
    public bool IsEnabled => !_cancellationTokenSource.IsCancellationRequested;

    private readonly CancellationTokenSource _cancellationTokenSource;
    private readonly IPluginLogger _pluginLogger;
    private readonly ConcurrentDictionary<Event, Function> _eventHandlers;

    public event PropertyChangedEventHandler? PropertyChanged;

    public JsPlugin(
        IServiceProvider serviceProvider,
        PluginInfo pluginInfo,
        string fileName,
        JsPluginConfig config
    )
    {
        _cancellationTokenSource = new();
        _pluginLogger = serviceProvider.GetRequiredService<IPluginLogger>();

        _eventHandlers = new();
        Info = pluginInfo;
        FileName = fileName;
        Config = config;
        TimerFactory = new(_cancellationTokenSource.Token);
        Console = new(_pluginLogger, Info.Name);
        ScriptInstance = new(serviceProvider, this);
        Engine = serviceProvider.GetRequiredService<JsEngineFactory>().Create(this);
    }

    public void Execute(string text) => Engine.Execute(text);

    public void Dispose()
    {
        Disable();
        _eventHandlers.Clear();
        Engine.Dispose();
        GC.SuppressFinalize(this);
    }

    public bool Invoke(Event @event, CancellationToken cancellationToken, params object[] args)
    {
        var entered = false;
        try
        {
            if (
                CancellationToken.IsCancellationRequested
                || !EventHandlers.TryGetValue(@event, out Function? func)
                || func is null
            )
                return false;

            if (!Monitor.TryEnter(Engine, 1000))
                throw new TimeoutException("等待引擎超时");

            entered = true;

            if (cancellationToken.IsCancellationRequested)
                return false;

            var result = Engine.Invoke(func, args);
            return result.IsBoolean() && result.AsBoolean();
        }
        catch (Exception e)
        {
            _pluginLogger.Log(
                LogLevel.Error,
                Info.Name,
                $"触发事件{@event}时出现异常：\n{e.GetDetailString()}"
            );
            return false;
        }
        finally
        {
            if (entered)
                Monitor.Exit(Engine);
        }
    }

    public void Disable()
    {
        if (!_cancellationTokenSource.IsCancellationRequested)
            _cancellationTokenSource.Cancel();

        PropertyChanged?.Invoke(this, new(nameof(IsEnabled)));
    }
}
