using Akka.Actor;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Logging;
using ScadaLink.Commons.Types;
using ScadaLink.Commons.Types.Enums;
using ScadaLink.Commons.Types.Scripts;
using ScadaLink.SiteRuntime.Scripts;

namespace ScadaLink.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
{
    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);

        _ = Task.Run(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);
            }
        });
    }
}
