package permissions

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

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

func TestCustomPermissionManagedRegistrationOwnsOneSecurityCatalog(t *testing.T) {
	base, lifecycle, err := NewCustomPermissionManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	if base.Contract.Kind != cp.ComponentKind("custom_permission") ||
		base.Capability.Kind != cp.ComponentKind("custom_permission") ||
		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.Contract.CollisionKey != customPermissionCollisionNamespace ||
		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)
	}
	if got, want := base.Contract.WriteSet, []cp.ComponentWriteSurface{{
		Table: "_System_CustomPermission",
		Fact:  "managed custom permission catalog state",
	}}; !reflect.DeepEqual(got, want) {
		t.Fatalf("write set = %#v, want %#v", got, want)
	}
	if lifecycle.Kind() != cp.ComponentKind("custom_permission") ||
		lifecycle.TransactionMode() != cp.ComponentManagedTransactionPessimisticReadCommitted {
		t.Fatalf("lifecycle identity = %q/%q", lifecycle.Kind(), lifecycle.TransactionMode())
	}
	if got, want := lifecycle.ReadSet(), []cp.ComponentReadSurface{
		{
			Table: "_System_CustomPermission",
			Fact:  "custom permission catalog projection and owner set",
		},
		{
			Table: "PermissionSetCustomPermission",
			Fact:  "canonical custom permission retirement reference",
		},
		{
			Table: "ProfileCustomPermission",
			Fact:  "canonical custom permission retirement reference",
		},
	}; !reflect.DeepEqual(got, want) {
		t.Fatalf("read set = %#v, want %#v", got, want)
	}
	exactSet := false
	for _, capability := range lifecycle.Capabilities() {
		if capability == cp.ComponentManagedCapabilityExactSet {
			exactSet = true
		}
	}
	if !exactSet {
		t.Fatal("the composed authority must advertise exact-set reconciliation after L1 declaration cutover")
	}
	if got := lifecycle.EffectClasses(); !reflect.DeepEqual(got, []cp.ComponentManagedEffectClass{
		PermissionSetDefinitionPermissionStateEffectClass,
	}) {
		t.Fatalf("effect classes = %q", got)
	}
	budget := lifecycle.Budget()
	if budget.MaxTargets != 2*customPermissionManagedMaximumRows+1 ||
		budget.MaxCurrentItems != 3*customPermissionManagedMaximumRows ||
		budget.MaxDesiredItems != customPermissionManagedMaximumRows ||
		budget.MaxDeltaRows != customPermissionManagedMaximumRows ||
		budget.MaxMutationStatements != customPermissionManagedMaximumRows ||
		budget.MaxBatchRows != customPermissionManagedMaximumRows {
		t.Fatalf("lifecycle budget = %#v", budget)
	}
}

func TestCustomPermissionBuildRejectsAmbiguousOrNoncanonicalSource(t *testing.T) {
	base, _, err := NewCustomPermissionManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	valid := cp.ComponentBuildRequest{
		PackName: "salesforce-crm-standard",
		Component: cp.ManifestComponent{
			Kind:          customPermissionComponentKind,
			BusinessKey:   "SendRecordEmail",
			Manageability: cp.ManageabilityLocked,
		},
		Payload: []byte(`{"schema":"nexuscrm.custom-permission/v1","label":"Send Record Email","active":true}`),
	}
	tests := []struct {
		name    string
		mutate  func(*cp.ComponentBuildRequest)
		wantErr string
	}{
		{name: "unknown field", mutate: func(request *cp.ComponentBuildRequest) {
			request.Payload = []byte(`{"schema":"nexuscrm.custom-permission/v1","label":"Send Record Email","active":true,"extra":true}`)
		}, wantErr: "unknown field"},
		{name: "null description", mutate: func(request *cp.ComponentBuildRequest) {
			request.Payload = []byte(`{"schema":"nexuscrm.custom-permission/v1","label":"Send Record Email","description":null,"active":true}`)
		}, wantErr: "description"},
		{name: "missing active", mutate: func(request *cp.ComponentBuildRequest) {
			request.Payload = []byte(`{"schema":"nexuscrm.custom-permission/v1","label":"Send Record Email"}`)
		}, wantErr: "active is required"},
		{name: "inactive", mutate: func(request *cp.ComponentBuildRequest) {
			request.Payload = []byte(`{"schema":"nexuscrm.custom-permission/v1","label":"Send Record Email","active":false}`)
		}, wantErr: "active must be true"},
		{name: "noncanonical key", mutate: func(request *cp.ComponentBuildRequest) {
			request.Component.BusinessKey = "send-record-email"
		}, wantErr: "must match"},
		{name: "overlong owner pack", mutate: func(request *cp.ComponentBuildRequest) {
			request.PackName = strings.Repeat("p", customPermissionMaximumOwnerPackBytes+1)
		}, wantErr: "1..64 bytes"},
		{name: "wrong pack", mutate: func(request *cp.ComponentBuildRequest) {
			request.PackName = "other-pack"
		}, wantErr: "pack name"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			request := valid
			test.mutate(&request)
			err := base.BuildHandler(&permissionSetDefinitionBuildContext{packName: "salesforce-crm-standard"}, request)
			if err == nil || !strings.Contains(err.Error(), test.wantErr) {
				t.Fatalf("BuildHandler() error = %v, want containing %q", err, test.wantErr)
			}
		})
	}
}

func TestCustomPermissionBuildPublishesCanonicalManagedProjection(t *testing.T) {
	base, _, err := NewCustomPermissionManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	payload := []byte(`{
		"schema":"nexuscrm.custom-permission/v1",
		"label":"Send Record Email",
		"description":"Allows the standard email action.",
		"active":true
	}`)
	build := &permissionSetDefinitionBuildContext{packName: "salesforce-crm-standard"}
	request := cp.ComponentBuildRequest{
		PackName: build.packName,
		Component: cp.ManifestComponent{
			Kind:          cp.ComponentKind("custom_permission"),
			BusinessKey:   "SendRecordEmail",
			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 got, want := managed.CollisionClaims, []cp.ComponentCollisionClaim{{
		Namespace: customPermissionCollisionNamespace,
		Key:       "sendrecordemail",
	}}; !reflect.DeepEqual(got, want) {
		t.Fatalf("collision claims = %#v, want %#v", got, want)
	}
	if got, want := string(managed.ExpectedProjection), `{"present":true,"apiName":"SendRecordEmail","ownerPack":"salesforce-crm-standard","label":"Send Record Email","description":"Allows the standard email action.","active":true,"deleted":false,"customized":false}`; got != want {
		t.Fatalf("expected projection = %s, want %s", got, want)
	}
}
