using Akka.Actor;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;

/// <summary>
/// WP-16: Alarm Execution Actor -- short-lived child of Alarm Actor.
/// Same pattern as ScriptExecutionActor.
/// WP-21: CAN call Instance.CallScript() (ask to sibling Script Actor).
///         Instance scripts CANNOT call alarm on-trigger scripts (no API for it).
/// Supervision: Stop on unhandled exception.
/// </summary>
public class AlarmExecutionActor : ReceiveActor
{
    /// <summary>Initializes a new <see cref="AlarmExecutionActor"/> and immediately schedules execution of the alarm on-trigger script.</summary>
    /// <param name="alarmName">The canonical name of the alarm that triggered.</param>
    /// <param name="instanceName">The name of the owning instance.</param>
    /// <param name="level">The alarm severity level at the time of triggering.</param>
    /// <param name="priority">The alarm priority value.</param>
    /// <param name="message">The alarm message to pass to the script.</param>
    /// <param name="compiledScript">The pre-compiled on-trigger script to execute.</param>
    /// <param name="instanceActor">Reference to the parent instance actor for attribute/script calls.</param>
    /// <param name="sharedScriptLibrary">Shared script library providing common utilities.</param>
    /// <param name="options">Site runtime configuration options, including the execution timeout.</param>
    /// <param name="logger">Logger for execution diagnostics.</param>
    public AlarmExecutionActor(
        string alarmName,
        string instanceName,
        AlarmLevel level,
        int priority,
        string message,
        Script<object?> compiledScript,
        IActorRef instanceActor,
        SharedScriptLibrary sharedScriptLibrary,
        SiteRuntimeOptions options,
        ILogger logger)
    {
        var self = Self;
        var parent = Context.Parent;

        ExecuteAlarmScript(
            alarmName, instanceName, level, priority, message,
            compiledScript, instanceActor,
            sharedScriptLibrary, options, self, parent, logger);
    }

    private static void ExecuteAlarmScript(
        string alarmName,
        string instanceName,
        AlarmLevel level,
        int priority,
        string message,
        Script<object?> compiledScript,
        IActorRef instanceActor,
        SharedScriptLibrary sharedScriptLibrary,
        SiteRuntimeOptions options,
        IActorRef self,
        IActorRef parent,
        ILogger logger)
    {
        var timeout = TimeSpan.FromSeconds(options.ScriptExecutionTimeoutSeconds);

        // SiteRuntime-009: run the alarm on-trigger body on the dedicated
        // script-execution scheduler, not the shared .NET thread pool.
        var scheduler = ScriptExecutionScheduler.Shared(options);

        _ = Task.Factory.StartNew(async () =>
        {
            using var cts = new CancellationTokenSource(timeout);
            try
            {
                // WP-21: AlarmExecutionActor can call Instance.CallScript()
                // via the ScriptRuntimeContext injected into globals
                var context = new ScriptRuntimeContext(
                    instanceActor,
                    self,
                    sharedScriptLibrary,
                    currentCallDepth: 0,
                    options.MaxScriptCallDepth,
                    timeout,
                    instanceName,
                    logger);

                var globals = new ScriptGlobals
                {
                    Instance = context,
                    Parameters = new ScriptParameters(),
                    CancellationToken = cts.Token,
                    Alarm = new AlarmContext
                    {
                        Name = alarmName,
                        Level = level,
                        Priority = priority,
                        Message = message
                    }
                };

                await compiledScript.RunAsync(globals, cts.Token);

                parent.Tell(new AlarmActor.AlarmExecutionCompleted(alarmName, true));
            }
            catch (OperationCanceledException)
            {
                logger.LogWarning(
                    "Alarm on-trigger script for {Alarm} on {Instance} timed out",
                    alarmName, instanceName);
                parent.Tell(new AlarmActor.AlarmExecutionCompleted(alarmName, false));
            }
            catch (Exception ex)
            {
                // WP-32: Failures logged, alarm continues
                logger.LogError(ex,
                    "Alarm on-trigger script for {Alarm} on {Instance} failed",
                    alarmName, instanceName);
                parent.Tell(new AlarmActor.AlarmExecutionCompleted(alarmName, false));
            }
            finally
            {
                self.Tell(PoisonPill.Instance);
            }
        }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, scheduler).Unwrap();
    }
}
