// Tests for account import/export cycle detection.
// Go reference: accounts_test.go TestAccountImportCycleDetection.

using NATS.Server.Auth;
using NATS.Server.Imports;

namespace NATS.Server.Auth.Tests.Auth;

public class AccountImportExportTests
{
    private static Account CreateAccount(string name) => new(name);

    private static void SetupServiceExport(Account exporter, string subject, IEnumerable<Account>? approved = null)
    {
        exporter.AddServiceExport(subject, ServiceResponseType.Singleton, approved);
    }

    [Fact]
    public void AddServiceImport_NoCycle_Succeeds()
    {
        // A exports "svc.foo", B imports from A — no cycle
        var a = CreateAccount("A");
        var b = CreateAccount("B");

        SetupServiceExport(a, "svc.foo"); // public export (no approved list)

        var import = b.AddServiceImport(a, "svc.foo", "svc.foo");

        import.ShouldNotBeNull();
        import.DestinationAccount.Name.ShouldBe("A");
        import.From.ShouldBe("svc.foo");
        b.Imports.Services.ShouldContainKey("svc.foo");
    }

    [Fact]
    public void AddServiceImport_DirectCycle_Throws()
    {
        // A exports "svc.foo", B exports "svc.bar"
        // B imports "svc.foo" from A (ok)
        // A imports "svc.bar" from B — creates cycle A->B->A
        var a = CreateAccount("A");
        var b = CreateAccount("B");

        SetupServiceExport(a, "svc.foo");
        SetupServiceExport(b, "svc.bar");

        b.AddServiceImport(a, "svc.foo", "svc.foo");

        Should.Throw<InvalidOperationException>(() => a.AddServiceImport(b, "svc.bar", "svc.bar"))
            .Message.ShouldContain("cycle");
    }

    [Fact]
    public void AddServiceImport_IndirectCycle_A_B_C_A_Throws()
    {
        // A->B->C, then C->A creates indirect cycle
        var a = CreateAccount("A");
        var b = CreateAccount("B");
        var c = CreateAccount("C");

        SetupServiceExport(a, "svc.a");
        SetupServiceExport(b, "svc.b");
        SetupServiceExport(c, "svc.c");

        // B imports from A
        b.AddServiceImport(a, "svc.a", "svc.a");
        // C imports from B
        c.AddServiceImport(b, "svc.b", "svc.b");
        // A imports from C — would create C->B->A->C cycle
        Should.Throw<InvalidOperationException>(() => a.AddServiceImport(c, "svc.c", "svc.c"))
            .Message.ShouldContain("cycle");
    }

    [Fact]
    public void DetectCycle_NoCycle_ReturnsFalse()
    {
        var a = CreateAccount("A");
        var b = CreateAccount("B");
        var c = CreateAccount("C");

        SetupServiceExport(a, "svc.a");
        SetupServiceExport(b, "svc.b");

        // A imports from B, B imports from C — linear chain, no cycle back to A
        // For this test we manually add imports without cycle check via ImportMap
        b.Imports.AddServiceImport(new ServiceImport
        {
            DestinationAccount = a,
            From = "svc.a",
            To = "svc.a",
        });

        // Check: does following imports from A lead back to C? No.
        AccountImportExport.DetectCycle(a, c).ShouldBeFalse();
    }

    [Fact]
    public void DetectCycle_DirectCycle_ReturnsTrue()
    {
        var a = CreateAccount("A");
        var b = CreateAccount("B");

        // A has import pointing to B
        a.Imports.AddServiceImport(new ServiceImport
        {
            DestinationAccount = b,
            From = "svc.x",
            To = "svc.x",
        });

        // Does following from A lead to B? Yes.
        AccountImportExport.DetectCycle(a, b).ShouldBeTrue();
    }

    [Fact]
    public void DetectCycle_IndirectCycle_ReturnsTrue()
    {
        var a = CreateAccount("A");
        var b = CreateAccount("B");
        var c = CreateAccount("C");

        // A -> B -> C (imports)
        a.Imports.AddServiceImport(new ServiceImport
        {
            DestinationAccount = b,
            From = "svc.1",
            To = "svc.1",
        });
        b.Imports.AddServiceImport(new ServiceImport
        {
            DestinationAccount = c,
            From = "svc.2",
            To = "svc.2",
        });

        // Does following from A lead to C? Yes, via B.
        AccountImportExport.DetectCycle(a, c).ShouldBeTrue();
    }

    [Fact]
    public void RemoveServiceImport_ExistingImport_Succeeds()
    {
        var a = CreateAccount("A");
        var b = CreateAccount("B");

        SetupServiceExport(a, "svc.foo");
        b.AddServiceImport(a, "svc.foo", "svc.foo");

        b.Imports.Services.ShouldContainKey("svc.foo");

        b.RemoveServiceImport("svc.foo").ShouldBeTrue();
        b.Imports.Services.ShouldNotContainKey("svc.foo");

        // Removing again returns false
        b.RemoveServiceImport("svc.foo").ShouldBeFalse();
    }

    [Fact]
    public void RemoveStreamImport_ExistingImport_Succeeds()
    {
        var a = CreateAccount("A");
        var b = CreateAccount("B");

        a.AddStreamExport("stream.data", null); // public
        b.AddStreamImport(a, "stream.data", "imported.data");

        b.Imports.Streams.Count.ShouldBe(1);

        b.RemoveStreamImport("stream.data").ShouldBeTrue();
        b.Imports.Streams.Count.ShouldBe(0);

        // Removing again returns false
        b.RemoveStreamImport("stream.data").ShouldBeFalse();
    }

    [Fact]
    public void ValidateImport_UnauthorizedAccount_Throws()
    {
        var exporter = CreateAccount("Exporter");
        var importer = CreateAccount("Importer");
        var approved = CreateAccount("Approved");

        // Export only approves "Approved" account, not "Importer"
        SetupServiceExport(exporter, "svc.restricted", [approved]);

        Should.Throw<UnauthorizedAccessException>(
            () => AccountImportExport.ValidateImport(importer, exporter, "svc.restricted"))
            .Message.ShouldContain("not authorized");
    }

    [Fact]
    public void AddStreamImport_NoCycleCheck_Succeeds()
    {
        // Stream imports do not require cycle detection (unlike service imports).
        // Even with a "circular" stream import topology, it should succeed.
        var a = CreateAccount("A");
        var b = CreateAccount("B");

        a.AddStreamExport("stream.a", null);
        b.AddStreamExport("stream.b", null);

        // B imports stream from A
        b.AddStreamImport(a, "stream.a", "imported.a");

        // A imports stream from B — no cycle check for streams
        a.AddStreamImport(b, "stream.b", "imported.b");

        a.Imports.Streams.Count.ShouldBe(1);
        b.Imports.Streams.Count.ShouldBe(1);
    }
}
