using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using RobotNet.Script.Shares;
using System.Diagnostics;

namespace RobotNet.ScriptManager.Models;

public class ScriptTask : IDisposable
{
    public bool Paused { get; private set; } = false;
    private readonly ScriptRunner<object> scriptRunner;
    private readonly ScriptTaskGlobals globals;
    private readonly int Interval;
    private readonly double ProcessTime;
    private CancellationTokenSource? internalCts;
    private Thread? thread;
    private bool disposed;
    private readonly string Name;
    private readonly RobotNet.Script.ILogger? Logger;
    private readonly bool AutoStart;

    public class ScriptTaskGlobals(IDictionary<string, object?> _globals, IDictionary<string, object?> _robotnet)
    {
        public IDictionary<string, object?> globals = _globals;
        public IDictionary<string, object?> robotnet = _robotnet;
    }

    public ScriptTask(ScriptTaskData task, ScriptOptions options, IDictionary<string, object?> _globals, IDictionary<string, object?> _robotnet)
    {
        var script = CSharpScript.Create(task.Script, options, globalsType: typeof(ScriptTaskGlobals));
        scriptRunner = script.CreateDelegate();

        Name = task.Name;
        globals = new ScriptTaskGlobals(_globals, _robotnet);
        Interval = task.Interval;
        ProcessTime = Interval * 0.8;
        AutoStart = task.AutoStart;
        Paused = !task.AutoStart;

        if (globals.robotnet.TryGetValue($"get_{nameof(IRobotNetGlobals.Logger)}", out object? get_logger)
            && get_logger is Func<RobotNet.Script.ILogger> get_logger_func)
        {
            Logger = get_logger_func.Invoke();
        }
    }

    public void Start(CancellationToken cancellationToken = default)
    {
        Stop(); // Ensure previous thread is stopped before starting a new one

        internalCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

        Paused = !AutoStart;
        thread = new Thread(RunningHandler)
        {
            IsBackground = true,
            Priority = ThreadPriority.Highest,
        };
        thread.Start();
    }

    public void Pause()
    {
        Paused = true;
    }

    public void Resume()
    {
        Paused = false;
    }

    private void RunningHandler()
    {
        if (internalCts == null || internalCts.IsCancellationRequested)
        {
            return;
        }
        var cts = new CancellationTokenSource();
        var stopwatch = Stopwatch.StartNew();
        int elapsed;
        int remaining;

        internalCts.Token.Register(() =>
        {
            cts.CancelAfter(TimeSpan.FromMilliseconds(Interval * 3));
        });

        try
        {
            while (!internalCts.IsCancellationRequested)
            {
                if (Paused)
                {
                    Thread.Sleep(Interval); // Sleep briefly to avoid busy waiting
                    continue;
                }
                stopwatch.Restart();
                scriptRunner.Invoke(globals, cts.Token).GetAwaiter().GetResult();

                stopwatch.Stop();
                elapsed = (int)stopwatch.ElapsedMilliseconds;
                remaining = Interval - elapsed;

                // If execution time exceeds ProcessTime, add another cycle
                if (elapsed > ProcessTime)
                {
                    remaining += Interval;
                }

                if (remaining > 0)
                {
                    try
                    {
                        Thread.Sleep(remaining);
                    }
                    catch (ThreadInterruptedException)
                    {
                        break;
                    }
                }
                GC.Collect();
            }
        }
        catch (Exception ex)
        {
            Logger?.LogError($"Task \"{Name}\" execution failed: {ex}");
        }
    }

    public void Stop()
    {
        if (internalCts != null && !internalCts.IsCancellationRequested)
        {
            internalCts.Cancel();
        }

        if (thread != null && thread.IsAlive)
        {
            //thread.Interrupt();
            thread.Join();
        }

        internalCts?.Dispose();
        internalCts = null;
        thread = null;
    }

    public void Dispose()
    {
        if (disposed) return;
        Stop();
        disposed = true;
        GC.SuppressFinalize(this);
    }
}
