using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Auth;

namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog;

public sealed partial class RouteHandlerTests
{
    [Fact] // T:2796
    public void RouteConfig_ShouldSucceed()
    {
        var options = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseCluster(
            Map(
                ("name", "abc"),
                ("listen", "127.0.0.1:4244"),
                ("authorization", Map(("user", "route_user"), ("password", "top_secret"), ("timeout", 1L))),
                ("no_advertise", true),
                ("connect_retries", 2L),
                ("connect_backoff", true),
                ("routes", Arr("nats-route://foo:bar@127.0.0.1:4245", "nats-route://foo:bar@127.0.0.1:4246"))),
            options,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        options.Cluster.Name.ShouldBe("abc");
        options.Cluster.Host.ShouldBe("127.0.0.1");
        options.Cluster.Port.ShouldBe(4244);
        options.Cluster.Username.ShouldBe("route_user");
        options.Cluster.Password.ShouldBe("top_secret");
        options.Cluster.NoAdvertise.ShouldBeTrue();
        options.Cluster.ConnectRetries.ShouldBe(2);
        options.Cluster.ConnectBackoff.ShouldBeTrue();
        options.Routes.Count.ShouldBe(2);
    }

    [Fact] // T:2797
    public void ClusterAdvertise_ShouldSucceed()
    {
        var options = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseCluster(
            Map(("listen", "127.0.0.1:6222"), ("advertise", "127.0.0.1:7222")),
            options,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        options.Cluster.Advertise.ShouldBe("127.0.0.1:7222");
    }

    [Fact] // T:2799
    public void ClientAdvertise_ShouldSucceed()
    {
        var options = new ServerOptions();
        var error = options.ProcessConfigString("""
        {
          "client_advertise": "me:1"
        }
        """);

        error.ShouldBeNull();
        options.ClientAdvertise.ShouldBe("me:1");
    }

    [Fact] // T:2800
    public void ServerRoutesWithClients_ShouldSucceed()
    {
        var merged = ServerOptions.MergeOptions(
            new ServerOptions { Routes = [new Uri("nats://127.0.0.1:4245")] },
            new ServerOptions { RoutesStr = "nats://127.0.0.1:4245, nats://127.0.0.1:4246" });

        merged.RoutesStr.ShouldBe("nats://127.0.0.1:4245, nats://127.0.0.1:4246");
        merged.Routes.Count.ShouldBe(2);
    }

    [Fact] // T:2801
    public void ServerRoutesWithAuthAndBCrypt_ShouldSucceed()
    {
        var (auth, error) = ServerOptions.ParseAuthorization(
            Map(("users", Arr(
                Map(("user", "derek"), ("password", "$2a$11$abcdefghijklmnopqrstuv")),
                Map(("user", "alice"), ("password", "$2a$11$zyxwvutsrqponmlkjihgfe"))))));

        error.ShouldBeNull();
        auth.ShouldNotBeNull();
        auth.Users.Count.ShouldBe(2);
        auth.Users[0].Password.ShouldStartWith("$2a$11$");
    }

    [Fact] // T:2802
    public void SeedSolicitWorks_ShouldSucceed()
    {
        var routes = ServerOptions.RoutesFromStr("nats://127.0.0.1:7244");
        routes.Count.ShouldBe(1);
        routes[0].Host.ShouldBe("127.0.0.1");
        routes[0].Port.ShouldBe(7244);
    }

    [Fact] // T:2803
    public void TLSSeedSolicitWorks_ShouldSucceed()
    {
        var (tlsOptions, parseError) = ServerOptions.ParseTLS(
            Map(("verify", true), ("timeout", 2L)),
            isClientCtx: false);

        parseError.ShouldBeNull();
        tlsOptions.ShouldNotBeNull();
        tlsOptions.Verify.ShouldBeTrue();
        tlsOptions.Timeout.ShouldBe(2d);
    }

    [Fact] // T:2804
    public void ChainedSolicitWorks_ShouldSucceed()
    {
        var routes = ServerOptions.RoutesFromStr(
            "nats://127.0.0.1:7244, nats://127.0.0.1:7245, nats://127.0.0.1:7246");
        routes.Count.ShouldBe(3);
    }

    [Fact] // T:2805
    public void TLSChainedSolicitWorks_ShouldSucceed()
    {
        var (tlsOptions, parseError) = ServerOptions.ParseTLS(
            Map(("verify", true), ("timeout", "3s")),
            isClientCtx: false);

        parseError.ShouldBeNull();
        tlsOptions.ShouldNotBeNull();
        tlsOptions.Timeout.ShouldBe(3d);
    }

    [Fact] // T:2806
    public void RouteTLSHandshakeError_ShouldSucceed()
    {
        var (tlsOptions, parseError) = ServerOptions.ParseTLS(
            Map(("cipher_suites", Arr("TLS_RSA_WITH_RC4_128_SHA"))),
            isClientCtx: false);

        tlsOptions.ShouldBeNull();
        parseError.ShouldNotBeNull();
        parseError.Message.ShouldContain("insecure cipher suites configured");
    }

    [Fact] // T:2813
    public void RoutePermsAppliedOnInboundAndOutboundRoute_ShouldSucceed()
    {
        var cluster = new ClusterOpts();
        var permissions = new Permissions
        {
            Publish = new SubjectPermission { Allow = ["imp.foo"], Deny = ["imp.bar"] },
            Subscribe = new SubjectPermission { Allow = ["exp.foo"], Deny = ["exp.bar"] },
        };

        ServerOptions.SetClusterPermissions(cluster, permissions);

        cluster.Permissions.ShouldNotBeNull();
        cluster.Permissions.Import.ShouldNotBeNull();
        cluster.Permissions.Export.ShouldNotBeNull();
        cluster.Permissions.Import.Allow.ShouldContain("imp.foo");
        cluster.Permissions.Import.Deny.ShouldContain("imp.bar");
        cluster.Permissions.Export.Allow.ShouldContain("exp.foo");
        cluster.Permissions.Export.Deny.ShouldContain("exp.bar");
    }

    private static Dictionary<string, object?> Map(params (string Key, object? Value)[] entries)
    {
        var map = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
        foreach (var (key, value) in entries)
            map[key] = value;
        return map;
    }

    private static List<object?> Arr(params object?[] entries) => [.. entries];
}
