using Microsoft.CodeAnalysis.Scripting;
using RobotNet.Script;
using RobotNet.Script.Shares;
using System.Collections.Concurrent;

namespace RobotNet.ScriptManager.Models;

public class ScriptMissionGlobals(IDictionary<string, object?> _globals, IDictionary<string, object?> _robotnet, ConcurrentDictionary<string, object?> _parameters)
{
    public readonly IDictionary<string, object?> globals = _globals;
    public readonly IDictionary<string, object?> robotnet = _robotnet;
    public readonly IDictionary<string, object?> parameters = _parameters;
}
public class ScriptMission : IDisposable
{
    public Guid Id { get; }
    public string Name { get; }
    public MissionStatus Status { get; private set; } = MissionStatus.Idle;
    public Exception? Exception { get; private set; } = null;
    public CancellationToken CancellationToken => internalCts?.Token ?? CancellationToken.None;
    public WaitHandle WaitHandle => waitHandle;

    private readonly ScriptRunner<IAsyncEnumerable<MissionState>> Runner;
    private readonly ScriptMissionGlobals Globals;
    private readonly CancellationTokenSource internalCts;
    private readonly Thread thread;
    private bool disposed;
    private readonly Mutex mutex = new();
    private readonly ScriptMissionLogger? Logger;
    private readonly ManualResetEvent waitHandle = new(false);

    public ScriptMission(Guid id, string name, ScriptRunner<IAsyncEnumerable<MissionState>> runner, ScriptMissionGlobals globals, CancellationTokenSource cts)
    {
        Id = id;
        Name = name;
        Runner = runner;
        Globals = globals;
        internalCts = cts;
        thread = new Thread(() => Run(internalCts.Token)) { IsBackground = true, Priority = ThreadPriority.Highest };

        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() as ScriptMissionLogger;
        }
    }

    public string GetLog() => Logger?.GetLog() ?? string.Empty;

    public void Start()
    {
        if (!mutex.WaitOne(1000)) return;

        try
        {
            ObjectDisposedException.ThrowIf(disposed, nameof(ScriptMission));
            if (Status != MissionStatus.Idle) throw new InvalidOperationException("Mission can only be started after preparation.");
            if (internalCts.IsCancellationRequested) throw new InvalidOperationException("Mission is not prepared or has been canceled.");

            Status = MissionStatus.Running;
            thread.Start();
        }
        finally
        {
            mutex.ReleaseMutex();
        }
    }

    public bool Pause()
    {
        if (!mutex.WaitOne(1000)) return false;

        try
        {
            ObjectDisposedException.ThrowIf(disposed, nameof(ScriptMission));
            if (Status == MissionStatus.Running)
            {
                Status = MissionStatus.Pausing;
                return true;
            }
            else
            {
                return false; // Cannot pause if not running
            }
        }
        catch (Exception)
        {
            return false;
        }
        finally
        {
            mutex.ReleaseMutex();
        }
    }

    public bool Resume()
    {
        if (!mutex.WaitOne(1000)) return false;
        try
        {
            ObjectDisposedException.ThrowIf(disposed, nameof(ScriptMission));
            if (Status == MissionStatus.Paused)
            {
                Status = MissionStatus.Resuming;
                return true;
            }
            else
            {
                return false; // Cannot resume if not paused
            }
        }
        catch (Exception)
        {
            return false;
        }
        finally
        {
            mutex.ReleaseMutex();
        }
    }

    public bool Cancel(string reason)
    {
        if (!mutex.WaitOne(1000))
        {
            return false;
        }
        try
        {
            ObjectDisposedException.ThrowIf(disposed, nameof(ScriptMission));
            if (!internalCts.IsCancellationRequested)
            {
                internalCts.Cancel();
            }
            if (Status == MissionStatus.Canceling || Status == MissionStatus.Canceled)
            {
                return true;
            }

            if (Status == MissionStatus.Idle)
            {
                Logger?.LogInfo($"{DateTime.UtcNow} Idle Mission '{Name}' cancel with reason: {reason}");
                Status = MissionStatus.Canceled;
                return true;
            }
            else if (Status == MissionStatus.Running || Status == MissionStatus.Pausing || Status == MissionStatus.Paused || Status == MissionStatus.Resuming)
            {
                Status = MissionStatus.Canceling;
                Logger?.LogInfo($"{DateTime.UtcNow} Mission '{Name}' canceling with reason: {reason}");
                return true;
            }
            else
            {
                return true;
            }
        }
        catch (Exception)
        {
            return false;
        }
        finally
        {
            mutex.ReleaseMutex();
        }
    }

    private void Run(CancellationToken cancellationToken)
    {
        Task.Factory.StartNew(async Task () =>
        {
            try
            {
                var cts = new CancellationTokenSource();
                cancellationToken.Register(() =>
                {
                    cts.CancelAfter(TimeSpan.FromSeconds(5));
                });
                await foreach (var state in await Runner.Invoke(Globals, cts.Token))
                {
                    Logger?.LogInfo($"{DateTime.UtcNow} Mission {Name}-{Id} with step: {state.Step}; message: {state.Message}");
                    if (Status == MissionStatus.Pausing)
                    {
                        Status = MissionStatus.Paused;
                        while (Status == MissionStatus.Paused && !cancellationToken.IsCancellationRequested)
                        {
                            try
                            {
                                await Task.Delay(1000, cancellationToken);
                            }
                            catch (TaskCanceledException) { }
                            catch (OperationCanceledException) { }
                        }
                    }

                    if (cancellationToken.IsCancellationRequested)
                    {
                        if (Status != MissionStatus.Canceling)
                        {
                            Exception = new OperationCanceledException($"{DateTime.UtcNow} Mission {Name}-{Id} was canceled externally.");
                            Logger?.LogError(Exception);
                            Status = MissionStatus.Error;
                        }
                        break;
                    }

                    if (Status == MissionStatus.Resuming)
                    {
                        Status = MissionStatus.Running;
                        continue;
                    }
                    else if (Status == MissionStatus.Canceling)
                    {
                        if (internalCts == null || internalCts.IsCancellationRequested)
                        {
                            Exception = new OperationCanceledException($"{DateTime.UtcNow} Mission {Name}-{Id} was canceled externally.");
                            Logger?.LogError(Exception);
                            Status = MissionStatus.Error;
                            break;
                        }
                        else
                        {
                            Logger?.LogError($"{DateTime.UtcNow} Mission {Name}-{Id} is canceling");
                            continue;
                        }
                    }
                    else if (Status == MissionStatus.Running)
                    {
                        continue;
                    }
                    else if (Status != MissionStatus.Error)
                    {
                        Status = MissionStatus.Error;
                        Exception = new InvalidOperationException($"{DateTime.UtcNow} Mission {Name}-{Id} Unexpected status change: {Status}");
                        Logger?.LogError(Exception);
                        break;
                    }
                }

                if (Status == MissionStatus.Running)
                {
                    Logger?.LogInfo($"{DateTime.UtcNow} Mission {Name}-{Id} is completed");
                    Status = MissionStatus.Completed;
                }
                else if (Status == MissionStatus.Canceling)
                {
                    Logger?.LogError($"{DateTime.UtcNow} Mission {Name}-{Id} is canceled");
                    Status = MissionStatus.Canceled;
                }
                else if (Status != MissionStatus.Error)
                {
                    Exception = new OperationCanceledException($"{DateTime.UtcNow} Mission {Name}-{Id} Cancel from status {Status}");
                    Logger?.LogError(Exception);
                    Status = MissionStatus.Error;
                }
            }
            catch (OperationCanceledException oce)
            {
                if (Status != MissionStatus.Canceling)
                {
                    Status = MissionStatus.Error;
                    Exception = oce;
                    Logger?.LogError(oce.ToString());
                }
                else
                {
                    Logger?.LogInfo($"{DateTime.UtcNow} Mission {Name}-{Id} was canceled successfully.");
                    Status = MissionStatus.Canceled;
                }
            }
            catch (Exception ex)
            {
                Status = MissionStatus.Error;
                Exception = ex;
                Logger?.LogError(Exception);
            }
            finally
            {
                waitHandle.Set();
                Logger?.LogInfo($"{DateTime.UtcNow} End Mission wtih status: {Status}");
            }
        }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Current).GetAwaiter().GetResult();
    }

    public void Dispose()
    {
        if (disposed) return;
        disposed = true;

        if (!internalCts.IsCancellationRequested)
        {
            internalCts.Cancel();
        }
        thread.Join();
        internalCts.Dispose();
        GC.SuppressFinalize(this);
    }

}
