package dialectsfcrm

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

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

func verifyApprovalProcessManagedProjection(ctx context.Context, reader cp.ComponentRowReader, request cp.ComponentManagedRequest) ([]byte, []byte, error) {
	if reader == nil {
		return nil, nil, fmt.Errorf("approval process verify row reader is required")
	}
	plan, err := decodeApprovalProcessManagedRequest(request)
	if err != nil {
		return nil, nil, fmt.Errorf("approval process verify: %w", err)
	}
	expectedParent := approvalProcessManagedParentValues(plan.Parent)
	expectedNodes := make([]cp.ComponentRow, 0, len(plan.Nodes))
	nodeNames := make(map[ApprovalProcessNodeBusinessKey]string, len(plan.Nodes))
	for _, node := range plan.Nodes {
		row := approvalProcessManagedNodeValues("", node)
		delete(row, "ProcessDefinitionId")
		expectedNodes = append(expectedNodes, row)
		nodeNames[node.BusinessKey] = node.BusinessKey.DeveloperName
	}
	expectedActions := make([]cp.ComponentRow, 0, len(plan.Actions))
	for _, action := range plan.Actions {
		row, valuesErr := approvalProcessManagedActionValues("", nodeNames, action)
		if valuesErr != nil {
			return nil, nil, valuesErr
		}
		delete(row, "ProcessDefinitionId")
		row["ProcessNodeDeveloperName"] = row["ProcessNodeId"]
		delete(row, "ProcessNodeId")
		expectedActions = append(expectedActions, row)
	}
	expected, err := marshalApprovalProcessManagedRows(expectedParent, expectedNodes, expectedActions)
	if err != nil {
		return nil, nil, fmt.Errorf("marshal expected approval process projection: %w", err)
	}

	parents, err := reader.ReadRows(ctx, cp.ComponentRowQuery{
		Table:   approvalProcessDefinitionTable,
		Columns: []string{"Id", "Name", "DeveloperName", "Description", "State", "TableEnumOrId", "LockType", "EntryFilter", "FinalApprovalRecordLock", "FinalRejectionRecordLock", "AllowRecall", "IsActive", "Metadata", "ProcessOrder", "OwnerPack"},
		Equal:   cp.ComponentRow{"DeveloperName": plan.Parent.BusinessKey.DeveloperName},
	})
	if err != nil {
		return nil, nil, fmt.Errorf("read ProcessDefinition %s: %w", plan.Parent.BusinessKey.DeveloperName, err)
	}
	if len(parents) != 1 {
		return nil, nil, fmt.Errorf("approval process developer name %s resolves to %d ProcessDefinition rows; expected exactly one", plan.Parent.BusinessKey.DeveloperName, len(parents))
	}
	parentID, err := validateApprovalProcessManagedParent(parents[0], plan.Parent)
	if err != nil {
		return nil, nil, err
	}
	liveParent := cloneApprovalProcessManagedRow(parents[0])
	delete(liveParent, "Id")

	nodes, err := reader.ReadRows(ctx, cp.ComponentRowQuery{
		Table:   approvalProcessNodeTable,
		Columns: []string{"Id", "Name", "DeveloperName", "ProcessDefinitionId", "Description", "IsDeleted", "ApproverType", "ApproverUserId", "ApproverRelatedField", "AllowComments", "RequireComments", "StepOrder", "RejectBehavior"},
		Equal:   cp.ComponentRow{"ProcessDefinitionId": parentID},
	})
	if err != nil {
		return nil, nil, fmt.Errorf("read ProcessNode rows for %s: %w", parentID, err)
	}
	liveNodes := make([]cp.ComponentRow, 0, len(nodes))
	nodeIDNames, foldedNames := make(map[string]string, len(nodes)), make(map[string]struct{}, len(nodes))
	for _, node := range nodes {
		deleted, deletedErr := approvalProcessManagedRowDeleted(node)
		if deletedErr != nil {
			return nil, nil, fmt.Errorf("classify ProcessNode tombstone: %w", deletedErr)
		}
		if deleted {
			continue
		}
		id, err := approvalProcessManagedProjectionString("ProcessNode", node, "Id")
		if err != nil {
			return nil, nil, err
		}
		name, err := approvalProcessManagedProjectionString("ProcessNode", node, "DeveloperName")
		if err != nil {
			return nil, nil, err
		}
		if _, duplicate := nodeIDNames[id]; duplicate {
			return nil, nil, fmt.Errorf("approval process %s has duplicate active ProcessNode Id %q", parentID, id)
		}
		folded := strings.ToLower(name)
		if _, duplicate := foldedNames[folded]; duplicate {
			return nil, nil, fmt.Errorf("approval process %s has duplicate active ProcessNode developer name %q", parentID, name)
		}
		nodeIDNames[id], foldedNames[folded] = name, struct{}{}
		row := cloneApprovalProcessManagedRow(node)
		delete(row, "Id")
		delete(row, "ProcessDefinitionId")
		row["IsDeleted"] = false
		liveNodes = append(liveNodes, row)
	}

	actions, err := reader.ReadRows(ctx, cp.ComponentRowQuery{
		Table:   approvalProcessActionTable,
		Columns: []string{"Name", "ProcessDefinitionId", "ProcessNodeId", "ActionType", "ActionEvent", "FieldApiName", "FieldValue", "EmailTemplateId", "ApiName", "Order", "IsDeleted"},
		Equal:   cp.ComponentRow{"ProcessDefinitionId": parentID},
	})
	if err != nil {
		return nil, nil, fmt.Errorf("read ProcessAction rows for %s: %w", parentID, err)
	}
	liveActions := make([]cp.ComponentRow, 0, len(actions))
	for _, action := range actions {
		deleted, deletedErr := approvalProcessManagedRowDeleted(action)
		if deletedErr != nil {
			return nil, nil, fmt.Errorf("classify ProcessAction tombstone: %w", deletedErr)
		}
		if deleted {
			continue
		}
		nodeID, exists := action["ProcessNodeId"]
		if !exists {
			return nil, nil, fmt.Errorf("ProcessAction row is missing ProcessNodeId")
		}
		var nodeName any
		if nodeID != nil {
			id, ok := nodeID.(string)
			if !ok || strings.TrimSpace(id) == "" {
				return nil, nil, fmt.Errorf("ProcessAction row has invalid ProcessNodeId value %T", nodeID)
			}
			var found bool
			nodeName, found = nodeIDNames[id]
			if !found {
				return nil, nil, fmt.Errorf("ProcessAction row references missing active ProcessNode %q", id)
			}
		}
		row := cloneApprovalProcessManagedRow(action)
		delete(row, "ProcessDefinitionId")
		delete(row, "ProcessNodeId")
		row["IsDeleted"] = false
		row["ProcessNodeDeveloperName"] = nodeName
		liveActions = append(liveActions, row)
	}
	live, err := marshalApprovalProcessManagedRows(liveParent, liveNodes, liveActions)
	if err != nil {
		return nil, nil, fmt.Errorf("marshal live approval process projection: %w", err)
	}
	return expected, live, nil
}

func approvalProcessManagedProjectionString(table string, row cp.ComponentRow, column string) (string, error) {
	value, exists := row[column]
	text, ok := value.(string)
	if !exists || !ok || strings.TrimSpace(text) == "" || strings.TrimSpace(text) != text {
		return "", fmt.Errorf("%s row has invalid %s", table, column)
	}
	return text, nil
}

func cloneApprovalProcessManagedRow(row cp.ComponentRow) cp.ComponentRow {
	clone := make(cp.ComponentRow, len(row))
	for key, value := range row {
		switch key {
		case "FinalApprovalRecordLock", "FinalRejectionRecordLock", "AllowRecall", "IsActive", "IsDeleted", "AllowComments", "RequireComments":
			if number, ok := value.(int64); ok && (number == 0 || number == 1) {
				value = number == 1
			}
		}
		clone[key] = value
	}
	return clone
}

func marshalApprovalProcessManagedRows(parent cp.ComponentRow, nodes, actions []cp.ComponentRow) ([]byte, error) {
	encodeRows := func(rows []cp.ComponentRow) ([]json.RawMessage, error) {
		encoded := make([]json.RawMessage, 0, len(rows))
		for _, row := range rows {
			value, err := json.Marshal(row)
			if err != nil {
				return nil, err
			}
			encoded = append(encoded, value)
		}
		sort.Slice(encoded, func(i, j int) bool { return bytes.Compare(encoded[i], encoded[j]) < 0 })
		return encoded, nil
	}
	nodeJSON, err := encodeRows(nodes)
	if err != nil {
		return nil, err
	}
	actionJSON, err := encodeRows(actions)
	if err != nil {
		return nil, err
	}
	return json.Marshal(struct {
		Parent  cp.ComponentRow
		Nodes   []json.RawMessage
		Actions []json.RawMessage
	}{parent, nodeJSON, actionJSON})
}
