package acl

import (
	"fmt"
	"sync"
	"testing"
)

func TestGrantAndCheck(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	a.Grant(id, "test", PermRead)
	if !a.Check(id, "test", PermRead) {
		t.Errorf("Check(Read) = false, want true after Grant(Read)")
	}
	if a.Check(id, "test", PermWrite) {
		t.Errorf("Check(Write) = true, want false (only Read granted)")
	}
}

func TestRevoke(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	a.Grant(id, "test", PermRead)
	a.Revoke(id, "test")
	if a.Check(id, "test", PermRead) {
		t.Errorf("Check(Read) = true after Revoke, want false")
	}
	if got := a.List(); len(got) != 0 {
		t.Errorf("List() len = %d after Revoke, want 0", len(got))
	}
}

func TestRevokeNonExistentNoOp(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	a.Revoke(id, "ghost")
	if got := a.List(); len(got) != 0 {
		t.Errorf("List() len = %d after no-op Revoke, want 0", len(got))
	}
}

func TestDenyByDefault(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	if a.Check(id, "test", PermRead) {
		t.Errorf("Check on un-granted identity = true, want false (deny-by-default)")
	}
	if a.Check(id, "test", PermWrite) {
		t.Errorf("Check Write on un-granted identity = true, want false")
	}
	if a.Check(id, "test", PermAdmin) {
		t.Errorf("Check Admin on un-granted identity = true, want false")
	}
}

func TestNamespaceIsolation(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "ns-A"}
	a.Grant(id, "ns-A", PermRead)
	if !a.Check(id, "ns-A", PermRead) {
		t.Errorf("Check on ns-A = false, want true")
	}
	if a.Check(id, "ns-B", PermRead) {
		t.Errorf("Check on ns-B = true, want false (namespace isolation)")
	}
}

func TestGrantReplacesPermissions(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	a.Grant(id, "test", PermRead)
	a.Grant(id, "test", PermWrite)
	if a.Check(id, "test", PermRead) {
		t.Errorf("Check(Read) = true after re-grant with Write-only, want false")
	}
	if !a.Check(id, "test", PermWrite) {
		t.Errorf("Check(Write) = false after re-grant, want true")
	}
	if got := a.List(); len(got) != 1 {
		t.Errorf("List() len = %d, want 1 (grant replaces, not appends)", len(got))
	}
}

func TestSpiffeNamespace(t *testing.T) {
	got, err := SpiffeNamespace("spiffe://orca.local/ns/myapp/sa/svc1/alloc-123")
	if err != nil {
		t.Fatalf("SpiffeNamespace: %v", err)
	}
	if got != "myapp" {
		t.Errorf("SpiffeNamespace = %q, want %q", got, "myapp")
	}
}

func TestSpiffeNamespace_OtherTrustDomain(t *testing.T) {
	got, err := SpiffeNamespace("spiffe://example.com/ns/prod/sa/api/0")
	if err != nil {
		t.Fatalf("SpiffeNamespace: %v", err)
	}
	if got != "prod" {
		t.Errorf("SpiffeNamespace = %q, want %q", got, "prod")
	}
}

func TestSpiffeNamespace_Malformed(t *testing.T) {
	cases := []string{
		"https://orca.local/ns/prod/sa/api/0",
		"spiffe://orca.local/ns/prod/api/0",
		"spiffe://orca.local/ns/prod/sa/api",
		"spiffe://orca.local/ns//sa/api/0",
		":::not-a-uri",
	}
	for _, c := range cases {
		if _, err := SpiffeNamespace(c); err == nil {
			t.Errorf("SpiffeNamespace(%q): expected error, got nil", c)
		}
	}
}

func TestPermissionsDistinct(t *testing.T) {
	if PermRead == PermWrite || PermRead == PermAdmin || PermWrite == PermAdmin {
		t.Errorf("permission flags collide: read=%d write=%d admin=%d", PermRead, PermWrite, PermAdmin)
	}
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	a.Grant(id, "test", PermRead|PermWrite)
	if !a.Check(id, "test", PermRead) {
		t.Errorf("Check(Read) for read+write grant = false, want true")
	}
	if !a.Check(id, "test", PermWrite) {
		t.Errorf("Check(Write) for read+write grant = false, want true")
	}
	if a.Check(id, "test", PermAdmin) {
		t.Errorf("Check(Admin) for read+write grant = true, want false")
	}
}

func TestAdminImpliesReadAndWrite(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	a.Grant(id, "test", PermAdmin)
	if !a.Check(id, "test", PermAdmin) {
		t.Errorf("Check(Admin) = false, want true")
	}
	if !a.Check(id, "test", PermRead) {
		t.Errorf("Check(Read) for admin grant = false, want true (admin implies read)")
	}
	if !a.Check(id, "test", PermWrite) {
		t.Errorf("Check(Write) for admin grant = false, want true (admin implies write)")
	}
}

func TestConcurrentAccess(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-concurrent", Namespace: "ns"}
	const n = 200
	var wg sync.WaitGroup
	wg.Add(n * 3)
	for i := 0; i < n; i++ {
		go func() {
			defer wg.Done()
			a.Grant(id, "ns", PermRead|PermWrite)
		}()
		go func() {
			defer wg.Done()
			a.Check(id, "ns", PermRead)
		}()
		go func() {
			defer wg.Done()
			a.List()
		}()
	}
	wg.Wait()
	if !a.Check(id, "ns", PermRead) {
		t.Errorf("Check(Read) after concurrent grants = false, want true")
	}
	if got := a.List(); len(got) != 1 {
		t.Errorf("List() len = %d, want 1 (concurrent grants replace, not append)", len(got))
	}
}

func TestListIsCopy(t *testing.T) {
	a := NewACL()
	id := Identity{Kind: KindOidc, ID: "tok-A", Namespace: "test"}
	a.Grant(id, "test", PermRead)
	lst := a.List()
	lst[0].Permissions = PermAdmin
	if a.Check(id, "test", PermAdmin) {
		t.Errorf("mutating List() result leaked into ACL: %v", a.List())
	}
}

func TestSpiffeIdentityGrant(t *testing.T) {
	a := NewACL()
	uri := "spiffe://orca.local/ns/myapp/sa/svc1/alloc-123"
	ns, err := SpiffeNamespace(uri)
	if err != nil {
		t.Fatalf("SpiffeNamespace: %v", err)
	}
	id := Identity{Kind: KindSpiffe, ID: uri, Namespace: ns}
	a.Grant(id, ns, PermRead|PermWrite)
	if !a.Check(id, ns, PermRead) || !a.Check(id, ns, PermWrite) {
		t.Errorf("spiffe identity check failed for ns=%s", ns)
	}
}

func TestTokenAndSpiffeIdentitiesIndependent(t *testing.T) {
	a := NewACL()
	uri := "spiffe://orca.local/ns/prod/sa/api/0"
	spiffeID := Identity{Kind: KindSpiffe, ID: uri, Namespace: "prod"}
	tokenID := Identity{Kind: KindOidc, ID: "operator-1", Namespace: "prod"}
	a.Grant(spiffeID, "prod", PermRead)
	if a.Check(tokenID, "prod", PermRead) {
		t.Errorf("token identity matched spiffe grant (kind isolation broken)")
	}
	if !a.Check(spiffeID, "prod", PermRead) {
		t.Errorf("spiffe identity check failed")
	}
	if got := a.List(); len(got) != 1 {
		t.Errorf("List() len = %d, want 1", len(got))
	}
}

func TestAllPermissionsConstant(t *testing.T) {
	if AllPermissions != PermRead|PermWrite|PermAdmin {
		t.Errorf("AllPermissions = %d, want %d", AllPermissions, PermRead|PermWrite|PermAdmin)
	}
}

func ExampleSpiffeNamespace() {
	ns, _ := SpiffeNamespace("spiffe://orca.local/ns/myapp/sa/svc1/alloc-123")
	fmt.Println(ns)
	// Output: myapp
}

// --- REQ-145 / F1 ACL OIDC rewrite tests ---

// TestACLOidcUserGrant verifies an OIDC user (by sub) can be granted
// and checked.
func TestACLOidcUserGrant(t *testing.T) {
	a := NewACL()
	claims := OIDCClaims{Subject: "user-1", Groups: []string{"devs"}}
	a.Grant(OidcIdentity(claims), "prod", PermWrite|PermRead)
	if !a.CheckOidc(claims, "prod", PermWrite) {
		t.Error("CheckOidc should allow write")
	}
	if !a.CheckOidc(claims, "prod", PermRead) {
		t.Error("CheckOidc should allow read (explicit)")
	}
	if a.CheckOidc(claims, "prod", PermAdmin) {
		t.Error("CheckOidc should deny admin")
	}
	if a.CheckOidc(claims, "other", PermRead) {
		t.Error("CheckOidc should deny on wrong ns")
	}
}

// TestACLOidcGroupGrant verifies group-based grants work.
func TestACLOidcGroupGrant(t *testing.T) {
	a := NewACL()
	a.Grant(OidcGroupIdentity("orca-admins"), "prod", PermAdmin)
	claims := OIDCClaims{Subject: "user-2", Groups: []string{"orca-admins"}}
	if !a.CheckOidc(claims, "prod", PermAdmin) {
		t.Error("admin group should have admin")
	}
	if !a.CheckOidc(claims, "prod", PermWrite) {
		t.Error("admin implies write")
	}
	claimsNoGroup := OIDCClaims{Subject: "user-3", Groups: []string{"devs"}}
	if a.CheckOidc(claimsNoGroup, "prod", PermRead) {
		t.Error("non-admin group should deny")
	}
}

// TestACLOidcDenyByDefault verifies an ungranted OIDC user is denied.
func TestACLOidcDenyByDefault(t *testing.T) {
	a := NewACL()
	claims := OIDCClaims{Subject: "nobody"}
	if a.CheckOidc(claims, "prod", PermRead) {
		t.Error("ungranted user should deny")
	}
}

// TestACLTokenDeprecated verifies KindToken always denies (R-021).
func TestACLTokenDeprecated(t *testing.T) {
	a := NewACL()
	// Even if an old acl.json has a KindToken entry, Check returns false.
	a.Grant(Identity{Kind: KindToken, ID: "old-token-123"}, "prod", PermAdmin)
	if a.Check(Identity{Kind: KindToken, ID: "old-token-123"}, "prod", PermRead) {
		t.Error("KindToken should always deny (R-021)")
	}
	if a.Check(Identity{Kind: KindToken, ID: "old-token-123"}, "prod", PermAdmin) {
		t.Error("KindToken should always deny even admin (R-021)")
	}
}
