package dialectsfcrm

import (
	"context"
	"fmt"
	"strings"

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

const (
	approvalProcessDefinitionTable = "ProcessDefinition"
	approvalProcessNodeTable       = "ProcessNode"
	approvalProcessActionTable     = "ProcessAction"
)

// preflightApprovalProcessManagedComponent enforces the globally unique
// ProcessDefinition.DeveloperName collision surface before the host opens a
// mutation transaction. Existing rows remain writable only by their exact
// owner pack and only for the same target object.
func preflightApprovalProcessManagedComponent(ctx context.Context, reader cp.ComponentRowReader, request cp.ComponentManagedRequest) error {
	if reader == nil {
		return fmt.Errorf("approval process preflight row reader is required")
	}
	plan, err := decodeApprovalProcessManagedRequest(request)
	if err != nil {
		return fmt.Errorf("approval process preflight: %w", err)
	}
	key := plan.Parent.BusinessKey
	// ProcessDefinition.DeveloperName is unique on the production
	// utf8mb4_unicode_ci table, so the host's equality lookup is
	// case-insensitive for this ASCII-only identifier.
	rows, err := reader.ReadRows(ctx, cp.ComponentRowQuery{
		Table:   approvalProcessDefinitionTable,
		Columns: []string{"Id", "DeveloperName", "OwnerPack", "TableEnumOrId"},
		Equal:   cp.ComponentRow{"DeveloperName": key.DeveloperName},
	})
	if err != nil {
		return fmt.Errorf("query ProcessDefinition %s: %w", key.DeveloperName, err)
	}
	if len(rows) == 0 {
		return nil
	}
	if len(rows) != 1 {
		return fmt.Errorf("approval process developer name %s resolves to %d ProcessDefinition rows; expected exactly one globally unique row", key.DeveloperName, len(rows))
	}

	rowID, err := approvalProcessManagedRowString(rows[0], "Id", true)
	if err != nil {
		return err
	}
	liveDeveloperName, err := approvalProcessManagedRowString(rows[0], "DeveloperName", true)
	if err != nil {
		return err
	}
	if !strings.EqualFold(liveDeveloperName, key.DeveloperName) {
		return fmt.Errorf("ProcessDefinition row %s DeveloperName %q does not match requested developer name %q", rowID, liveDeveloperName, key.DeveloperName)
	}
	ownerPack, err := approvalProcessManagedRowString(rows[0], "OwnerPack", false)
	if err != nil {
		return err
	}
	objectAPIName, err := approvalProcessManagedRowString(rows[0], "TableEnumOrId", true)
	if err != nil {
		return err
	}
	if strings.TrimSpace(objectAPIName) != objectAPIName {
		return fmt.Errorf("ProcessDefinition row %s TableEnumOrId %q is not canonical", rowID, objectAPIName)
	}
	if !strings.EqualFold(objectAPIName, key.ObjectAPIName) {
		return fmt.Errorf("approval process developer name %s already exists for object %s (row %s); ProcessDefinition.DeveloperName is globally unique", key.DeveloperName, objectAPIName, rowID)
	}
	if ownerPack != plan.Parent.OwnerPack {
		return fmt.Errorf("approval process %s/%s OwnerPack = %q (row %s), refusing to install for %q", key.ObjectAPIName, key.DeveloperName, ownerPack, rowID, plan.Parent.OwnerPack)
	}
	return nil
}

func approvalProcessManagedRowString(row cp.ComponentRow, column string, required bool) (string, error) {
	value, exists := row[column]
	if !exists {
		return "", fmt.Errorf("ProcessDefinition row is missing %s", column)
	}
	var text string
	switch value := value.(type) {
	case nil:
	case string:
		text = value
	default:
		return "", fmt.Errorf("ProcessDefinition row %s has unsupported value type %T", column, value)
	}
	if required && strings.TrimSpace(text) == "" {
		return "", fmt.Errorf("ProcessDefinition row %s is required", column)
	}
	return text, nil
}
