package permissions

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

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

type customPermissionPayload struct {
	Schema      string  `json:"schema"`
	Label       string  `json:"label"`
	Description *string `json:"description,omitempty"`
	Active      bool    `json:"active"`
}

type customPermissionProjection struct {
	Present     bool    `json:"present"`
	APIName     string  `json:"apiName"`
	OwnerPack   string  `json:"ownerPack"`
	Label       string  `json:"label"`
	Description *string `json:"description,omitempty"`
	Active      bool    `json:"active"`
	Deleted     bool    `json:"deleted"`
	Customized  bool    `json:"customized"`
}

const (
	customPermissionComponentKind          cp.ComponentKind    = "custom_permission"
	customPermissionInstallPlanPhase       cp.InstallPlanPhase = "security"
	customPermissionPayloadSchema                              = "nexuscrm.custom-permission/v1"
	customPermissionManagedTargetDomain                        = "custom_permission/api_name/utf8mb4_unicode_ci-v1"
	customPermissionManagedSetTargetDomain                     = "custom_permission/owner_pack/exact_set-v1"
	customPermissionCollisionNamespace                         = customPermissionManagedTargetDomain
	// CustomPermissionExactSetMaximumRows bounds one owner-pack live set for
	// exact-set lifecycle transactions and CRM serving proof.
	CustomPermissionExactSetMaximumRows   = 500
	customPermissionManagedMaximumRows    = CustomPermissionExactSetMaximumRows
	customPermissionMaximumRowsPerRead    = 2
	customPermissionMaximumOwnerPackBytes = 64
)

type customPermissionRetirementReferenceSurface struct {
	Table  string
	Column string
}

// NewCustomPermissionManagedLifecycleRegistration returns the source-backed
// security catalog declaration and its transaction-bound lifecycle authority.
func NewCustomPermissionManagedLifecycleRegistration() (
	cp.ComponentKindRegistration,
	cp.ComponentManagedLifecycleRegistration,
	error,
) {
	readSet, references, err := customPermissionLifecycleInventory()
	if err != nil {
		return cp.ComponentKindRegistration{}, cp.ComponentManagedLifecycleRegistration{}, err
	}
	manageabilities := map[cp.Manageability]struct{}{cp.ManageabilityLocked: {}}
	base := cp.ComponentKindRegistration{
		Contract: cp.ComponentKindContract{
			Kind:               customPermissionComponentKind,
			Phase:              customPermissionInstallPlanPhase,
			WriteSet:           []cp.ComponentWriteSurface{{Table: "_System_CustomPermission", Fact: "managed custom permission catalog state"}},
			BusinessKey:        "_System_CustomPermission.ApiName",
			OwnerDiscriminator: cp.ComponentOwnerStrategyOwnerPack,
			Manageabilities:    manageabilities,
			VerifyProjection:   customPermissionPayloadSchema,
			CollisionKey:       customPermissionCollisionNamespace,
		},
		Capability: cp.SourceBackedComponentCapability{
			Kind:            customPermissionComponentKind,
			Installable:     true,
			Manageabilities: manageabilities,
		},
		AllowedCategories: []cp.PackCategory{cp.AppPack},
		BuildHandler:      buildCustomPermission,
	}
	lifecycle, err := cp.NewComponentManagedLifecycleRegistration(cp.ComponentManagedLifecycleRegistrationSpec{
		Kind:    customPermissionComponentKind,
		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:            2*customPermissionManagedMaximumRows + 1,
			MaxCurrentItems:       3 * customPermissionManagedMaximumRows,
			MaxDesiredItems:       customPermissionManagedMaximumRows,
			MaxDeltaRows:          customPermissionManagedMaximumRows,
			MaxMutationBytes:      4 << 20,
			MaxMutationStatements: customPermissionManagedMaximumRows,
			MaxBatchRows:          customPermissionManagedMaximumRows,
			MaxEvidenceWrites:     1,
			MaxEffectCalls:        1,
			MaxLockWait:           5 * time.Second,
			MaxElapsed:            30 * time.Second,
			MaxAttempts:           3,
		},
		Lifecycle: cp.ComponentManagedLifecycleProvider{
			Plan: func(ctx context.Context, scope cp.ComponentManagedScope, request cp.ComponentManagedLifecycleRequest, store cp.ComponentManagedReadStore) (cp.ComponentManagedPlan, error) {
				return planCustomPermission(ctx, scope, request, store, references)
			},
			Mutate: func(ctx context.Context, scope cp.ComponentManagedScope, request cp.ComponentManagedLifecycleRequest, plan cp.ComponentManagedPlan, store cp.ComponentManagedMutationStore) (cp.ComponentManagedMutationResult, error) {
				return mutateCustomPermission(ctx, scope, request, plan, store, references)
			},
			Project: func(ctx context.Context, scope cp.ComponentManagedScope, request cp.ComponentManagedLifecycleRequest, store cp.ComponentManagedReadStore) (cp.ComponentManagedProjection, error) {
				return projectCustomPermission(ctx, scope, request, store, references)
			},
		},
	})
	if err != nil {
		return cp.ComponentKindRegistration{}, cp.ComponentManagedLifecycleRegistration{}, fmt.Errorf("custom permission lifecycle registration: %w", err)
	}
	return base, lifecycle, nil
}

func customPermissionLifecycleInventory() (
	[]cp.ComponentReadSurface,
	[]customPermissionRetirementReferenceSurface,
	error,
) {
	definitions, err := schemacatalog.ManagedTableDefinitions()
	if err != nil {
		return nil, nil, fmt.Errorf("custom permission lifecycle inventory: %w", err)
	}
	references := make([]customPermissionRetirementReferenceSurface, 0)
	for _, definition := range definitions {
		for _, foreignKey := range definition.ForeignKeys {
			if foreignKey.References != "_System_CustomPermission(Id)" {
				continue
			}
			if strings.TrimSpace(definition.TableName) == "" || strings.TrimSpace(foreignKey.Column) == "" {
				return nil, nil, fmt.Errorf("custom permission lifecycle inventory contains an invalid retirement reference")
			}
			references = append(references, customPermissionRetirementReferenceSurface{
				Table:  definition.TableName,
				Column: foreignKey.Column,
			})
		}
	}
	if len(references) == 0 {
		return nil, nil, fmt.Errorf("custom permission lifecycle inventory contains no canonical retirement references")
	}
	sort.Slice(references, func(i, j int) bool {
		if references[i].Table == references[j].Table {
			return references[i].Column < references[j].Column
		}
		return references[i].Table < references[j].Table
	})
	readSet := []cp.ComponentReadSurface{{
		Table: "_System_CustomPermission",
		Fact:  "custom permission catalog projection and owner set",
	}}
	for _, reference := range references {
		readSet = append(readSet, cp.ComponentReadSurface{
			Table: reference.Table,
			Fact:  "canonical custom permission retirement reference",
		})
	}
	return readSet, references, nil
}

func buildCustomPermission(ctx cp.ComponentBuildContext, request cp.ComponentBuildRequest) error {
	if request.Component.Kind != customPermissionComponentKind {
		return fmt.Errorf("custom permission payload: component kind %q is unsupported", request.Component.Kind)
	}
	if request.Component.Manageability != cp.ManageabilityLocked {
		return fmt.Errorf("custom permission payload: manageability must be %q", cp.ManageabilityLocked)
	}
	if err := validateCustomPermissionOwnerPack(request.PackName); err != nil {
		return err
	}
	if request.PackName != ctx.PackName() {
		return fmt.Errorf("custom permission payload: pack name does not match build context")
	}
	if err := validateCustomPermissionIdentifier("API name", request.Component.BusinessKey, 255); err != nil {
		return err
	}
	payload, err := decodeCustomPermissionPayload(request.Payload)
	if err != nil {
		return err
	}
	projection := customPermissionProjection{
		Present:     true,
		APIName:     request.Component.BusinessKey,
		OwnerPack:   request.PackName,
		Label:       payload.Label,
		Description: payload.Description,
		Active:      true,
	}
	expected, err := json.Marshal(projection)
	if err != nil {
		return fmt.Errorf("custom permission payload: encode projection: %w", err)
	}
	target, err := CustomPermissionManagedTarget(request.Component.BusinessKey)
	if err != nil {
		return err
	}
	return ctx.AddManagedComponent(cp.ComponentBuildManagedComponent{
		ExpectedProjection: expected,
		CollisionClaims: []cp.ComponentCollisionClaim{{
			Namespace: customPermissionCollisionNamespace,
			Key:       string(target.PhysicalKeyBytes),
		}},
	})
}

func decodeCustomPermissionPayload(raw []byte) (customPermissionPayload, error) {
	var fields map[string]json.RawMessage
	fieldDecoder := json.NewDecoder(bytes.NewReader(raw))
	if err := fieldDecoder.Decode(&fields); err != nil {
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: %w", err)
	}
	var trailingFields any
	if err := fieldDecoder.Decode(&trailingFields); err != io.EOF {
		if err == nil {
			return customPermissionPayload{}, fmt.Errorf("custom permission payload: trailing input")
		}
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: trailing input: %w", err)
	}
	for _, field := range []string{"schema", "label", "active"} {
		value, present := fields[field]
		if !present {
			return customPermissionPayload{}, fmt.Errorf("custom permission payload: %s is required", field)
		}
		if bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
			return customPermissionPayload{}, fmt.Errorf("custom permission payload: %s must not be null", field)
		}
	}
	if description, present := fields["description"]; present && bytes.Equal(bytes.TrimSpace(description), []byte("null")) {
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: description must not be null")
	}
	var payload customPermissionPayload
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&payload); err != nil {
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: %w", err)
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return customPermissionPayload{}, fmt.Errorf("custom permission payload: trailing input")
		}
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: trailing input: %w", err)
	}
	if payload.Schema != customPermissionPayloadSchema {
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: schema must be %q", customPermissionPayloadSchema)
	}
	payload.Label = strings.TrimSpace(payload.Label)
	if payload.Label == "" || len(payload.Label) > 255 {
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: label must contain 1..255 bytes")
	}
	if !payload.Active {
		return customPermissionPayload{}, fmt.Errorf("custom permission payload: active must be true; omit the component to retire it")
	}
	if payload.Description != nil {
		normalized := strings.TrimSpace(*payload.Description)
		if normalized == "" || len(normalized) > 4096 {
			return customPermissionPayload{}, fmt.Errorf("custom permission payload: description must contain 1..4096 bytes")
		}
		payload.Description = &normalized
	}
	return payload, nil
}

// CustomPermissionManagedTarget maps an API name to the same
// case-insensitive identity used by the database uniqueness constraint.
func CustomPermissionManagedTarget(apiName string) (cp.ComponentManagedTarget, error) {
	if err := validateCustomPermissionIdentifier("API name", apiName, 255); err != nil {
		return cp.ComponentManagedTarget{}, err
	}
	return cp.ComponentManagedTarget{
		TargetDomain:     customPermissionManagedTargetDomain,
		PhysicalKeyBytes: []byte(strings.ToLower(apiName)),
	}, nil
}

func validateCustomPermissionIdentifier(field, value string, maximumBytes int) error {
	if len(value) == 0 || len(value) > maximumBytes {
		return fmt.Errorf("custom permission 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("custom permission payload: %s %q must match [A-Za-z_][A-Za-z0-9_]*", field, value)
		}
	}
	return nil
}

func validateCustomPermissionOwnerPack(packName string) error {
	if len(packName) == 0 || len(packName) > customPermissionMaximumOwnerPackBytes || strings.TrimSpace(packName) != packName {
		return fmt.Errorf(
			"custom permission payload: pack name must contain 1..%d bytes without surrounding whitespace",
			customPermissionMaximumOwnerPackBytes,
		)
	}
	return nil
}
