using Akka.Actor;
using Akka.Cluster.Tools.Singleton;
using Microsoft.Extensions.Logging;

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

/// <summary>
/// Registers a cluster singleton with the canonical naming scheme
/// (<c>{name}-singleton</c> / <c>{name}-proxy</c>), a PoisonPill termination
/// message, and a PhaseClusterLeave drain task that GracefulStops the manager
/// so in-flight EF (central) or SQLite (site) work completes before handover.
/// Extracted from copy-pasted ~60-line blocks (review 01 [Low]) whose drift
/// left the two busiest singletons (notification-outbox, audit-log-ingest)
/// without drain tasks (review 01 [Medium]). The optional <c>role</c> maps to
/// <see cref="ClusterSingletonManagerSettings"/>/<see cref="ClusterSingletonProxySettings"/>
/// <c>.WithRole</c>, so role-scoped site singletons get the same drain
/// (review 01 round-2 N5).
/// </summary>
internal static class SingletonRegistrar
{
    internal sealed record Handle(IActorRef Manager, IActorRef Proxy);

    /// <summary>
    /// Creates the <see cref="ClusterSingletonManager"/> and proxy for a cluster
    /// singleton under the canonical naming scheme, and registers a
    /// PhaseClusterLeave drain task that GracefulStops the manager on shutdown.
    /// </summary>
    /// <param name="system">The actor system to register the singleton on.</param>
    /// <param name="name">The singleton's base name (actors are named <c>{name}-singleton</c> / <c>{name}-proxy</c>).</param>
    /// <param name="singletonProps">The <see cref="Props"/> used to create the singleton's managed actor.</param>
    /// <param name="logger">Logger used to report drain progress/failures.</param>
    /// <param name="drainTimeout">Maximum time to wait for the drain task to complete; defaults to 10 seconds.</param>
    /// <param name="role">Optional cluster role to scope the singleton to (applied to both the manager and proxy settings).</param>
    /// <returns>A <see cref="Handle"/> carrying the singleton manager and proxy actor refs.</returns>
    internal static Handle Start(
        ActorSystem system,
        string name,
        Props singletonProps,
        ILogger logger,
        TimeSpan? drainTimeout = null,
        string? role = null)
    {
        var managerSettings = ClusterSingletonManagerSettings.Create(system).WithSingletonName(name);
        if (role is not null) managerSettings = managerSettings.WithRole(role);

        var manager = system.ActorOf(
            ClusterSingletonManager.Props(
                singletonProps,
                PoisonPill.Instance,
                managerSettings),
            $"{name}-singleton");

        var timeout = drainTimeout ?? TimeSpan.FromSeconds(10);
        Akka.Actor.CoordinatedShutdown.Get(system).AddTask(
            Akka.Actor.CoordinatedShutdown.PhaseClusterLeave,
            $"drain-{name}-singleton",
            async () =>
            {
                try
                {
                    await manager.GracefulStop(timeout);
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex,
                        "{Singleton} singleton did not drain within the graceful-stop timeout; "
                        + "falling through to PoisonPill handover", name);
                }
                return Akka.Done.Instance;
            });

        var proxySettings = ClusterSingletonProxySettings.Create(system).WithSingletonName(name);
        if (role is not null) proxySettings = proxySettings.WithRole(role);

        var proxy = system.ActorOf(
            ClusterSingletonProxy.Props(
                $"/user/{name}-singleton",
                proxySettings),
            $"{name}-proxy");

        return new Handle(manager, proxy);
    }
}
