using Akka.Actor;
using Akka.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
using ZB.MOM.WW.OtOpcUa.Cluster;
using ZB.MOM.WW.OtOpcUa.Configuration;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Hubs;

/// <summary>
///     Verifies the Phase 5 dark switch in <see cref="HubServiceCollectionExtensions.WithOtOpcUaSignalRBridges"/>:
///     <c>TelemetryDial:Mode = Dps</c> (default) spawns the four telemetry DPS bridge actors and NOT the
///     gRPC dial supervisor; <c>Grpc</c> spawns the dial supervisor and NONE of the four telemetry
///     bridges. The fleet-status bridge (deferred / out of Phase 5 scope) is spawned in BOTH modes.
///     The gate is proven on a real <see cref="ActorRegistry"/> from a started Akka host.
/// </summary>
public sealed class TelemetryModeWiringTests
{
    /// <summary>Dps mode: the four telemetry bridges + fleet bridge are registered; the dial supervisor is not.</summary>
    [Fact]
    public async Task Dps_mode_spawns_the_four_telemetry_bridges_and_not_the_dial_supervisor()
    {
        using var host = BuildBridgeHost(TelemetryDialOptions.ModeDps);
        await host.StartAsync();
        try
        {
            var registry = host.Services.GetRequiredService<ActorRegistry>();

            registry.TryGet<FleetStatusSignalRBridgeKey>(out _).ShouldBeTrue();
            registry.TryGet<AlertSignalRBridgeKey>(out _).ShouldBeTrue();
            registry.TryGet<ScriptLogSignalRBridgeKey>(out _).ShouldBeTrue();
            registry.TryGet<DriverStatusSignalRBridgeKey>(out _).ShouldBeTrue();
            registry.TryGet<DriverResilienceStatusBridgeKey>(out _).ShouldBeTrue();

            registry.TryGet<TelemetryDialSupervisorKey>(out _).ShouldBeFalse();
        }
        finally
        {
            await host.StopAsync();
        }
    }

    /// <summary>Grpc mode: the dial supervisor + fleet bridge are registered; none of the four telemetry bridges are.</summary>
    [Fact]
    public async Task Grpc_mode_spawns_the_dial_supervisor_and_none_of_the_four_telemetry_bridges()
    {
        using var host = BuildBridgeHost(TelemetryDialOptions.ModeGrpc);
        await host.StartAsync();
        try
        {
            var registry = host.Services.GetRequiredService<ActorRegistry>();

            registry.TryGet<FleetStatusSignalRBridgeKey>(out _).ShouldBeTrue();
            registry.TryGet<TelemetryDialSupervisorKey>(out _).ShouldBeTrue();

            registry.TryGet<AlertSignalRBridgeKey>(out _).ShouldBeFalse();
            registry.TryGet<ScriptLogSignalRBridgeKey>(out _).ShouldBeFalse();
            registry.TryGet<DriverStatusSignalRBridgeKey>(out _).ShouldBeFalse();
            registry.TryGet<DriverResilienceStatusBridgeKey>(out _).ShouldBeFalse();
        }
        finally
        {
            await host.StopAsync();
        }
    }

    /// <summary>Unset (default) mode is treated as Dps — the four bridges spawn, the supervisor does not.</summary>
    [Fact]
    public async Task Default_mode_is_dps()
    {
        using var host = BuildBridgeHost(mode: null);
        await host.StartAsync();
        try
        {
            var registry = host.Services.GetRequiredService<ActorRegistry>();
            registry.TryGet<AlertSignalRBridgeKey>(out _).ShouldBeTrue();
            registry.TryGet<TelemetryDialSupervisorKey>(out _).ShouldBeFalse();
        }
        finally
        {
            await host.StopAsync();
        }
    }

    /// <summary>Builds an admin-role host that runs <c>WithOtOpcUaSignalRBridges</c> under the given telemetry mode.</summary>
    /// <param name="mode">The <c>TelemetryDial:Mode</c> value, or <see langword="null"/> to leave it at its default.</param>
    private static IHost BuildBridgeHost(string? mode)
        => Host.CreateDefaultBuilder()
            .ConfigureServices((_, services) =>
            {
                services.AddSignalR();
                services.AddOtOpcUaDriverStatusServices();
                services.AddSingleton<IDbContextFactory<OtOpcUaConfigDbContext>>(
                    new InMemoryConfigDbFactory(Guid.NewGuid().ToString("N")));

                var options = new TelemetryDialOptions { ApiKey = "test-key" };
                if (mode is not null)
                {
                    options.Mode = mode;
                }

                services.AddSingleton<IOptions<TelemetryDialOptions>>(Options.Create(options));

                services.AddAkka("otopcua-test", (ab, _) =>
                {
                    ab.AddHocon(@"
                        akka.actor.provider = ""Akka.Cluster.ClusterActorRefProvider, Akka.Cluster""
                        akka.remote.dot-netty.tcp.hostname = ""127.0.0.1""
                        akka.remote.dot-netty.tcp.port = 0
                        akka.cluster.seed-nodes = []
                        akka.cluster.roles = [""admin""]
                    ", HoconAddMode.Prepend);
                    ab.WithOtOpcUaSignalRBridges();
                });
            })
            .Build();

    /// <summary>An <see cref="IDbContextFactory{TContext}"/> whose contexts share one InMemory database.</summary>
    private sealed class InMemoryConfigDbFactory(string dbName) : IDbContextFactory<OtOpcUaConfigDbContext>
    {
        public OtOpcUaConfigDbContext CreateDbContext() =>
            new(new DbContextOptionsBuilder<OtOpcUaConfigDbContext>()
                .UseInMemoryDatabase(dbName)
                .Options);

        public Task<OtOpcUaConfigDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default) =>
            Task.FromResult(CreateDbContext());
    }
}
