package permissions

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"sort"
	"strings"
	"time"

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

const (
	permissionSetDefinitionComponentKind          cp.ComponentKind    = "permission_set_definition"
	permissionSetDefinitionInstallPlanPhase       cp.InstallPlanPhase = "security"
	permissionSetDefinitionPayloadSchema                              = "nexuscrm.permission-set-definition/v1"
	permissionSetDefinitionManagedTargetDomain                        = "permission_set/name/utf8mb4_unicode_ci-v1"
	permissionSetDefinitionNameCollisionNamespace                     = "permission_set/name/utf8mb4_unicode_ci-v1"
	// PermissionSetDefinitionPermissionStateEffectClass binds semantic definition changes to the host's permission-state effect.
	PermissionSetDefinitionPermissionStateEffectClass cp.ComponentManagedEffectClass = "permission_state_advance_v1"
)

var permissionSetDefinitionWriteTables = []string{
	"PermissionSet",
	"_System_PermissionSetPermission",
	"PermissionSetCustomPermission",
	"ObjectPermissions",
	"FieldPermissions",
}

type permissionSetDefinitionPayload struct {
	Schema            string                               `json:"schema"`
	Label             string                               `json:"label"`
	Description       *string                              `json:"description,omitempty"`
	Active            bool                                 `json:"active"`
	AdoptionMode      string                               `json:"adoptionMode,omitempty"`
	SystemPermissions []string                             `json:"systemPermissions"`
	CustomPermissions []string                             `json:"customPermissions"`
	ObjectPermissions []permissionSetDefinitionObjectGrant `json:"objectPermissions"`
	FieldPermissions  []permissionSetDefinitionFieldGrant  `json:"fieldPermissions"`
}

type permissionSetDefinitionObjectGrant struct {
	Object           string `json:"object"`
	Read             bool   `json:"read"`
	Create           bool   `json:"create"`
	Edit             bool   `json:"edit"`
	Delete           bool   `json:"delete"`
	ViewAllRecords   bool   `json:"viewAllRecords"`
	ModifyAllRecords bool   `json:"modifyAllRecords"`
}

type permissionSetDefinitionFieldGrant struct {
	Object string `json:"object"`
	Field  string `json:"field"`
	Read   bool   `json:"read"`
	Edit   bool   `json:"edit"`
}

type permissionSetDefinitionProjection struct {
	Present           bool                                 `json:"present"`
	Name              string                               `json:"name"`
	OwnerPack         string                               `json:"ownerPack"`
	Label             string                               `json:"label"`
	Description       *string                              `json:"description,omitempty"`
	Active            bool                                 `json:"active"`
	SystemPermissions []string                             `json:"systemPermissions"`
	CustomPermissions []string                             `json:"customPermissions"`
	ObjectPermissions []permissionSetDefinitionObjectGrant `json:"objectPermissions"`
	FieldPermissions  []permissionSetDefinitionFieldGrant  `json:"fieldPermissions"`
}

// NewPermissionSetDefinitionManagedLifecycleRegistration returns the platform
// registration and its aggregate-owned Plan/Mutate/Project lifecycle.
func NewPermissionSetDefinitionManagedLifecycleRegistration() (
	cp.ComponentKindRegistration,
	cp.ComponentManagedLifecycleRegistration,
	error,
) {
	readSet, err := permissionSetDefinitionReadSet()
	if err != nil {
		return cp.ComponentKindRegistration{}, cp.ComponentManagedLifecycleRegistration{}, err
	}
	manageabilities := map[cp.Manageability]struct{}{cp.ManageabilityLocked: {}}
	writeSet := make([]cp.ComponentWriteSurface, len(permissionSetDefinitionWriteTables))
	for index, table := range permissionSetDefinitionWriteTables {
		writeSet[index] = cp.ComponentWriteSurface{
			Table: table,
			Fact:  "managed Permission Set definition state",
		}
	}
	base := cp.ComponentKindRegistration{
		Contract: cp.ComponentKindContract{
			Kind:               permissionSetDefinitionComponentKind,
			Phase:              permissionSetDefinitionInstallPlanPhase,
			WriteSet:           writeSet,
			BusinessKey:        "PermissionSet.Name",
			OwnerDiscriminator: cp.ComponentOwnerStrategyOwnerPack,
			Manageabilities:    manageabilities,
			VerifyProjection:   permissionSetDefinitionPayloadSchema,
			CollisionKey:       permissionSetDefinitionNameCollisionNamespace,
		},
		Capability: cp.SourceBackedComponentCapability{
			Kind:            permissionSetDefinitionComponentKind,
			Installable:     true,
			Manageabilities: manageabilities,
		},
		AllowedCategories: []cp.PackCategory{cp.AppPack},
		BuildHandler:      buildPermissionSetDefinition,
	}
	lifecycle, err := cp.NewComponentManagedLifecycleRegistration(cp.ComponentManagedLifecycleRegistrationSpec{
		Kind:    permissionSetDefinitionComponentKind,
		ReadSet: readSet,
		Capabilities: []cp.ComponentManagedCapability{
			cp.ComponentManagedCapabilityExactSet,
			cp.ComponentManagedCapabilityTargetSerialization,
			cp.ComponentManagedCapabilityStructuredMutation,
			cp.ComponentManagedCapabilityEffects,
			cp.ComponentManagedCapabilityEvidence,
			cp.ComponentManagedCapabilityFreshProjection,
		},
		EffectClasses: []cp.ComponentManagedEffectClass{
			PermissionSetDefinitionPermissionStateEffectClass,
		},
		TransactionMode: cp.ComponentManagedTransactionPessimisticReadCommitted,
		Budget: cp.ComponentManagedBudget{
			MaxTargets:            1001,
			MaxCurrentItems:       12500,
			MaxDesiredItems:       2000,
			MaxDeltaRows:          4001,
			MaxMutationBytes:      16 << 20,
			MaxMutationStatements: 4001,
			MaxBatchRows:          permissionSetDefinitionManagedMaximumRows,
			MaxEvidenceWrites:     1,
			MaxEffectCalls:        1,
			MaxLockWait:           5 * time.Second,
			MaxElapsed:            30 * time.Second,
			MaxAttempts:           3,
		},
		Lifecycle: cp.ComponentManagedLifecycleProvider{
			Plan:    planPermissionSetDefinition,
			Mutate:  mutatePermissionSetDefinition,
			Project: projectPermissionSetDefinition,
		},
	})
	if err != nil {
		return cp.ComponentKindRegistration{}, cp.ComponentManagedLifecycleRegistration{}, fmt.Errorf(
			"permission set definition lifecycle registration: %w",
			err,
		)
	}
	return base, lifecycle, nil
}

func permissionSetDefinitionReadSet() ([]cp.ComponentReadSurface, error) {
	inventory, err := schemacatalog.ManagedPermissionSetDefinitionInventory()
	if err != nil {
		return nil, fmt.Errorf("permission set definition lifecycle inventory: %w", err)
	}
	roles := map[string][]string{}
	readTables := map[string]struct{}{
		"_System_Permission":       {},
		"_System_CustomPermission": {},
		"_System_Object":           {},
		"_System_Field":            {},
	}
	for _, relation := range inventory.Relations() {
		roles[relation.Role] = append(roles[relation.Role], relation.TableName)
		readTables[relation.TableName] = struct{}{}
	}
	wantRoles := map[string][]string{
		schemacatalog.PermissionSetDefinitionRoleRoot: {"PermissionSet"},
		schemacatalog.PermissionSetDefinitionRoleModeledGrant: {
			"FieldPermissions",
			"ObjectPermissions",
			"PermissionSetCustomPermission",
			"_System_PermissionSetPermission",
		},
		schemacatalog.PermissionSetDefinitionRoleSeparateAssignment: {"PermissionSetAssignment"},
		schemacatalog.PermissionSetDefinitionRoleNegativeEdge: {
			"PermissionSetAppVisibility",
			"PermissionSetGroupComponent",
			"PermissionSetRecordTypeVisibility",
			"PermissionSetTabSetting",
			"_System_ConnectedAppAccess",
			schemacatalog.AssignmentPolicyClaimTable,
		},
	}
	for role, want := range wantRoles {
		got := append([]string(nil), roles[role]...)
		sort.Strings(got)
		sort.Strings(want)
		if len(got) != len(want) {
			return nil, fmt.Errorf(
				"permission set definition lifecycle inventory role %q has tables %v, want %v",
				role,
				got,
				want,
			)
		}
		for index := range got {
			if got[index] != want[index] {
				return nil, fmt.Errorf(
					"permission set definition lifecycle inventory role %q has tables %v, want %v",
					role,
					got,
					want,
				)
			}
		}
		delete(roles, role)
	}
	if len(roles) != 0 {
		return nil, fmt.Errorf("permission set definition lifecycle inventory contains unsupported roles %v", roles)
	}
	readSet := make([]cp.ComponentReadSurface, 0, len(readTables))
	for table := range readTables {
		readSet = append(readSet, cp.ComponentReadSurface{
			Table: table,
			Fact:  "Permission Set definition projection or dependency",
		})
	}
	return readSet, nil
}

// PermissionSetDefinitionManagedTarget maps a PermissionSet.Name to the same
// case-insensitive physical identity used by the database uniqueness constraint.
func PermissionSetDefinitionManagedTarget(name string) (cp.ComponentManagedTarget, error) {
	if err := validatePermissionSetDefinitionIdentifier("Permission Set name", name, 255); err != nil {
		return cp.ComponentManagedTarget{}, err
	}
	return cp.ComponentManagedTarget{
		TargetDomain:     permissionSetDefinitionManagedTargetDomain,
		PhysicalKeyBytes: []byte(strings.ToLower(name)),
	}, nil
}

func buildPermissionSetDefinition(ctx cp.ComponentBuildContext, request cp.ComponentBuildRequest) error {
	if request.Component.Kind != permissionSetDefinitionComponentKind {
		return fmt.Errorf("permission set definition payload: component kind %q is unsupported", request.Component.Kind)
	}
	if request.Component.Manageability != cp.ManageabilityLocked {
		return fmt.Errorf("permission set definition payload: manageability must be %q", cp.ManageabilityLocked)
	}
	if strings.TrimSpace(request.PackName) == "" || request.PackName != ctx.PackName() {
		return fmt.Errorf("permission set definition payload: pack name does not match build context")
	}
	if err := validatePermissionSetDefinitionIdentifier("Permission Set name", request.Component.BusinessKey, 255); err != nil {
		return err
	}
	payload, err := decodePermissionSetDefinitionPayload(request.Payload)
	if err != nil {
		return err
	}
	projection := permissionSetDefinitionProjection{
		Present:           true,
		Name:              request.Component.BusinessKey,
		OwnerPack:         request.PackName,
		Label:             payload.Label,
		Description:       payload.Description,
		Active:            true,
		SystemPermissions: payload.SystemPermissions,
		CustomPermissions: payload.CustomPermissions,
		ObjectPermissions: payload.ObjectPermissions,
		FieldPermissions:  payload.FieldPermissions,
	}
	expected, err := json.Marshal(projection)
	if err != nil {
		return fmt.Errorf("permission set definition payload: encode projection: %w", err)
	}
	target, err := PermissionSetDefinitionManagedTarget(request.Component.BusinessKey)
	if err != nil {
		return err
	}
	return ctx.AddManagedComponent(cp.ComponentBuildManagedComponent{
		ExpectedProjection: expected,
		CollisionClaims: []cp.ComponentCollisionClaim{{
			Namespace: permissionSetDefinitionNameCollisionNamespace,
			Key:       string(target.PhysicalKeyBytes),
		}},
	})
}

func decodePermissionSetDefinitionPayload(raw []byte) (permissionSetDefinitionPayload, error) {
	var fields map[string]json.RawMessage
	fieldDecoder := json.NewDecoder(bytes.NewReader(raw))
	if err := fieldDecoder.Decode(&fields); err != nil {
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: %w", err)
	}
	var trailingFields any
	if err := fieldDecoder.Decode(&trailingFields); err != io.EOF {
		if err == nil {
			return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: trailing input")
		}
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: trailing input: %w", err)
	}
	for _, field := range []string{
		"schema",
		"label",
		"active",
		"systemPermissions",
		"customPermissions",
		"objectPermissions",
		"fieldPermissions",
	} {
		value, present := fields[field]
		if !present {
			return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: %s is required", field)
		}
		if bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
			return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: %s must not be null", field)
		}
	}
	if description, present := fields["description"]; present &&
		bytes.Equal(bytes.TrimSpace(description), []byte("null")) {
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: description must not be null")
	}
	if err := validatePermissionSetDefinitionRawGrantFields(
		"objectPermissions",
		fields["objectPermissions"],
		[]string{"object", "read", "create", "edit", "delete", "viewAllRecords", "modifyAllRecords"},
	); err != nil {
		return permissionSetDefinitionPayload{}, err
	}
	if err := validatePermissionSetDefinitionRawGrantFields(
		"fieldPermissions",
		fields["fieldPermissions"],
		[]string{"object", "field", "read", "edit"},
	); err != nil {
		return permissionSetDefinitionPayload{}, err
	}

	var payload permissionSetDefinitionPayload
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&payload); err != nil {
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: %w", err)
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: trailing input")
		}
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: trailing input: %w", err)
	}
	if payload.Schema != permissionSetDefinitionPayloadSchema {
		return permissionSetDefinitionPayload{}, fmt.Errorf(
			"permission set definition payload: schema must be %q",
			permissionSetDefinitionPayloadSchema,
		)
	}
	payload.Label = strings.TrimSpace(payload.Label)
	if payload.Label == "" || len(payload.Label) > 255 {
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: label must contain 1..255 bytes")
	}
	if payload.Description != nil {
		normalized := strings.TrimSpace(*payload.Description)
		if normalized == "" || len(normalized) > 4096 {
			return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: description must contain 1..4096 bytes")
		}
		payload.Description = &normalized
	}
	if !payload.Active {
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: active must be true; omit the component to retire it")
	}
	if payload.AdoptionMode != "" && payload.AdoptionMode != "exact_match" {
		return permissionSetDefinitionPayload{}, fmt.Errorf("permission set definition payload: adoptionMode must be %q", "exact_match")
	}
	for _, collection := range []struct {
		field string
		count int
	}{
		{field: "systemPermissions", count: len(payload.SystemPermissions)},
		{field: "customPermissions", count: len(payload.CustomPermissions)},
		{field: "objectPermissions", count: len(payload.ObjectPermissions)},
		{field: "fieldPermissions", count: len(payload.FieldPermissions)},
	} {
		if collection.count > permissionSetDefinitionManagedMaximumRows {
			return permissionSetDefinitionPayload{}, fmt.Errorf(
				"permission set definition payload: %s contains %d entries; maximum is %d",
				collection.field,
				collection.count,
				permissionSetDefinitionManagedMaximumRows,
			)
		}
	}
	if total := 1 +
		len(payload.SystemPermissions) +
		len(payload.CustomPermissions) +
		len(payload.ObjectPermissions) +
		len(payload.FieldPermissions); total > 2000 {
		return permissionSetDefinitionPayload{}, fmt.Errorf(
			"permission set definition payload contains %d managed rows; maximum is 2000",
			total,
		)
	}
	if err := canonicalizePermissionSetDefinitionIdentifiers("systemPermissions", payload.SystemPermissions); err != nil {
		return permissionSetDefinitionPayload{}, err
	}
	if err := canonicalizePermissionSetDefinitionIdentifiers("customPermissions", payload.CustomPermissions); err != nil {
		return permissionSetDefinitionPayload{}, err
	}
	if err := canonicalizePermissionSetDefinitionObjectGrants(payload.ObjectPermissions); err != nil {
		return permissionSetDefinitionPayload{}, err
	}
	if err := canonicalizePermissionSetDefinitionFieldGrants(payload.FieldPermissions); err != nil {
		return permissionSetDefinitionPayload{}, err
	}
	return payload, nil
}

func validatePermissionSetDefinitionRawGrantFields(
	field string,
	raw json.RawMessage,
	required []string,
) error {
	var entries []map[string]json.RawMessage
	if err := json.Unmarshal(raw, &entries); err != nil {
		return fmt.Errorf("permission set definition payload: %s: %w", field, err)
	}
	for index, entry := range entries {
		for _, name := range required {
			value, present := entry[name]
			if !present {
				return fmt.Errorf(
					"permission set definition payload: %s[%d].%s is required",
					field,
					index,
					name,
				)
			}
			if bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
				return fmt.Errorf(
					"permission set definition payload: %s[%d].%s must not be null",
					field,
					index,
					name,
				)
			}
		}
	}
	return nil
}

func canonicalizePermissionSetDefinitionIdentifiers(field string, values []string) error {
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		if err := validatePermissionSetDefinitionIdentifier(field+" entry", value, 255); err != nil {
			return err
		}
		key := strings.ToLower(value)
		if _, duplicate := seen[key]; duplicate {
			return fmt.Errorf("permission set definition payload: %s contains duplicates under database collation", field)
		}
		seen[key] = struct{}{}
	}
	sort.Slice(values, func(i, j int) bool {
		return strings.ToLower(values[i]) < strings.ToLower(values[j])
	})
	return nil
}

func canonicalizePermissionSetDefinitionObjectGrants(grants []permissionSetDefinitionObjectGrant) error {
	seen := make(map[string]struct{}, len(grants))
	for _, grant := range grants {
		if err := validatePermissionSetDefinitionIdentifier("objectPermissions object", grant.Object, 255); err != nil {
			return err
		}
		if err := validateObjectPermissionGrantDependencies(
			grant.Object,
			grant.Read,
			grant.Create,
			grant.Edit,
			grant.Delete,
			grant.ViewAllRecords,
			grant.ModifyAllRecords,
		); err != nil {
			return fmt.Errorf("permission set definition payload: objectPermissions: %w", err)
		}
		key := strings.ToLower(grant.Object)
		if _, duplicate := seen[key]; duplicate {
			return fmt.Errorf("permission set definition payload: objectPermissions contains duplicates under database collation")
		}
		seen[key] = struct{}{}
	}
	sort.Slice(grants, func(i, j int) bool {
		return strings.ToLower(grants[i].Object) < strings.ToLower(grants[j].Object)
	})
	return nil
}

func canonicalizePermissionSetDefinitionFieldGrants(grants []permissionSetDefinitionFieldGrant) error {
	seen := make(map[string]struct{}, len(grants))
	for _, grant := range grants {
		if err := validatePermissionSetDefinitionIdentifier("fieldPermissions object", grant.Object, 255); err != nil {
			return err
		}
		if err := validatePermissionSetDefinitionIdentifier("fieldPermissions field", grant.Field, 255); err != nil {
			return err
		}
		if len(grant.Object)+1+len(grant.Field) > 255 {
			return fmt.Errorf(
				"permission set definition payload: fieldPermissions qualified field %s.%s exceeds 255 bytes",
				grant.Object,
				grant.Field,
			)
		}
		if !fieldpermissions.AccessCoherent(grant.Read, grant.Edit) {
			return fmt.Errorf(
				"permission set definition payload: fieldPermissions: FIELD_INTEGRITY_EXCEPTION: Permission Edit %s.%s depends on permission(s): Read %s.%s",
				grant.Object,
				grant.Field,
				grant.Object,
				grant.Field,
			)
		}
		key := strings.ToLower(grant.Object) + "\x00" + strings.ToLower(grant.Field)
		if _, duplicate := seen[key]; duplicate {
			return fmt.Errorf("permission set definition payload: fieldPermissions contains duplicates under database collation")
		}
		seen[key] = struct{}{}
	}
	sort.Slice(grants, func(i, j int) bool {
		left := strings.ToLower(grants[i].Object) + "\x00" + strings.ToLower(grants[i].Field)
		right := strings.ToLower(grants[j].Object) + "\x00" + strings.ToLower(grants[j].Field)
		return left < right
	})
	return nil
}

func validatePermissionSetDefinitionIdentifier(field, value string, maximumBytes int) error {
	if len(value) == 0 || len(value) > maximumBytes {
		return fmt.Errorf("permission set definition payload: %s must contain 1..%d bytes", field, maximumBytes)
	}
	for index, character := range []byte(value) {
		isLetter := character >= 'A' && character <= 'Z' || character >= 'a' && character <= 'z'
		isDigit := character >= '0' && character <= '9'
		if !isLetter && character != '_' && (index == 0 || !isDigit) {
			return fmt.Errorf(
				"permission set definition payload: %s %q must match [A-Za-z_][A-Za-z0-9_]*",
				field,
				value,
			)
		}
	}
	return nil
}
