package permissions

import (
	"context"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/schemacatalog"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

func TestAssignmentPolicyRootLockRevalidatesDefinitionEligibilityAndOwner(t *testing.T) {
	tests := map[string]func(cp.ComponentRow){
		"inactive":        func(row cp.ComponentRow) { row["IsActive"] = false },
		"deleted":         func(row cp.ComponentRow) { row["IsDeleted"] = true },
		"not custom":      func(row cp.ComponentRow) { row["IsCustom"] = false },
		"profile owned":   func(row cp.ComponentRow) { row["IsOwnedByProfile"] = true },
		"profile bound":   func(row cp.ComponentRow) { row["ProfileId"] = "profile-sales" },
		"different owner": func(row cp.ComponentRow) { row["OwnerPack"] = "other-definitions" },
	}
	for name, mutate := range tests {
		t.Run(name, func(t *testing.T) {
			store := newPermissionSetDefinitionLifecycleMemoryStore()
			root := cp.ComponentRow{
				"Id": "ps-sales", "Name": "Sales_User", "OwnerPack": "definitions",
				"IsActive": true, "IsDeleted": false, "IsCustom": true,
				"ProfileId": nil, "IsOwnedByProfile": false,
			}
			mutate(root)
			store.rows["PermissionSet"] = []cp.ComponentRow{root}
			err := lockAssignmentPolicyRoots(context.Background(), store, []assignmentPolicyRootLock{{
				PermissionSetID: "ps-sales", PermissionSet: "Sales_User", OwnerPack: "definitions",
			}})
			if err == nil {
				t.Fatal("ineligible or re-owned Permission Set remained eligible after its root was locked")
			}
		})
	}
}

func TestAssignmentPolicyRootLockUsesDatabaseEquivalentDefinitionName(t *testing.T) {
	store := newPermissionSetDefinitionLifecycleMemoryStore()
	store.rows["PermissionSet"] = []cp.ComponentRow{{
		"Id": "ps-sales", "Name": "sales_user", "OwnerPack": "definitions",
		"IsActive": true, "IsDeleted": false, "IsCustom": true,
		"ProfileId": nil, "IsOwnedByProfile": false,
	}}
	if err := lockAssignmentPolicyRoots(
		context.Background(),
		store,
		[]assignmentPolicyRootLock{{
			PermissionSetID: "ps-sales", PermissionSet: "Sales_User", OwnerPack: "definitions",
		}},
	); err != nil {
		t.Fatalf("database-equivalent Permission Set name failed root locking: %v", err)
	}
}

func TestAssignmentPolicyDefinitionRootRejectsNameOutsideManagedDomain(t *testing.T) {
	for _, name := range []string{"Café_User", "Ｓales_User"} {
		t.Run(name, func(t *testing.T) {
			row := cp.ComponentRow{
				"Id": "ps-sales", "Name": name, "OwnerPack": "definitions",
				"IsActive": true, "IsDeleted": false, "IsCustom": true,
				"ProfileId": nil, "IsOwnedByProfile": false,
			}
			if _, _, _, err := assignmentPolicyDefinitionRootFromRow(row); err == nil ||
				!strings.Contains(err.Error(), "Permission Set name") {
				t.Fatalf("invalid Definition name planning error = %v", err)
			}
		})
	}
}

func TestReadAssignmentPolicyTargetRejectsMalformedClaimIdentity(t *testing.T) {
	store := assignmentPolicyTransactionTestStore{newPermissionSetDefinitionLifecycleMemoryStore()}
	store.rows["PermissionSet"] = []cp.ComponentRow{{
		"Id": "ps-sales", "Name": "Sales_User", "OwnerPack": "definitions",
		"IsActive": true, "IsDeleted": false, "IsCustom": true,
		"ProfileId": nil, "IsOwnedByProfile": false,
	}}
	store.rows[schemacatalog.AssignmentPolicyClaimTable] = []cp.ComponentRow{{
		"Id": "claim-1", "PermissionSetId": "ps-sales", "PolicyKey": "sales_users",
	}}
	if _, err := readAssignmentPolicyTarget(context.Background(), store, "Sales_User"); err == nil {
		t.Fatal("claim without an owner was accepted as an unclaimed target")
	}
}

func TestResolveAssignmentPolicyEligibilityRejectsMalformedProfileIdentity(t *testing.T) {
	store := assignmentPolicyTransactionTestStore{newPermissionSetDefinitionLifecycleMemoryStore()}
	store.rows["Profile"] = []cp.ComponentRow{{
		"Name": "Sales", "IsActive": true, "IsDeleted": false,
	}}
	if _, _, _, _, err := projectAssignmentPolicyEligibility(
		context.Background(), store, "ps-sales", []string{"Sales"},
		assignmentPolicyTestEligibilityProvider(),
		newAssignmentPolicyEligibilityReadBudget(assignmentPolicyManagedBudget().MaxCurrentItems),
	); err == nil {
		t.Fatal("Profile without an ID was accepted as a valid selector")
	}
}

func TestReconcileAssignmentPolicySelectorsRejectsMalformedCurrentRow(t *testing.T) {
	store := assignmentPolicyTransactionTestStore{newPermissionSetDefinitionLifecycleMemoryStore()}
	store.rows[schemacatalog.AssignmentPolicySelectorTable] = []cp.ComponentRow{{
		"PermissionSetId": "ps-sales", "ProfileName": "Sales",
	}}
	if _, err := reconcileAssignmentPolicySelectors(
		context.Background(), store, "ps-sales", nil,
	); err == nil {
		t.Fatal("selector without an ID was accepted during exact reconciliation")
	}
}

func TestAssignmentPolicyRequiredWritesRejectZeroAffectedRows(t *testing.T) {
	t.Run("insert", func(t *testing.T) {
		store := &assignmentPolicyZeroWriteStore{assignmentPolicyTransactionTestStore{
			newPermissionSetDefinitionLifecycleMemoryStore(),
		}}
		if _, _, err := ensureAssignmentPolicyState(
			context.Background(), store, "ps-sales", "Sales_User", assignmentPolicyResolutionResolved,
		); err == nil || !strings.Contains(err.Error(), "affected 0, want 1") {
			t.Fatalf("zero-row state insert error = %v", err)
		}
	})

	t.Run("update", func(t *testing.T) {
		store := &assignmentPolicyZeroWriteStore{assignmentPolicyTransactionTestStore{
			newPermissionSetDefinitionLifecycleMemoryStore(),
		}}
		store.rows[schemacatalog.AssignmentPolicyTombstoneReceiptTable] = []cp.ComponentRow{{
			"Id": "receipt-1", "AssignmentId": "assignment-1",
			"PermissionSetId": "ps-sales", "AssigneeId": "user-sales",
			"OwnerPack": "app", "PolicyKey": "sales_users",
			"TombstonedGeneration": uint64(1), "TombstonedAt": "2026-08-13T08:00:00Z",
		}}
		if _, err := upsertAssignmentPolicyTombstoneReceipt(
			context.Background(), store, "assignment-1", "ps-sales", "user-sales",
			"app", "sales_users", 2, "2026-08-13T08:00:00Z",
		); err == nil || !strings.Contains(err.Error(), "affected 0, want 1") {
			t.Fatalf("zero-row receipt update error = %v", err)
		}
	})

	t.Run("delete", func(t *testing.T) {
		store := &assignmentPolicyZeroWriteStore{assignmentPolicyTransactionTestStore{
			newPermissionSetDefinitionLifecycleMemoryStore(),
		}}
		store.rows[schemacatalog.AssignmentPolicyTombstoneReceiptTable] = []cp.ComponentRow{{
			"Id": "receipt-1", "AssignmentId": "assignment-1",
			"PermissionSetId": "ps-sales", "AssigneeId": "user-sales",
			"OwnerPack": "app", "PolicyKey": "sales_users",
			"TombstonedGeneration": uint64(1), "TombstonedAt": "2026-08-13T08:00:00Z",
		}}
		if _, err := consumeAssignmentPolicyTombstoneReceipt(
			context.Background(), store, "assignment-1", "ps-sales", "user-sales",
			"app", "sales_users", 2, "2026-08-13T08:00:00Z", false, map[string]uint64{},
		); err == nil || !strings.Contains(err.Error(), "affected 0, want 1") {
			t.Fatalf("zero-row receipt delete error = %v", err)
		}
	})
}

func TestConsumeAssignmentPolicyTombstoneReceiptRejectsStaleDeletionEvent(t *testing.T) {
	store := newPermissionSetDefinitionLifecycleMemoryStore()
	store.rows[schemacatalog.AssignmentPolicyTombstoneReceiptTable] = []cp.ComponentRow{{
		"Id": "receipt-1", "AssignmentId": "assignment-1",
		"PermissionSetId": "ps-sales", "AssigneeId": "user-sales",
		"OwnerPack": "app", "PolicyKey": "sales_users",
		"TombstonedGeneration": uint64(1), "TombstonedAt": "2026-08-13T08:00:00Z",
	}}
	if _, err := consumeAssignmentPolicyTombstoneReceipt(
		context.Background(), store, "assignment-1", "ps-sales", "user-sales", "app", "sales_users",
		2, "2026-08-13T08:01:00Z", false, map[string]uint64{},
	); err == nil || !strings.Contains(err.Error(), "current deletion event") {
		t.Fatalf("stale deletion-event receipt error = %v", err)
	}
}

func TestResolveAssignmentPolicyEligibilitySortsProfileReads(t *testing.T) {
	store := assignmentPolicyTransactionTestStore{newPermissionSetDefinitionLifecycleMemoryStore()}
	store.rows["Profile"] = []cp.ComponentRow{
		{"Id": "profile-z", "Name": "Zeta", "IsActive": true, "IsDeleted": false},
		{"Id": "profile-a", "Name": "Alpha", "IsActive": true, "IsDeleted": false},
	}
	if _, _, _, _, err := projectAssignmentPolicyEligibility(
		context.Background(), store, "ps-sales", []string{"Zeta", "Alpha"},
		assignmentPolicyTestEligibilityProvider(),
		newAssignmentPolicyEligibilityReadBudget(assignmentPolicyManagedBudget().MaxCurrentItems),
	); err != nil {
		t.Fatal(err)
	}
	var readProfiles []string
	for _, query := range store.reads {
		if query.Table == "Profile" {
			readProfiles = append(readProfiles, query.Equal["Name"].(string))
		}
	}
	if got := strings.Join(readProfiles, ","); got != "Alpha,Zeta" {
		t.Fatalf("Profile read order = %s, want Alpha,Zeta", got)
	}
}

func TestEnsureAssignmentPolicyStateRejectsMisboundExistingRow(t *testing.T) {
	store := newPermissionSetDefinitionLifecycleMemoryStore()
	store.rows[schemacatalog.AssignmentPolicyTargetStateTable] = []cp.ComponentRow{{
		"Id": "state-1", "TargetDomain": "wrong-domain", "PhysicalKey": []byte("wrong-key"),
		"PermissionSetId": "ps-sales", "Suspended": false,
		"WorkGeneration": uint64(1), "AppliedGeneration": uint64(1),
		"ResolutionState": assignmentPolicyResolutionResolved,
	}}
	if _, _, err := ensureAssignmentPolicyState(
		context.Background(), store, "ps-sales", "Sales_User", assignmentPolicyResolutionResolved,
	); err == nil || !strings.Contains(err.Error(), "identity") {
		t.Fatalf("misbound target state error = %v", err)
	}
}

func TestConsumeAssignmentPolicyTombstoneReceiptRejectsDifferentPolicy(t *testing.T) {
	store := newPermissionSetDefinitionLifecycleMemoryStore()
	store.rows[schemacatalog.AssignmentPolicyTombstoneReceiptTable] = []cp.ComponentRow{{
		"Id": "receipt-1", "AssignmentId": "assignment-1",
		"PermissionSetId": "ps-sales", "AssigneeId": "user-sales",
		"OwnerPack": "old-app", "PolicyKey": "old-policy",
		"TombstonedGeneration": uint64(1), "TombstonedAt": "2026-08-13T08:00:00Z",
	}}
	if _, err := consumeAssignmentPolicyTombstoneReceipt(
		context.Background(), store, "assignment-1", "ps-sales", "user-sales", "app", "sales_users",
		2, "2026-08-13T08:00:00Z", false, map[string]uint64{},
	); err == nil {
		t.Fatal("tombstone receipt from a different policy authorized reactivation")
	}
}

func TestAssignmentPolicyStateFromRowAcceptsDecimalGenerationStrings(t *testing.T) {
	state, err := assignmentPolicyStateFromRow(cp.ComponentRow{
		"Id":                "state-1",
		"Suspended":         false,
		"WorkGeneration":    "2",
		"AppliedGeneration": "1",
		"ResolutionState":   assignmentPolicyResolutionResolved,
	})
	if err != nil {
		t.Fatal(err)
	}
	if state.WorkGeneration != 2 || state.AppliedGeneration != 1 {
		t.Fatalf("generation state = %#v", state)
	}
}

func TestAssignmentPolicyStateFromRowRejectsAppliedGenerationAheadOfWork(t *testing.T) {
	_, err := assignmentPolicyStateFromRow(cp.ComponentRow{
		"Id":                "state-1",
		"Suspended":         false,
		"WorkGeneration":    uint64(1),
		"AppliedGeneration": uint64(2),
		"ResolutionState":   assignmentPolicyResolutionResolved,
	})
	if err == nil || !strings.Contains(err.Error(), "ahead") {
		t.Fatalf("invalid generation ordering error = %v", err)
	}
}

func TestAssignmentPolicyKeysetReadRequiresExactExhaustion(t *testing.T) {
	query := cp.ComponentManagedRowQuery{
		Table: "PermissionSetAssignment", Columns: []string{"AssigneeId"},
		OrderBy:           []cp.ComponentManagedOrder{{Column: "AssigneeId", Direction: cp.ComponentManagedOrderAscending}},
		LogicalKeyColumns: []string{"AssigneeId"}, MaximumRows: 1,
	}
	if _, err := readAssignmentPolicyRows(
		context.Background(),
		assignmentPolicyLegacyReadStore{},
		query,
	); err == nil || !strings.Contains(err.Error(), "requires exact keyset read exhaustion") {
		t.Fatalf("legacy keyset store error = %v", err)
	}
	store := &assignmentPolicyScriptedKeysetStore{pages: []cp.ComponentManagedKeysetRowPage{
		{Rows: []cp.ComponentRow{{"AssigneeId": "user-a"}}, Exhausted: false},
		{Rows: []cp.ComponentRow{{"AssigneeId": "user-b"}}, Exhausted: true},
	}}
	rows, err := readAssignmentPolicyRows(context.Background(), store, query)
	if err != nil {
		t.Fatal(err)
	}
	if len(rows) != 2 || rows[0]["AssigneeId"] != "user-a" || rows[1]["AssigneeId"] != "user-b" {
		t.Fatalf("keyset rows = %#v", rows)
	}
	if len(store.requests) != 2 || store.requests[1].ContinueAfter["AssigneeId"] != "user-a" {
		t.Fatalf("keyset requests = %#v", store.requests)
	}
}

type assignmentPolicyLegacyReadStore struct{}

func (assignmentPolicyLegacyReadStore) ReadRows(
	context.Context,
	cp.ComponentManagedRowQuery,
) ([]cp.ComponentRow, error) {
	return nil, nil
}

type assignmentPolicyTransactionTestStore struct {
	*permissionSetDefinitionLifecycleMemoryStore
}

func (s assignmentPolicyTransactionTestStore) ReadRowsPage(
	ctx context.Context,
	page cp.ComponentManagedKeysetRowQuery,
) ([]cp.ComponentRow, error) {
	result, err := s.ReadRowsKeysetPage(ctx, page)
	return result.Rows, err
}

func (s assignmentPolicyTransactionTestStore) LockRowsPage(
	ctx context.Context,
	page cp.ComponentManagedKeysetRowQuery,
) ([]cp.ComponentRow, error) {
	result, err := s.LockRowsKeysetPage(ctx, page)
	return result.Rows, err
}

type assignmentPolicyZeroWriteStore struct {
	assignmentPolicyTransactionTestStore
}

func (s *assignmentPolicyZeroWriteStore) InsertRows(
	context.Context,
	cp.ComponentManagedInsertBatch,
) (int64, error) {
	return 0, nil
}

func (s *assignmentPolicyZeroWriteStore) UpdateRows(
	context.Context,
	cp.ComponentManagedUpdate,
) (int64, error) {
	return 0, nil
}

func (s *assignmentPolicyZeroWriteStore) DeleteRows(
	context.Context,
	cp.ComponentManagedDelete,
) (int64, error) {
	return 0, nil
}

func (s assignmentPolicyTransactionTestStore) ReadRowsKeysetPage(
	ctx context.Context,
	page cp.ComponentManagedKeysetRowQuery,
) (cp.ComponentManagedKeysetRowPage, error) {
	rows, err := s.ReadRows(ctx, page.Query)
	return cp.ComponentManagedKeysetRowPage{Rows: rows, Exhausted: true}, err
}

func (s assignmentPolicyTransactionTestStore) LockRowsKeysetPage(
	ctx context.Context,
	page cp.ComponentManagedKeysetRowQuery,
) (cp.ComponentManagedKeysetRowPage, error) {
	rows, err := s.LockRows(ctx, page.Query)
	return cp.ComponentManagedKeysetRowPage{Rows: rows, Exhausted: true}, err
}

type assignmentPolicyScriptedKeysetStore struct {
	pages    []cp.ComponentManagedKeysetRowPage
	requests []cp.ComponentManagedKeysetRowQuery
}

func (s *assignmentPolicyScriptedKeysetStore) ReadRows(
	context.Context,
	cp.ComponentManagedRowQuery,
) ([]cp.ComponentRow, error) {
	return nil, nil
}

func (s *assignmentPolicyScriptedKeysetStore) ReadRowsPage(
	ctx context.Context,
	page cp.ComponentManagedKeysetRowQuery,
) ([]cp.ComponentRow, error) {
	result, err := s.ReadRowsKeysetPage(ctx, page)
	return result.Rows, err
}

func (s *assignmentPolicyScriptedKeysetStore) ReadRowsKeysetPage(
	_ context.Context,
	page cp.ComponentManagedKeysetRowQuery,
) (cp.ComponentManagedKeysetRowPage, error) {
	s.requests = append(s.requests, page)
	result := s.pages[0]
	s.pages = s.pages[1:]
	return result, nil
}
