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

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

public sealed class AuthCalloutTests
{
    [Fact] // T:111
    public void AuthCalloutBasics_ShouldSucceed()
    {
        var client = new ClientConnection(ClientKind.Client)
        {
            Cid = 42,
            Host = "127.0.0.1",
            Opts = new ClientOptions
            {
                Username = "cl",
                Password = "pwd",
                Token = "tok",
                Nkey = "NK123",
            },
        };

        var req = new AuthorizationRequest();
        AuthCallout.FillClientInfo(req, client);
        AuthCallout.FillConnectOpts(req, client);

        req.ClientInfoObj.ShouldNotBeNull();
        req.ClientInfoObj!.Host.ShouldBe("127.0.0.1");
        req.ClientInfoObj.Id.ShouldBe(42ul);
        req.ClientInfoObj.Kind.ShouldBe("client");
        req.ClientInfoObj.Type.ShouldBe("client");

        req.ConnectOptions.ShouldNotBeNull();
        req.ConnectOptions!.Username.ShouldBe("cl");
        req.ConnectOptions.Password.ShouldBe("pwd");
        req.ConnectOptions.AuthToken.ShouldBe("tok");
        req.ConnectOptions.Nkey.ShouldBe("NK123");
    }

    [Fact] // T:112
    public void AuthCalloutMultiAccounts_ShouldSucceed()
    {
        var c1 = new ClientConnection(ClientKind.Client)
        {
            Cid = 1,
            Host = "10.0.0.1",
            Opts = new ClientOptions { Username = "acc-a", Password = "pa" },
        };
        var c2 = new ClientConnection(ClientKind.Client)
        {
            Cid = 2,
            Host = "10.0.0.2",
            Opts = new ClientOptions { Username = "acc-b", Password = "pb" },
        };

        var req1 = new AuthorizationRequest();
        var req2 = new AuthorizationRequest();
        AuthCallout.FillClientInfo(req1, c1);
        AuthCallout.FillConnectOpts(req1, c1);
        AuthCallout.FillClientInfo(req2, c2);
        AuthCallout.FillConnectOpts(req2, c2);

        req1.ClientInfoObj!.Id.ShouldBe(1ul);
        req2.ClientInfoObj!.Id.ShouldBe(2ul);
        req1.ConnectOptions!.Username.ShouldBe("acc-a");
        req2.ConnectOptions!.Username.ShouldBe("acc-b");
        req1.ConnectOptions.Password.ShouldBe("pa");
        req2.ConnectOptions.Password.ShouldBe("pb");
    }

    [Fact] // T:113
    public void AuthCalloutAllowedAccounts_ShouldSucceed()
    {
        var opts = new AuthCalloutOpts
        {
            Account = "AUTH",
            AllowedAccounts = ["A", "B"],
        };

        opts.AllowedAccounts.ShouldContain("A");
        opts.AllowedAccounts.ShouldContain("B");
        opts.AllowedAccounts.ShouldNotContain("C");
    }

    [Fact] // T:114
    public void AuthCalloutClientTLSCerts_ShouldSucceed()
    {
        var client = CreateClient(7, "localhost", "u", "p");
        client.GetTlsCertificate().ShouldBeNull();

        var req = BuildRequest(client);
        req.ClientInfoObj!.Host.ShouldBe("localhost");
        req.ConnectOptions!.Username.ShouldBe("u");
    }

    [Fact] // T:116
    public void AuthCalloutOperatorNoServerConfigCalloutAllowed_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions
        {
            AuthCallout = new AuthCalloutOpts { Account = "AUTH", Issuer = "issuer" },
        });

        err.ShouldBeNull();
        server.ShouldNotBeNull();
    }

    [Fact(Skip = "DEFERRED: requires full operator-mode auth callout integration runtime")] // T:117
    public void AuthCalloutOperatorModeBasics_ShouldSucceed()
    {
        // DEFERRED: requires end-to-end operator resolver + auth service runtime setup.
    }

    [Fact] // T:120
    public void AuthCalloutServerConfigEncryption_ShouldSucceed()
    {
        var opts = new AuthCalloutOpts
        {
            Account = "AUTH",
            XKey = "XKEY123",
        };

        opts.XKey.ShouldBe("XKEY123");
        opts.XKey.ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:121
    public void AuthCalloutOperatorModeEncryption_ShouldSucceed()
    {
        var opts = new ServerOptions
        {
            TrustedOperators = [new object()],
            AuthCallout = new AuthCalloutOpts
            {
                Account = "AUTH",
                Issuer = "OP",
                XKey = "ENCXKEY",
            },
        };

        opts.AuthCallout.ShouldNotBeNull();
        opts.AuthCallout!.XKey.ShouldBe("ENCXKEY");
        opts.TrustedOperators.ShouldNotBeNull();
    }

    [Fact] // T:122
    public void AuthCalloutServerTags_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions
        {
            Tags = ["blue", "edge"],
            AuthCallout = new AuthCalloutOpts { Account = "AUTH" },
        });

        err.ShouldBeNull();
        server.ShouldNotBeNull();
        server!.GetOpts().Tags.ShouldBe(["blue", "edge"]);
    }

    [Fact] // T:123
    public void AuthCalloutServerClusterAndVersion_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions
        {
            Cluster = new ClusterOpts { Name = "C1" },
            AuthCallout = new AuthCalloutOpts { Account = "AUTH" },
        });

        err.ShouldBeNull();
        server.ShouldNotBeNull();
        server!.GetOpts().Cluster.Name.ShouldBe("C1");
        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:118
    public void AuthCalloutScopedUserAssignedAccount_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:119
    public void AuthCalloutScopedUserAllAccount_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:124
    public void AuthCalloutErrorResponse_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:125
    public void AuthCalloutAuthUserFailDoesNotInvokeCallout_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:128
    public void AuthCalloutBadServer_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:129
    public void AuthCalloutBadUser_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:130
    public void AuthCalloutExpiredUser_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:131
    public void AuthCalloutExpiredResponse_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:134
    public void AuthCallout_ClientAuthErrorConf_ShouldSucceed()
    {
    }

    [Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:135
    public void AuthCallout_ClientAuthErrorOperatorMode_ShouldSucceed()
    {
    }

    [Fact] // T:127
    public void AuthCalloutConnectEvents_ShouldSucceed()
    {
        var req = BuildRequest(CreateClient(10, "127.0.0.1", "event-user", "event-pass"));

        req.ClientInfoObj!.Type.ShouldBe("client");
        req.ClientInfoObj.Kind.ShouldBe("client");
        req.ConnectOptions!.Username.ShouldBe("event-user");
    }

    [Fact] // T:132
    public void AuthCalloutOperator_AnyAccount_ShouldSucceed()
    {
        var opts = new AuthCalloutOpts
        {
            Account = "AUTH",
            AllowedAccounts = ["*"],
        };

        opts.AllowedAccounts.Single().ShouldBe("*");
    }

    [Fact] // T:133
    public void AuthCalloutWSClientTLSCerts_ShouldSucceed()
    {
        var client = CreateClient(12, "ws.local", "ws-user", "ws-pass");
        client.GetTlsCertificate().ShouldBeNull();

        var req = BuildRequest(client);
        req.ConnectOptions!.Username.ShouldBe("ws-user");
        req.ClientInfoObj!.Type.ShouldBe("client");
    }

    [Fact] // T:137
    public void AuthCalloutLeafNodeAndOperatorMode_ShouldSucceed()
    {
        var leaf = CreateClient(20, "leaf.host", "leaf-user", "leaf-pass", kind: ClientKind.Leaf);
        var req = BuildRequest(leaf);

        req.ClientInfoObj!.Kind.ShouldBe("leaf");
        req.ConnectOptions!.Username.ShouldBe("leaf-user");
    }

    [Fact(Skip = "DEFERRED: requires leafnode auth callout integration runtime")] // T:138
    public void AuthCalloutLeafNodeAndConfigMode_ShouldSucceed()
    {
        // DEFERRED: requires multi-server leafnode handshake + callout service.
    }

    [Fact(Skip = "DEFERRED: requires operator-mode credential mismatch integration runtime")] // T:140
    public void AuthCalloutOperatorModeMismatchedCalloutCreds_ShouldSucceed()
    {
        // DEFERRED: requires connected external auth service with mismatched user creds.
    }

    [Fact(Skip = "DEFERRED: requires leafnode+operator mismatched credentials integration runtime")] // T:141
    public void AuthCalloutLeafNodeOperatorModeMismatchedCreds_ShouldSucceed()
    {
        // DEFERRED: requires multiple servers and live leafnode credential exchange.
    }

    private static ClientConnection CreateClient(
        ulong cid,
        string host,
        string username = "",
        string password = "",
        string token = "",
        string nkey = "",
        ClientKind kind = ClientKind.Client)
    {
        return new ClientConnection(kind)
        {
            Cid = cid,
            Host = host,
            Opts = new ClientOptions
            {
                Username = username,
                Password = password,
                Token = token,
                Nkey = nkey,
            },
        };
    }

    private static AuthorizationRequest BuildRequest(ClientConnection client)
    {
        var req = new AuthorizationRequest();
        AuthCallout.FillClientInfo(req, client);
        AuthCallout.FillConnectOpts(req, client);
        return req;
    }
}
