package dialectsfcrm

import (
	"context"
	"fmt"
	"strings"

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

// approvalProcessUserReferenceResolver resolves the first authored User
// username. The executor owns the legacy non-fatal diagnostic and NULL fallback.
type approvalProcessUserReferenceResolver func(context.Context, string) (string, error)

func applyApprovalProcessManagedComponent(ctx context.Context, store cp.ComponentMutationStore, request cp.ComponentManagedRequest) error {
	if store == nil {
		return fmt.Errorf("approval process apply mutation store is required")
	}
	plan, err := decodeApprovalProcessManagedRequest(request)
	if err != nil {
		return fmt.Errorf("approval process apply: %w", err)
	}
	return reconcileApprovalProcessPersistencePlan(ctx, store, plan, nil)
}

func reconcileApprovalProcessPersistencePlan(ctx context.Context, store cp.ComponentMutationStore, plan ApprovalProcessPersistencePlan, resolveUser approvalProcessUserReferenceResolver) error {
	if err := plan.Validate(); err != nil {
		return fmt.Errorf("validate approval process persistence plan: %w", err)
	}
	if plan.ParentDisposition == ApprovalProcessParentPreAppliedOwnerless && resolveUser == nil {
		for _, node := range plan.Nodes {
			if node.ApproverType == constants.ApprovalApproverTypeUser {
				return fmt.Errorf("pre-applied ownerless approval process with user approvers requires a user-reference resolver")
			}
		}
	}
	if store == nil {
		return fmt.Errorf("approval process mutation store is required")
	}

	key := plan.Parent.BusinessKey
	parentID, parentExists := "", false
	switch plan.ParentDisposition {
	case ApprovalProcessParentManagedUpsert:
		parents, err := store.LockRows(ctx, cp.ComponentRowQuery{
			Table: approvalProcessDefinitionTable, Columns: []string{"Id", "DeveloperName", "OwnerPack", "TableEnumOrId"},
			Equal: cp.ComponentRow{"DeveloperName": key.DeveloperName},
		})
		if err != nil {
			return fmt.Errorf("lock ProcessDefinition %s: %w", key.DeveloperName, err)
		}
		if len(parents) > 1 {
			return fmt.Errorf("approval process developer name %s resolves to %d ProcessDefinition rows; expected exactly one globally unique row", key.DeveloperName, len(parents))
		}
		parentExists = len(parents) == 1
		if parentExists {
			parentID, err = validateApprovalProcessManagedParent(parents[0], plan.Parent)
			if err != nil {
				return err
			}
		} else {
			parentID = store.NewID()
		}
	case ApprovalProcessParentPreAppliedOwnerless:
		parents, err := store.LockRows(ctx, cp.ComponentRowQuery{
			Table: approvalProcessDefinitionTable, Columns: []string{"Id", "DeveloperName", "OwnerPack", "TableEnumOrId", "Metadata"},
			Equal: cp.ComponentRow{"Id": plan.PreAppliedParentID},
		})
		if err != nil {
			return fmt.Errorf("lock pre-applied ProcessDefinition %s: %w", plan.PreAppliedParentID, err)
		}
		if len(parents) != 1 {
			return fmt.Errorf("pre-applied ProcessDefinition %s resolves to %d rows; expected exactly one", plan.PreAppliedParentID, len(parents))
		}
		parentID, err = validateApprovalProcessPreAppliedParent(parents[0], plan)
		if err != nil {
			return err
		}
		parentExists = true
	}

	liveNodes, deletedNodes, liveNodeIDs := map[string]string{}, map[string][]string{}, []string{}
	rows, err := store.LockRows(ctx, cp.ComponentRowQuery{
		Table: approvalProcessNodeTable, Columns: []string{"Id", "DeveloperName", "IsDeleted"},
		Equal: cp.ComponentRow{"ProcessDefinitionId": parentID},
	})
	if err != nil {
		return fmt.Errorf("lock ProcessNode rows for %s: %w", parentID, err)
	}
	for _, row := range rows {
		deleted, deletedErr := approvalProcessManagedRowDeleted(row)
		if deletedErr != nil {
			return deletedErr
		}
		id, idOK := row["Id"].(string)
		name, nameOK := row["DeveloperName"].(string)
		if !idOK || strings.TrimSpace(id) == "" || strings.TrimSpace(id) != id || !nameOK || !metadataDeploySafeIdentifier.MatchString(name) {
			if deleted {
				continue
			}
			return fmt.Errorf("ProcessNode row has invalid Id or DeveloperName")
		}
		folded := strings.ToLower(name)
		if deleted {
			deletedNodes[folded] = append(deletedNodes[folded], id)
			continue
		}
		if _, duplicate := liveNodes[folded]; duplicate {
			return fmt.Errorf("approval process %s/%s has duplicate active ProcessNode developer name %q", key.ObjectAPIName, key.DeveloperName, name)
		}
		liveNodes[folded] = id
		liveNodeIDs = append(liveNodeIDs, id)
	}
	if _, err = store.LockRows(ctx, cp.ComponentRowQuery{
		Table: approvalProcessActionTable, Columns: []string{"Id"},
		Equal: cp.ComponentRow{"ProcessDefinitionId": parentID},
	}); err != nil {
		return fmt.Errorf("lock ProcessAction rows for %s: %w", parentID, err)
	}

	nodeIDs, retained, existingNodeIDs := map[ApprovalProcessNodeBusinessKey]string{}, map[string]struct{}{}, map[string]struct{}{}
	for _, node := range plan.Nodes {
		folded := strings.ToLower(node.BusinessKey.DeveloperName)
		id := liveNodes[folded]
		if id == "" && len(deletedNodes[folded]) > 1 {
			return fmt.Errorf("approval process %s/%s has %d deleted ProcessNode rows for developer name %q; cannot choose a stable reactivation identity", key.ObjectAPIName, key.DeveloperName, len(deletedNodes[folded]), node.BusinessKey.DeveloperName)
		}
		if id == "" && len(deletedNodes[folded]) == 1 {
			id = deletedNodes[folded][0]
		}
		if id == "" {
			id = store.NewID()
		} else {
			existingNodeIDs[id] = struct{}{}
		}
		nodeIDs[node.BusinessKey], retained[id] = id, struct{}{}
	}
	resolvedUserIDs := map[ApprovalProcessNodeBusinessKey]string{}
	if plan.ParentDisposition == ApprovalProcessParentPreAppliedOwnerless {
		for _, node := range plan.Nodes {
			if node.ApproverType != constants.ApprovalApproverTypeUser {
				continue
			}
			username := node.ApproverReferences[0]
			userID, lookupErr := resolveUser(ctx, username)
			if lookupErr != nil || strings.TrimSpace(userID) == "" {
				contract.ReportRecordLifecycleDiagnostic(ctx, contract.RecordLifecycleDiagnostic{
					Problem: fmt.Sprintf("Could not resolve approver user '%s' — stored without UserId", username), ComponentType: "ApprovalProcess",
				})
				continue
			}
			resolvedUserIDs[node.BusinessKey] = userID
		}
	}

	if plan.ParentDisposition == ApprovalProcessParentManagedUpsert {
		parentValues := approvalProcessManagedParentValues(plan.Parent)
		if parentExists {
			_, err = store.UpdateRows(ctx, cp.ComponentUpdate{Table: approvalProcessDefinitionTable,
				Match: cp.ComponentRow{"Id": parentID, "OwnerPack": plan.Parent.OwnerPack}, Values: parentValues,
				CurrentTimestampColumns: []string{"LastModifiedDate"}})
		} else {
			parentValues["Id"] = parentID
			err = store.InsertRow(ctx, cp.ComponentInsert{Table: approvalProcessDefinitionTable, Values: parentValues,
				CurrentTimestampColumns: []string{"CreatedDate", "LastModifiedDate"}})
		}
		if err != nil {
			return fmt.Errorf("persist ProcessDefinition %s/%s: %w", key.ObjectAPIName, key.DeveloperName, err)
		}
	}
	for _, node := range plan.Nodes {
		id := nodeIDs[node.BusinessKey]
		values := approvalProcessPersistenceNodeValues(parentID, node, resolvedUserIDs[node.BusinessKey])
		if _, exists := existingNodeIDs[id]; exists {
			_, err = store.UpdateRows(ctx, cp.ComponentUpdate{Table: approvalProcessNodeTable,
				Match: cp.ComponentRow{"Id": id, "ProcessDefinitionId": parentID}, Values: values,
				CurrentTimestampColumns: []string{"LastModifiedDate"}})
		} else {
			values["Id"] = id
			err = store.InsertRow(ctx, cp.ComponentInsert{Table: approvalProcessNodeTable, Values: values,
				CurrentTimestampColumns: []string{"CreatedDate", "LastModifiedDate"}})
		}
		if err != nil {
			return fmt.Errorf("persist ProcessNode %s: %w", node.BusinessKey.DeveloperName, err)
		}
	}
	for _, id := range liveNodeIDs {
		if _, keep := retained[id]; keep {
			continue
		}
		if _, err = store.UpdateRows(ctx, cp.ComponentUpdate{Table: approvalProcessNodeTable,
			Match:  cp.ComponentRow{"Id": id, "ProcessDefinitionId": parentID},
			Values: cp.ComponentRow{"IsDeleted": true}, CurrentTimestampColumns: []string{"LastModifiedDate"}}); err != nil {
			return fmt.Errorf("soft-delete obsolete ProcessNode %s: %w", id, err)
		}
	}
	if parentExists {
		if _, err = store.DeleteRows(ctx, cp.ComponentDelete{Table: approvalProcessActionTable,
			Match: cp.ComponentRow{"ProcessDefinitionId": parentID}}); err != nil {
			return fmt.Errorf("delete ProcessAction rows for %s: %w", parentID, err)
		}
	}
	for _, action := range plan.Actions {
		values, valuesErr := approvalProcessManagedActionValues(parentID, nodeIDs, action)
		if valuesErr != nil {
			return valuesErr
		}
		values["Id"] = store.NewID()
		if err = store.InsertRow(ctx, cp.ComponentInsert{Table: approvalProcessActionTable, Values: values,
			CurrentTimestampColumns: []string{"CreatedDate", "LastModifiedDate"}}); err != nil {
			return fmt.Errorf("insert ProcessAction %s: %w", action.Name, err)
		}
	}
	return nil
}

func validateApprovalProcessPreAppliedParent(row cp.ComponentRow, plan ApprovalProcessPersistencePlan) (string, error) {
	values := make([]string, 5)
	for index, column := range []string{"Id", "DeveloperName", "OwnerPack", "TableEnumOrId", "Metadata"} {
		value, err := approvalProcessManagedRowString(row, column, index != 2)
		if err != nil {
			return "", err
		}
		values[index] = value
	}
	id, developerName, owner, object, metadata := values[0], values[1], values[2], values[3], values[4]
	if id != plan.PreAppliedParentID {
		return "", fmt.Errorf("pre-applied ProcessDefinition Id %q does not match requested Id %q", id, plan.PreAppliedParentID)
	}
	if owner != "" {
		return "", fmt.Errorf("pre-applied ProcessDefinition %s must be ownerless, got OwnerPack %q", id, owner)
	}
	if !strings.EqualFold(developerName, plan.Parent.BusinessKey.DeveloperName) || strings.TrimSpace(object) != object || !strings.EqualFold(object, plan.Parent.BusinessKey.ObjectAPIName) {
		return "", fmt.Errorf("pre-applied ProcessDefinition %s identity does not match %s/%s", id, plan.Parent.BusinessKey.ObjectAPIName, plan.Parent.BusinessKey.DeveloperName)
	}
	if metadata != plan.Parent.RawMetadata {
		return "", fmt.Errorf("pre-applied ProcessDefinition %s Metadata is not byte-identical to the persistence plan", id)
	}
	return id, nil
}

func validateApprovalProcessManagedParent(row cp.ComponentRow, parent ApprovalProcessParentRowIntent) (string, error) {
	values := make([]string, 4)
	for index, column := range []string{"Id", "DeveloperName", "OwnerPack", "TableEnumOrId"} {
		value, err := approvalProcessManagedRowString(row, column, index != 2)
		if err != nil {
			return "", err
		}
		values[index] = value
	}
	id, developerName, owner, object := values[0], values[1], values[2], values[3]
	if strings.TrimSpace(id) != id {
		return "", fmt.Errorf("ProcessDefinition row Id %q is not canonical", id)
	}
	if !strings.EqualFold(developerName, parent.BusinessKey.DeveloperName) {
		return "", fmt.Errorf("ProcessDefinition row %s DeveloperName does not match requested developer name", id)
	}
	if strings.TrimSpace(object) != object || !strings.EqualFold(object, parent.BusinessKey.ObjectAPIName) {
		return "", fmt.Errorf("approval process developer name %s already exists for object %s (row %s)", parent.BusinessKey.DeveloperName, object, id)
	}
	if owner != parent.OwnerPack {
		return "", fmt.Errorf("approval process %s/%s OwnerPack = %q (row %s), refusing to install for %q", parent.BusinessKey.ObjectAPIName, parent.BusinessKey.DeveloperName, owner, id, parent.OwnerPack)
	}
	return id, nil
}

func approvalProcessManagedParentValues(parent ApprovalProcessParentRowIntent) cp.ComponentRow {
	return cp.ComponentRow{"Name": parent.Name, "DeveloperName": parent.BusinessKey.DeveloperName,
		"Description": approvalProcessManagedNullable(parent.Description), "State": parent.State, "TableEnumOrId": parent.BusinessKey.ObjectAPIName,
		"LockType": approvalProcessManagedNullable(parent.LockType), "EntryFilter": approvalProcessManagedNullable(parent.EntryFilter),
		"FinalApprovalRecordLock": parent.FinalApprovalRecordLock, "FinalRejectionRecordLock": parent.FinalRejectionRecordLock,
		"AllowRecall": parent.AllowRecall, "IsActive": parent.IsActive, "Metadata": parent.RawMetadata,
		"ProcessOrder": approvalProcessManagedOptionalInt(parent.ProcessOrder), "OwnerPack": parent.OwnerPack}
}

func approvalProcessManagedNodeValues(parentID string, node ApprovalProcessNodeRowIntent) cp.ComponentRow {
	return approvalProcessPersistenceNodeValues(parentID, node, "")
}

func approvalProcessPersistenceNodeValues(parentID string, node ApprovalProcessNodeRowIntent, approverUserID string) cp.ComponentRow {
	var relatedField any
	if node.ApproverType == constants.ApprovalApproverTypeRelatedUserField && len(node.ApproverReferences) == 1 {
		relatedField = node.ApproverReferences[0]
	}
	var userID any
	if approverUserID != "" {
		userID = approverUserID
	}
	return cp.ComponentRow{"Name": node.Name, "DeveloperName": node.BusinessKey.DeveloperName, "ProcessDefinitionId": parentID,
		"Description": approvalProcessManagedNullable(node.Description), "IsDeleted": false,
		"ApproverType": approvalProcessManagedNullable(node.ApproverType), "ApproverUserId": userID,
		"ApproverRelatedField": relatedField,
		"AllowComments":        node.AllowComments, "RequireComments": node.RequireComments, "StepOrder": node.StepOrder,
		"RejectBehavior": approvalProcessManagedNullable(node.RejectBehavior)}
}

func approvalProcessManagedActionValues(parentID string, nodeIDs map[ApprovalProcessNodeBusinessKey]string, action ApprovalProcessActionRowIntent) (cp.ComponentRow, error) {
	var processNodeID any
	if action.ProcessNode != nil {
		id, exists := nodeIDs[*action.ProcessNode]
		if !exists {
			return nil, fmt.Errorf("approval process action %q references missing ProcessNode %s", action.Name, action.ProcessNode.DeveloperName)
		}
		processNodeID = id
	}
	var fieldValue any
	if action.FieldValue != nil {
		fieldValue = *action.FieldValue
	}
	return cp.ComponentRow{"Name": action.Name, "ProcessDefinitionId": parentID, "ProcessNodeId": processNodeID,
		"ActionType": action.ActionType, "ActionEvent": action.ActionEvent,
		"FieldApiName": approvalProcessManagedNullable(action.FieldAPIName), "FieldValue": fieldValue,
		"EmailTemplateId": approvalProcessManagedNullable(action.EmailTemplateID), "ApiName": approvalProcessManagedNullable(action.APIName),
		"Order": action.Order, "IsDeleted": false}, nil
}

func approvalProcessManagedRowDeleted(row cp.ComponentRow) (bool, error) {
	switch value := row["IsDeleted"].(type) {
	case nil:
		return false, nil
	case bool:
		return value, nil
	case string:
		if value == "0" || value == "false" {
			return false, nil
		}
		if value == "1" || value == "true" {
			return true, nil
		}
	case int64:
		if value == 0 || value == 1 {
			return value == 1, nil
		}
	}
	return false, fmt.Errorf("ProcessNode row IsDeleted has unsupported value %v (%T)", row["IsDeleted"], row["IsDeleted"])
}

func approvalProcessManagedNullable(value string) any {
	if value == "" {
		return nil
	}
	return value
}

func approvalProcessManagedOptionalInt(value *int) any {
	if value == nil {
		return nil
	}
	return *value
}
