package dialectsfcrm

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"

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

// ProcessDefinition has a global DeveloperName uniqueness constraint even
// though its business identity also records the target object.
const approvalProcessDeveloperNameCollision = "ProcessDefinition.DeveloperName"

// NewApprovalProcessComponentRegistration binds the CRM-owned ApprovalProcess
// grammar and persistence contract to the one complete managed provider.
func NewApprovalProcessComponentRegistration() (cp.ComponentKindRegistration, error) {
	provider := &cp.ComponentManagedProvider{
		Preflight: preflightApprovalProcessManagedComponent, Apply: applyApprovalProcessManagedComponent,
		VerifyProjection: verifyApprovalProcessManagedProjection, ListLiveBusinessKeys: listApprovalProcessManagedBusinessKeys,
		ReconcileSet: reconcileApprovalProcessManagedSet,
	}
	if err := provider.Validate(); err != nil {
		return cp.ComponentKindRegistration{}, fmt.Errorf("approval process managed provider: %w", err)
	}
	return cp.ComponentKindRegistration{
		Contract: cp.ComponentKindContract{
			Kind:  cp.ComponentKindApprovalProcess,
			Phase: cp.InstallPlanPhaseAutomation,
			WriteSet: []cp.ComponentWriteSurface{
				{Table: approvalProcessDefinitionTable, Fact: "approval process definition"},
				{Table: approvalProcessNodeTable, Fact: "approval process steps"},
				{Table: approvalProcessActionTable, Fact: "approval process actions"},
			},
			BusinessKey:        "object api name / approval process developer name",
			OwnerDiscriminator: cp.ComponentOwnerStrategyOwnerPack,
			Manageabilities:    approvalProcessManageabilities(),
			VerifyProjection:   "owner-scoped approval definition, active steps, and actions",
			CollisionKey:       "globally unique approval process developer name",
		},
		Capability: cp.SourceBackedComponentCapability{
			Kind:            cp.ComponentKindApprovalProcess,
			Installable:     true,
			Manageabilities: approvalProcessManageabilities(),
		},
		AllowedCategories: []cp.PackCategory{cp.AppPack},
		BuildHandler:      buildApprovalProcessManagedComponent,
		ManagedProvider:   provider,
	}, nil
}

func approvalProcessManageabilities() map[cp.Manageability]struct{} {
	return map[cp.Manageability]struct{}{cp.ManageabilityLocked: {}}
}

func buildApprovalProcessManagedComponent(buildContext cp.ComponentBuildContext, request cp.ComponentBuildRequest) error {
	if buildContext == nil {
		return fmt.Errorf("approval process build context is required")
	}
	if request.Component.Kind != cp.ComponentKindApprovalProcess {
		return fmt.Errorf("approval process build received component kind %q", request.Component.Kind)
	}
	packName := buildContext.PackName()
	if packName == "" || strings.TrimSpace(packName) != packName {
		return fmt.Errorf("approval process build context pack name %q is not canonical", packName)
	}
	if request.PackName != packName {
		return fmt.Errorf("approval process build pack name %q does not match context pack %q", request.PackName, packName)
	}
	if request.Component.Manageability != cp.ManageabilityLocked {
		return fmt.Errorf("approval process %q manageability must be %q, got %q", request.Component.BusinessKey, cp.ManageabilityLocked, request.Component.Manageability)
	}
	if len(request.Payload) == 0 || (len(request.Component.Payload) > 0 && !bytes.Equal(request.Component.Payload, request.Payload)) {
		return fmt.Errorf("approval process %q component payload must exactly match the build payload", request.Component.BusinessKey)
	}
	objectAPIName, developerName, err := splitApprovalProcessComponentBusinessKey(request.Component.BusinessKey)
	if err != nil {
		return err
	}
	rawMetadata := string(request.Payload)
	metadata, err := DecodeApprovalProcessMetadata(rawMetadata)
	if err != nil {
		return fmt.Errorf("decode approval process %s/%s: %w", objectAPIName, developerName, err)
	}
	plan, err := PlanApprovalProcessPersistence(ApprovalProcessProjectionRequest{
		ObjectAPIName:     objectAPIName,
		DeveloperName:     developerName,
		ParentDisposition: ApprovalProcessParentManagedUpsert,
		OwnerPack:         packName,
		RawMetadata:       rawMetadata,
		Metadata:          metadata,
	})
	if err != nil {
		return err
	}
	expected, err := json.Marshal(plan)
	if err != nil {
		return fmt.Errorf("marshal approval process %s/%s projection: %w", objectAPIName, developerName, err)
	}
	managed := cp.ComponentBuildManagedComponent{
		ExpectedProjection: expected,
		CollisionClaims: []cp.ComponentCollisionClaim{{
			Namespace: approvalProcessDeveloperNameCollision,
			Key:       strings.ToLower(developerName),
		}},
	}
	if err := managed.Validate(); err != nil {
		return fmt.Errorf("approval process %s/%s managed declaration: %w", objectAPIName, developerName, err)
	}
	if err := buildContext.AddManagedComponent(managed); err != nil {
		return fmt.Errorf("add approval process %s/%s managed component: %w", objectAPIName, developerName, err)
	}
	return nil
}

func splitApprovalProcessComponentBusinessKey(businessKey string) (string, string, error) {
	objectAPIName, developerName, found := strings.Cut(businessKey, "/")
	if !found || !metadataDeploySafeIdentifier.MatchString(objectAPIName) || !metadataDeploySafeIdentifier.MatchString(developerName) {
		return "", "", fmt.Errorf("approval_process business key %q must be canonical <ObjectAPIName>/<DeveloperName>", businessKey)
	}
	return objectAPIName, developerName, nil
}
