package permissions

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"testing"

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

func TestPermissionSetDefinitionManagedRegistrationIsDeepAndExact(t *testing.T) {
	base, lifecycle, err := NewPermissionSetDefinitionManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	if base.Contract.Kind != cp.ComponentKind("permission_set_definition") ||
		base.Capability.Kind != cp.ComponentKind("permission_set_definition") ||
		base.Contract.Phase != cp.InstallPlanPhase("security") ||
		!base.Capability.Installable {
		t.Fatalf("registration identity = %#v / %#v", base.Contract, base.Capability)
	}
	if base.Contract.OwnerDiscriminator != cp.ComponentOwnerStrategyOwnerPack ||
		base.ManagedProvider != nil ||
		base.BuildHandler == nil {
		t.Fatalf("registration boundary = %#v", base)
	}
	if _, ok := base.Contract.Manageabilities[cp.ManageabilityLocked]; !ok ||
		len(base.Contract.Manageabilities) != 1 {
		t.Fatalf("registration manageabilities = %#v", base.Contract.Manageabilities)
	}
	gotWriteTables := make([]string, len(base.Contract.WriteSet))
	for index, surface := range base.Contract.WriteSet {
		gotWriteTables[index] = surface.Table
	}
	wantWriteTables := []string{
		"PermissionSet",
		"_System_PermissionSetPermission",
		"PermissionSetCustomPermission",
		"ObjectPermissions",
		"FieldPermissions",
	}
	if !reflect.DeepEqual(gotWriteTables, wantWriteTables) {
		t.Fatalf("write tables = %v, want %v", gotWriteTables, wantWriteTables)
	}
	if lifecycle.Kind() != cp.ComponentKind("permission_set_definition") ||
		lifecycle.TransactionMode() != cp.ComponentManagedTransactionPessimisticReadCommitted {
		t.Fatalf("lifecycle identity = %q/%q", lifecycle.Kind(), lifecycle.TransactionMode())
	}
	if lifecycle.Budget().MaxBatchRows != permissionSetDefinitionManagedMaximumRows {
		t.Fatalf(
			"lifecycle maximum batch rows = %d, want %d",
			lifecycle.Budget().MaxBatchRows,
			permissionSetDefinitionManagedMaximumRows,
		)
	}
	effectClasses := lifecycle.EffectClasses()
	if len(effectClasses) != 1 ||
		effectClasses[0] != PermissionSetDefinitionPermissionStateEffectClass {
		t.Fatalf("effect classes = %q", effectClasses)
	}

	gotReadTables := make([]string, len(lifecycle.ReadSet()))
	for index, surface := range lifecycle.ReadSet() {
		gotReadTables[index] = surface.Table
	}
	wantReadTables := []string{
		"_System_ConnectedAppAccess",
		"_System_CustomPermission",
		"_System_Field",
		"_System_Object",
		"_System_Permission",
		"_System_PermissionSetAssignmentPolicyClaim",
		"_System_PermissionSetPermission",
		"FieldPermissions",
		"ObjectPermissions",
		"PermissionSet",
		"PermissionSetAppVisibility",
		"PermissionSetAssignment",
		"PermissionSetCustomPermission",
		"PermissionSetGroupComponent",
		"PermissionSetRecordTypeVisibility",
		"PermissionSetTabSetting",
	}
	if !reflect.DeepEqual(gotReadTables, wantReadTables) {
		t.Fatalf("read tables = %v, want %v", gotReadTables, wantReadTables)
	}
}

func TestPermissionSetDefinitionManagedBudgetCoversMaximumGrantReplacement(t *testing.T) {
	_, lifecycle, err := NewPermissionSetDefinitionManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	const (
		grantFamilies             = 4
		maximumReplacementChanges = 1 + 2*grantFamilies*permissionSetDefinitionManagedMaximumRows
	)
	budget := lifecycle.Budget()
	if budget.MaxDeltaRows < maximumReplacementChanges {
		t.Fatalf(
			"maximum delta rows = %d, want at least %d for a disjoint full replacement",
			budget.MaxDeltaRows,
			maximumReplacementChanges,
		)
	}
	if budget.MaxMutationStatements < maximumReplacementChanges {
		t.Fatalf(
			"maximum mutation statements = %d, want at least %d for a disjoint full replacement",
			budget.MaxMutationStatements,
			maximumReplacementChanges,
		)
	}
}

func TestPermissionSetDefinitionManagedTargetUsesDatabaseCollationIdentity(t *testing.T) {
	first, err := PermissionSetDefinitionManagedTarget("Sales_Operator")
	if err != nil {
		t.Fatal(err)
	}
	second, err := PermissionSetDefinitionManagedTarget("sales_operator")
	if err != nil {
		t.Fatal(err)
	}
	if first.TargetDomain != permissionSetDefinitionManagedTargetDomain ||
		!reflect.DeepEqual(first, second) ||
		string(first.PhysicalKeyBytes) != "sales_operator" {
		t.Fatalf("targets = %#v / %#v", first, second)
	}
	for _, invalid := range []string{"", " sales", "sales-user", "销售", strings.Repeat("a", 256)} {
		if _, err := PermissionSetDefinitionManagedTarget(invalid); err == nil {
			t.Fatalf("PermissionSetDefinitionManagedTarget(%q) error = nil", invalid)
		}
	}
}

func TestPermissionSetDefinitionBuildIsStrictAndCanonical(t *testing.T) {
	base, _, err := NewPermissionSetDefinitionManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	payload := []byte(`{
		"schema": "nexuscrm.permission-set-definition/v1",
		"label": "Sales Operator",
		"description": "Bounded sales access",
		"active": true,
		"adoptionMode": "exact_match",
		"systemPermissions": ["ApiEnabled", "ViewSetup"],
		"customPermissions": ["Can_Export"],
		"objectPermissions": [{
			"object": "Account",
			"read": true,
			"create": true,
			"edit": true,
			"delete": false,
			"viewAllRecords": false,
			"modifyAllRecords": false
		}],
		"fieldPermissions": [{
			"object": "Account",
			"field": "Name",
			"read": true,
			"edit": true
		}]
	}`)
	build := &permissionSetDefinitionBuildContext{packName: "sales"}
	request := cp.ComponentBuildRequest{
		PackName: "sales",
		Component: cp.ManifestComponent{
			Kind:          cp.ComponentKind("permission_set_definition"),
			BusinessKey:   "Sales_Operator",
			Manageability: cp.ManageabilityLocked,
			Payload:       append(json.RawMessage(nil), payload...),
		},
		Payload: payload,
	}
	if err := base.BuildHandler(build, request); err != nil {
		t.Fatal(err)
	}
	if len(build.managed) != 1 {
		t.Fatalf("managed components = %d, want 1", len(build.managed))
	}
	managed := build.managed[0]
	if len(managed.CollisionClaims) != 1 ||
		managed.CollisionClaims[0].Namespace != permissionSetDefinitionNameCollisionNamespace ||
		managed.CollisionClaims[0].Key != "sales_operator" {
		t.Fatalf("collision claims = %#v", managed.CollisionClaims)
	}
	var projection permissionSetDefinitionProjection
	if err := json.Unmarshal(managed.ExpectedProjection, &projection); err != nil {
		t.Fatal(err)
	}
	if projection.Name != "Sales_Operator" ||
		projection.OwnerPack != "sales" ||
		projection.Description == nil ||
		*projection.Description != "Bounded sales access" ||
		!projection.Active {
		t.Fatalf("projection root = %#v", projection)
	}
	if got := projection.SystemPermissions; !reflect.DeepEqual(got, []string{"ApiEnabled", "ViewSetup"}) {
		t.Fatalf("system permissions = %v", got)
	}

	tests := []struct {
		name    string
		payload string
		want    string
	}{
		{name: "unknown field", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[],"extra":true}`, want: "unknown field"},
		{name: "trailing input", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[]} {}`, want: "trailing"},
		{name: "null collection", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":null,"customPermissions":[],"objectPermissions":[],"fieldPermissions":[]}`, want: "systemPermissions"},
		{name: "null description", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","description":null,"active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[]}`, want: "description"},
		{name: "inactive declaration", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":false,"systemPermissions":[],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[]}`, want: "active must be true"},
		{name: "duplicate system permission", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":["ViewSetup","viewsetup"],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[]}`, want: "duplicates"},
		{name: "duplicate object grant", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[{"object":"Account","read":true,"create":false,"edit":false,"delete":false,"viewAllRecords":false,"modifyAllRecords":false},{"object":"account","read":true,"create":false,"edit":false,"delete":false,"viewAllRecords":false,"modifyAllRecords":false}],"fieldPermissions":[]}`, want: "duplicates"},
		{name: "implicit object boolean", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[{"object":"Account","read":true,"create":false,"edit":false,"delete":false,"viewAllRecords":false}],"fieldPermissions":[]}`, want: "modifyAllRecords is required"},
		{name: "implicit field boolean", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[{"object":"Account","field":"Name","read":true}]}`, want: "edit is required"},
		{name: "field edit requires read", payload: `{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[{"object":"Account","field":"Name","read":false,"edit":true}]}`, want: "FIELD_INTEGRITY_EXCEPTION"},
		{
			name: "qualified field exceeds storage limit",
			payload: fmt.Sprintf(
				`{"schema":"nexuscrm.permission-set-definition/v1","label":"x","active":true,"systemPermissions":[],"customPermissions":[],"objectPermissions":[],"fieldPermissions":[{"object":%q,"field":%q,"read":true,"edit":false}]}`,
				strings.Repeat("O", 128),
				strings.Repeat("F", 127),
			),
			want: "qualified field",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			bad := request
			bad.Payload = []byte(test.payload)
			bad.Component.Payload = append(json.RawMessage(nil), bad.Payload...)
			err := base.BuildHandler(&permissionSetDefinitionBuildContext{packName: "sales"}, bad)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("BuildHandler() error = %v, want containing %q", err, test.want)
			}
		})
	}
}

func TestPermissionSetDefinitionBuildReusesObjectPermissionDependencies(t *testing.T) {
	base, _, err := NewPermissionSetDefinitionManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	tests := []struct {
		name  string
		grant permissionSetDefinitionObjectGrant
		valid bool
	}{
		{name: "create requires read", grant: permissionSetDefinitionObjectGrant{Object: "Account", Create: true}},
		{name: "edit requires read", grant: permissionSetDefinitionObjectGrant{Object: "Account", Edit: true}},
		{name: "delete requires read", grant: permissionSetDefinitionObjectGrant{Object: "Account", Delete: true}},
		{name: "view all requires read", grant: permissionSetDefinitionObjectGrant{Object: "Account", ViewAllRecords: true}},
		{name: "modify all requires read", grant: permissionSetDefinitionObjectGrant{
			Object: "Account", Create: true, Edit: true, Delete: true, ViewAllRecords: true, ModifyAllRecords: true,
		}},
		{name: "modify all without create is valid", valid: true, grant: permissionSetDefinitionObjectGrant{
			Object: "Account", Read: true, Edit: true, Delete: true, ViewAllRecords: true, ModifyAllRecords: true,
		}},
		{name: "modify all requires edit", grant: permissionSetDefinitionObjectGrant{
			Object: "Account", Read: true, Create: true, Delete: true, ViewAllRecords: true, ModifyAllRecords: true,
		}},
		{name: "modify all requires delete", grant: permissionSetDefinitionObjectGrant{
			Object: "Account", Read: true, Create: true, Edit: true, ViewAllRecords: true, ModifyAllRecords: true,
		}},
		{name: "modify all requires view all", grant: permissionSetDefinitionObjectGrant{
			Object: "Account", Read: true, Create: true, Edit: true, Delete: true, ModifyAllRecords: true,
		}},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			payload, err := json.Marshal(permissionSetDefinitionPayload{
				Schema:            permissionSetDefinitionPayloadSchema,
				Label:             "Invalid Dependencies",
				Active:            true,
				SystemPermissions: []string{},
				CustomPermissions: []string{},
				ObjectPermissions: []permissionSetDefinitionObjectGrant{test.grant},
				FieldPermissions:  []permissionSetDefinitionFieldGrant{},
			})
			if err != nil {
				t.Fatal(err)
			}
			component := cp.ManifestComponent{
				Kind:          permissionSetDefinitionComponentKind,
				BusinessKey:   "Invalid_Dependencies",
				Manageability: cp.ManageabilityLocked,
				Payload:       append(json.RawMessage(nil), payload...),
			}
			err = base.BuildHandler(&permissionSetDefinitionBuildContext{packName: "sales"}, cp.ComponentBuildRequest{
				PackName:  "sales",
				Component: component,
				Payload:   payload,
			})
			if test.valid {
				if err != nil {
					t.Fatalf("BuildHandler() rejected ModifyAll without Create: %v", err)
				}
			} else if err == nil || !strings.Contains(err.Error(), "FIELD_INTEGRITY_EXCEPTION") {
				t.Fatalf("BuildHandler() dependency error = %v", err)
			}
		})
	}
}

type permissionSetDefinitionBuildContext struct {
	packName string
	managed  []cp.ComponentBuildManagedComponent
}

func (c *permissionSetDefinitionBuildContext) PackName() string { return c.packName }
func (*permissionSetDefinitionBuildContext) LookupObjectSchema(string) (cp.ComponentObjectSchema, bool) {
	return cp.ComponentObjectSchema{}, false
}
func (c *permissionSetDefinitionBuildContext) AddManagedComponent(component cp.ComponentBuildManagedComponent) error {
	c.managed = append(c.managed, component)
	return nil
}
func (*permissionSetDefinitionBuildContext) AddRuntimeStartupShallowDeclaration(cp.ComponentKind, string, []byte) error {
	return nil
}
