using Akka.Actor;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
using ZB.MOM.WW.ScadaBridge.Host;
using ZB.MOM.WW.ScadaBridge.Host.Actors;

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

/// <summary>
/// Verifies that all expected Central-role actors are created at the correct paths
/// when AkkaHostedService starts.
/// </summary>
[Collection(HostBootCollection.Name)]
public class CentralActorPathTests : IAsyncLifetime
{
    private WebApplicationFactory<Program>? _factory;
    private ActorSystem? _actorSystem;
    private string? _previousEnv;
    private CentralDbTestEnvironment? _dbEnv;

    public Task InitializeAsync()
    {
        _previousEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
        Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", "Central");

        // Supply the pepper, MachineDataDb and the G-4 secret-backed keys
        // (ConfigurationDb / LDAP service-account password / JWT signing key) as
        // whole-key env overrides so the pre-host config builder — which calls
        // AddEnvironmentVariables() before the ${secret:...} expander runs — sees
        // concrete values and the Central-role StartupValidator preflight passes.
        _dbEnv = new CentralDbTestEnvironment();

        _factory = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureAppConfiguration((_, config) =>
                {
                    config.AddInMemoryCollection(new Dictionary<string, string?>
                    {
                        ["ScadaBridge:Node:NodeName"] = "central-a",
                        ["ScadaBridge:Node:NodeHostname"] = "localhost",
                        ["ScadaBridge:Node:RemotingPort"] = "0",
                        ["ScadaBridge:Cluster:SeedNodes:0"] = "akka.tcp://scadabridge@localhost:25510",
                        ["ScadaBridge:Cluster:SeedNodes:1"] = "akka.tcp://scadabridge@localhost:25520",
                        ["ScadaBridge:Cluster:MinNrOfMembers"] = "1",
                        ["ScadaBridge:Database:SkipMigrations"] = "true",
                        ["ScadaBridge:Security:JwtSigningKey"] = "test-signing-key-must-be-at-least-32-chars-long!",
                        // Task 1.4: LDAP settings nest under Security:Ldap (shared LdapOptions).
                        // ServiceAccountDn is now required by the library's LdapOptionsValidator
                        // (ValidateOnStart), so it must be present for the host to start.
                        ["ScadaBridge:Security:Ldap:Server"] = "localhost",
                        ["ScadaBridge:Security:Ldap:Port"] = "3893",
                        ["ScadaBridge:Security:Ldap:Transport"] = "None",
                        ["ScadaBridge:Security:Ldap:AllowInsecure"] = "true",
                        ["ScadaBridge:Security:Ldap:SearchBase"] = "dc=zb,dc=local",
                        ["ScadaBridge:Security:Ldap:ServiceAccountDn"] = "cn=admin,dc=zb,dc=local",
                    });
                });
                builder.UseSetting("ScadaBridge:Node:Role", "Central");
                builder.UseSetting("ScadaBridge:Database:SkipMigrations", "true");
                builder.ConfigureServices(services =>
                {
                    // Replace SQL Server with in-memory database
                    var descriptorsToRemove = services
                        .Where(d =>
                            d.ServiceType == typeof(DbContextOptions<ScadaBridgeDbContext>) ||
                            d.ServiceType == typeof(DbContextOptions) ||
                            d.ServiceType == typeof(ScadaBridgeDbContext) ||
                            d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true)
                        .ToList();
                    foreach (var d in descriptorsToRemove)
                        services.Remove(d);

                    services.AddDbContext<ScadaBridgeDbContext>(options =>
                        options.UseInMemoryDatabase($"ActorPathTests_{Guid.NewGuid()}"));
                });
            });

        // CreateClient triggers host startup including AkkaHostedService
        _ = _factory.CreateClient();

        var akkaService = _factory.Services.GetRequiredService<AkkaHostedService>();
        _actorSystem = akkaService.ActorSystem;
        return Task.CompletedTask;
    }

    public async Task DisposeAsync()
    {
        // try/finally: a throwing host teardown must not strand the process-wide
        // environment variables. Without it, one failed _factory.Dispose() leaks
        // DOTNET_ENVIRONMENT=Central and the CentralDbTestEnvironment vars into
        // every later test in the run.
        try
        {
            _factory?.Dispose();
        }
        finally
        {
            Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", _previousEnv);
            _dbEnv?.Dispose();
        }

        await Task.CompletedTask;
    }

    [Fact]
    public async Task CentralActors_DeadLetterMonitor_Exists()
        => await AssertActorExists("/user/dead-letter-monitor");

    [Fact]
    public async Task CentralActors_CentralCommunication_Exists()
        => await AssertActorExists("/user/central-communication");

    [Fact]
    public async Task CentralActors_Management_Exists()
        => await AssertActorExists("/user/management");

    [Fact]
    public async Task CentralActors_NotificationOutboxSingleton_Exists()
        => await AssertActorExists("/user/notification-outbox-singleton");

    [Fact]
    public async Task CentralActors_NotificationOutboxProxy_Exists()
        => await AssertActorExists("/user/notification-outbox-proxy");

    [Fact]
    public async Task CentralActors_AuditLogPurgeSingleton_Exists()
        => await AssertActorExists("/user/audit-log-purge-singleton");

    [Fact]
    public async Task CentralActors_AuditLogPurgeProxy_Exists()
        => await AssertActorExists("/user/audit-log-purge-proxy");

    [Fact]
    public async Task CentralActors_SiteAuditReconciliationSingleton_Exists()
        => await AssertActorExists("/user/site-audit-reconciliation-singleton");

    [Fact]
    public async Task CentralActors_SiteAuditReconciliationProxy_Exists()
        => await AssertActorExists("/user/site-audit-reconciliation-proxy");

    private async Task AssertActorExists(string path)
    {
        Assert.NotNull(_actorSystem);
        var selection = _actorSystem!.ActorSelection(path);
        var identity = await selection.Ask<ActorIdentity>(
            new Identify(path), TimeSpan.FromSeconds(5));
        Assert.NotNull(identity.Subject);
    }
}

/// <summary>
/// Verifies that all expected Site-role actors are created at the correct paths
/// when AkkaHostedService starts.
/// </summary>
[Collection(HostBootCollection.Name)]
public class SiteActorPathTests : IAsyncLifetime
{
    private IHost? _host;
    private ActorSystem? _actorSystem;
    private string _tempDbPath = null!;
    private string _tempLocalDbPath = null!;

    public async Task InitializeAsync()
    {
        _tempDbPath = Path.Combine(Path.GetTempPath(), $"scadabridge_actor_test_{Guid.NewGuid()}.db");
        _tempLocalDbPath = Path.Combine(Path.GetTempPath(), $"scadabridge_actor_localdb_{Guid.NewGuid()}.db");

        var builder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder();
        builder.ConfigureAppConfiguration(config =>
        {
            config.Sources.Clear();
            config.AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["ScadaBridge:Node:Role"] = "Site",
                ["ScadaBridge:Node:NodeName"] = "node-a",
                ["ScadaBridge:Node:NodeHostname"] = "localhost",
                ["ScadaBridge:Node:SiteId"] = "TestSite",
                ["ScadaBridge:Node:RemotingPort"] = "0",
                ["ScadaBridge:Cluster:SeedNodes:0"] = "akka.tcp://scadabridge@localhost:25510",
                ["ScadaBridge:Cluster:SeedNodes:1"] = "akka.tcp://scadabridge@localhost:25520",
                ["ScadaBridge:Cluster:MinNrOfMembers"] = "1",
                ["ScadaBridge:Database:SiteDbPath"] = _tempDbPath,
                // Required: LocalDbOptions.Path is validated with ValidateOnStart, and this
                // fixture actually starts the host — a site node with no configured
                // consolidated-database path fails fast rather than booting half-working.
                ["LocalDb:Path"] = _tempLocalDbPath,
                // A central gRPC endpoint so the Site branch builds its GrpcCentralTransport
                // (the only site→central transport since Phase 4). Never dialled by this test.
                ["ScadaBridge:Communication:CentralGrpcEndpoints:0"] = "http://localhost:8083",
            });
        });
        builder.ConfigureServices((context, services) =>
        {
            SiteServiceRegistration.Configure(services, context.Configuration);
        });

        _host = builder.Build();
        await _host.StartAsync();

        var akkaService = _host.Services.GetRequiredService<AkkaHostedService>();
        _actorSystem = akkaService.ActorSystem;
    }

    public async Task DisposeAsync()
    {
        if (_host != null)
        {
            await _host.StopAsync();
            _host.Dispose();
        }
        try { File.Delete(_tempDbPath); } catch { /* best effort */ }
        try { File.Delete(_tempLocalDbPath); } catch { /* best effort */ }
    }

    [Fact]
    public async Task SiteActors_DeadLetterMonitor_Exists()
        => await AssertActorExists("/user/dead-letter-monitor");

    [Fact]
    public async Task SiteActors_DclManager_Exists()
        => await AssertActorExists("/user/dcl-manager");

    [Fact]
    public async Task SiteActors_DeploymentManagerSingleton_Exists()
        => await AssertActorExists("/user/deployment-manager-singleton");

    [Fact]
    public async Task SiteActors_DeploymentManagerProxy_Exists()
        => await AssertActorExists("/user/deployment-manager-proxy");

    [Fact]
    public async Task SiteActors_SiteCommunication_Exists()
        => await AssertActorExists("/user/site-communication");

    // SiteActors_CentralClusterClient_Exists was removed with the site→central Akka ClusterClient
    // (the `central-cluster-client` actor) in the ClusterClient→gRPC migration's Phase 4. The site
    // now reaches central over gRPC (GrpcCentralTransport dialling CentralControlService), which is
    // not an actor in the local system, so there is no actor path to assert here.

    private async Task AssertActorExists(string path)
    {
        Assert.NotNull(_actorSystem);
        var selection = _actorSystem!.ActorSelection(path);
        var identity = await selection.Ask<ActorIdentity>(
            new Identify(path), TimeSpan.FromSeconds(5));
        Assert.NotNull(identity.Subject);
    }
}
