package cli

import (
	"bytes"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"git.cloudinit.dev/coreci/orca/internal/paths"
)

func resetACLFlags() {
	aclGrantNamespace = ""
	aclGrantPermissions = "read"
	aclRevokeNamespace = ""
	aclCheckNamespace = ""
	aclCheckPermission = "read"
}

func TestACLCommandRegistered(t *testing.T) {
	registered := make(map[string]bool)
	for _, cmd := range rootCmd.Commands() {
		registered[cmd.Name()] = true
	}
	if !registered["acl"] {
		t.Fatal("acl command not registered on root")
	}
}

func TestACLSubcommands(t *testing.T) {
	expected := []string{"grant", "revoke", "list", "check"}
	registered := make(map[string]bool)
	for _, cmd := range aclCmd.Commands() {
		registered[cmd.Name()] = true
	}
	for _, name := range expected {
		if !registered[name] {
			t.Errorf("expected acl subcommand %q not registered", name)
		}
	}
}

func TestParseIdentity_Spiffe(t *testing.T) {
	id, err := parseIdentity("spiffe://orca.local/ns/myapp/sa/svc1/alloc-1")
	if err != nil {
		t.Fatalf("parseIdentity: %v", err)
	}
	if id.Kind != "spiffe" {
		t.Errorf("kind = %q, want spiffe", id.Kind)
	}
	if id.Namespace != "myapp" {
		t.Errorf("namespace = %q, want myapp", id.Namespace)
	}
}

func TestParseIdentity_Oidc(t *testing.T) {
	id, err := parseIdentity("operator-1")
	if err != nil {
		t.Fatalf("parseIdentity: %v", err)
	}
	if id.Kind != "oidc" {
		t.Errorf("kind = %q, want oidc", id.Kind)
	}
	if id.ID != "operator-1" {
		t.Errorf("id = %q, want operator-1", id.ID)
	}
	if id.Namespace != "" {
		t.Errorf("namespace = %q, want empty (set via --namespace)", id.Namespace)
	}
}

func TestParseIdentity_Empty(t *testing.T) {
	if _, err := parseIdentity(""); err == nil {
		t.Errorf("parseIdentity(\"\"): expected error, got nil")
	}
}

func TestParsePermissions(t *testing.T) {
	cases := []struct {
		in   string
		want uint8
	}{
		{"", 1},
		{"read", 1},
		{"write", 2},
		{"admin", 4},
		{"read,write", 3},
		{"read,write,admin", 7},
		{"READ,Write", 3},
	}
	for _, c := range cases {
		got, err := parsePermissions(c.in)
		if err != nil {
			t.Errorf("parsePermissions(%q): unexpected err %v", c.in, err)
			continue
		}
		if uint8(got) != c.want {
			t.Errorf("parsePermissions(%q) = %d, want %d", c.in, uint8(got), c.want)
		}
	}
}

func TestParsePermissions_Unknown(t *testing.T) {
	if _, err := parsePermissions("read,delete"); err == nil {
		t.Errorf("parsePermissions(read,delete): expected error, got nil")
	}
}

func TestACLGrantAndCheck(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read,write"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("acl grant: %v", err)
	}

	if _, err := os.Stat(paths.ACLPath()); err != nil {
		t.Fatalf("acl.json not written: %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("acl check read: %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "admin"})
	err := rootCmd.Execute()
	if err == nil {
		t.Fatalf("acl check admin: expected denied error, got nil")
	}
}

func TestACLCheckDeniedExits1(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "check", "ghost", "--namespace", "prod", "--permission", "read"})
	err := rootCmd.Execute()
	if err == nil {
		t.Fatal("expected denied error for un-granted identity, got nil")
	}
}

func TestACLRevoke(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant: %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "revoke", "operator-1", "--namespace", "prod"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("revoke: %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "read"})
	if err := rootCmd.Execute(); err == nil {
		t.Fatalf("check after revoke: expected denied, got nil")
	}
}

func TestACLListEmpty(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetArgs([]string{"acl", "list"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("acl list empty: %v", err)
	}
	if !strings.Contains(buf.String(), "No ACL entries") {
		t.Errorf("acl list empty: %s", buf.String())
	}
}

func TestACLListWithEntries(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read,write"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant: %v", err)
	}
	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "grant", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--namespace", "myapp", "--permissions", "admin"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant spiffe: %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetArgs([]string{"acl", "list"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("acl list: %v", err)
	}
	out := buf.String()
	if !strings.Contains(out, "operator-1") || !strings.Contains(out, "prod") {
		t.Errorf("list missing operator-1/prod: %s", out)
	}
	if !strings.Contains(out, "spiffe://orca.local/ns/myapp") || !strings.Contains(out, "myapp") {
		t.Errorf("list missing spiffe entry: %s", out)
	}
	if !strings.Contains(out, "read,write") || !strings.Contains(out, "admin") {
		t.Errorf("list missing permissions: %s", out)
	}
}

func TestACLListJSON(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant: %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetArgs([]string{"acl", "list", "--json"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("acl list --json: %v", err)
	}
	var entries []map[string]any
	if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &entries); err != nil {
		t.Fatalf("unmarshal: %v\n%s", err, buf.String())
	}
	if len(entries) != 1 {
		t.Fatalf("entries len = %d, want 1", len(entries))
	}
	id, _ := entries[0]["identity"].(map[string]any)
	if id == nil || id["id"] != "operator-1" {
		t.Errorf("identity = %v, want operator-1", entries[0]["identity"])
	}
}

func TestACLGrantSpiffeNamespaceMismatch(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--namespace", "other"})
	err := rootCmd.Execute()
	if err == nil {
		t.Fatal("expected mismatch error, got nil")
	}
	if !strings.Contains(err.Error(), "does not match") {
		t.Errorf("error = %q, want contains 'does not match'", err.Error())
	}
}

func TestACLGrantSpiffeDefaultsNamespaceFromPath(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--permissions", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant spiffe (no --namespace): %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "check", "spiffe://orca.local/ns/myapp/sa/svc1/alloc-1", "--namespace", "myapp", "--permission", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("check spiffe: %v", err)
	}
}

func TestACLGrantTokenRequiresNamespace(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--permissions", "read"})
	err := rootCmd.Execute()
	if err == nil {
		t.Fatal("expected error for token grant without --namespace, got nil")
	}
}

func TestACLStatePersists(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant: %v", err)
	}

	data, err := os.ReadFile(paths.ACLPath())
	if err != nil {
		t.Fatalf("read acl.json: %v", err)
	}
	if !strings.Contains(string(data), "operator-1") || !strings.Contains(string(data), "prod") {
		t.Errorf("acl.json missing entry: %s", string(data))
	}
}

func TestACLAtomicWriteNoPartialFile(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant: %v", err)
	}
	entries, err := os.ReadDir(filepath.Dir(paths.ACLPath()))
	if err != nil {
		t.Fatalf("readdir cluster: %v", err)
	}
	for _, e := range entries {
		if strings.HasPrefix(e.Name(), ".acl-tmp-") {
			t.Errorf("leftover temp file: %s", e.Name())
		}
	}
}

func TestACLCheckJSONDenied(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetArgs([]string{"acl", "check", "ghost", "--namespace", "prod", "--permission", "read", "--json"})
	_ = rootCmd.Execute()
	var result map[string]any
	if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &result); err != nil {
		t.Fatalf("unmarshal: %v\n%s", err, buf.String())
	}
	if result["allowed"] != false {
		t.Errorf("allowed = %v, want false", result["allowed"])
	}
}

func TestACLAdminImpliesReadCheck(t *testing.T) {
	t.Setenv("ORCA_HOME", t.TempDir())
	resetRootFlags(t)
	resetACLFlags()

	rootCmd.SetArgs([]string{"acl", "grant", "operator-1", "--namespace", "prod", "--permissions", "admin"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("grant admin: %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "read"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("check read (admin grant): %v", err)
	}

	resetRootFlags(t)
	resetACLFlags()
	rootCmd.SetArgs([]string{"acl", "check", "operator-1", "--namespace", "prod", "--permission", "write"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("check write (admin grant): %v", err)
	}
}
