package records

import (
	"context"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

// CreateTreeNodes is the neutral tree-create engine path — the sole tree-create
// surface below the dialect boundary. It operates over the typed nested tree IR
// (contract.TreeNode) instead of SF-shaped maps: there is nothing to decode
// because the IR is already structured, so the whole SF Composite-Tree wire
// decode (pulling the object type and intra-batch alias out of the per-node
// metadata sub-map, unwrapping each child relationship's nested record
// collection, and skipping the wire-only keys) lives entirely in the dialect —
// the SF dialect performs that decode before it reaches this boundary (see
// dialect-sf-crm). Every platform mechanism is applied here: the group-membership
// object-name closure, the generic-list-view mutation guard under the
// !BypassesCRUDandFLS() condition, the exclusive-sibling shared-write guard, the
// pessimistic group-membership transaction, resolveFK, Insert, and the
// intra-batch parent-before-child ReferenceID -> id alias resolution.
func (ps *PersistenceService) CreateTreeNodes(ctx context.Context, nodes []contract.TreeNode, user *models.UserSession) ([]contract.TreeCreateResult, error) {
	results := make([]contract.TreeCreateResult, 0, len(nodes))
	objectNames := groupMembershipClosureObjectNamesFromTreeNodes(nodes)
	if !constants.GetSecurityContext(ctx).BypassesCRUDandFLS() {
		for _, objectName := range objectNames {
			if err := rejectGenericListViewMutation(objectName, "tree create"); err != nil {
				return nil, err
			}
		}
	}
	if err := ps.rejectExclusiveSiblingSharedWriteSurface(ctx, "CreateTreeNodes", objectNames); err != nil {
		return nil, err
	}

	err := ps.runGroupMembershipAwarePessimisticTransactionForObjects(ctx, objectNames, func(txCtx context.Context) error {
		for _, node := range nodes {
			result, err := ps.insertTreeNodeIR(txCtx, node, nil, user)
			if err != nil {
				return err
			}
			results = append(results, result)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return results, nil
}

// insertTreeNodeIR inserts one node of the typed-IR tree. It reads
// node.ObjectType / node.ReferenceID / node.Fields / node.Children directly and
// applies the ordering and FK-resolution semantics of a nested-tree create:
// resolve the parent FK, insert the current record, then recurse into each child
// relationship carrying the freshly created parent id.
func (ps *PersistenceService) insertTreeNodeIR(ctx context.Context, node contract.TreeNode, parent *parentInfo, user *models.UserSession) (contract.TreeCreateResult, error) {
	objType := node.ObjectType

	// Copy the node's fields into the record image the engine will insert. The
	// object type, reference alias, and children are structured fields on the
	// node, so unlike the SF-shaped path there are no in-band keys to skip.
	data := make(map[string]interface{}, len(node.Fields))
	for k, v := range node.Fields {
		data[k] = v
	}

	// Resolve FK if parent exists — same metadata-driven resolveFK the SF path
	// uses; the parent's freshly created id is stamped onto the child record.
	if parent != nil {
		fkField := ps.resolveFK(ctx, parent.Type, objType, parent.RelName)
		if fkField != "" {
			data[fkField] = parent.ID
		}
	}

	// Insert current record via the shared Insert mechanism.
	insertResult, err := ps.Insert(ctx, objType, data, user)
	if err != nil {
		return contract.TreeCreateResult{}, err
	}

	// Extract ID.
	var idStr string
	if val, ok := insertResult["id"].(string); ok {
		idStr = val
	} else if val, ok := insertResult["Id"].(string); ok {
		idStr = val
	}

	// Recurse children in parent-before-child order, carrying the parent id.
	for relName, childNodes := range node.Children {
		for _, childNode := range childNodes {
			if _, err := ps.insertTreeNodeIR(ctx, childNode, &parentInfo{Type: objType, ID: idStr, RelName: relName}, user); err != nil {
				return contract.TreeCreateResult{}, err // Tree API is atomic
			}
		}
	}

	return contract.TreeCreateResult{ReferenceID: node.ReferenceID, ID: idStr, Errors: []string{}}, nil
}

// groupMembershipClosureObjectNamesFromTreeNodes walks the nested TreeNode tree
// and collects every node's object type so the pessimistic group-membership
// transaction and the mutation guards see the full closure.
func groupMembershipClosureObjectNamesFromTreeNodes(nodes []contract.TreeNode) []string {
	objectNames := make([]string, 0, len(nodes))
	for _, node := range nodes {
		objectNames = appendTreeNodeObjectNames(objectNames, node)
	}
	return objectNames
}

func appendTreeNodeObjectNames(objectNames []string, node contract.TreeNode) []string {
	if node.ObjectType != "" {
		objectNames = append(objectNames, node.ObjectType)
	}
	for _, childNodes := range node.Children {
		for _, childNode := range childNodes {
			objectNames = appendTreeNodeObjectNames(objectNames, childNode)
		}
	}
	return objectNames
}
