package records

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/kernel/pkg/validation"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type DefaultChildRowsSchemaSnapshot interface {
	GetSchema(ctx context.Context, apiName string) *models.ObjectMetadata
}

type DefaultChildRowContract struct {
	TargetObject      string
	TargetLookupField string
	InsertValues      models.SObject
}

func defaultChildRowsInsertContext(ctx context.Context) context.Context {
	return constants.WithSecurityContext(ctx, constants.SystemContext, "default child rows")
}

func ResolveDefaultChildRowsContracts(
	ctx context.Context,
	snapshot DefaultChildRowsSchemaSnapshot,
	parent *models.ObjectMetadata,
	rows []models.DefaultChildRow,
	engineOwnedByObject func(string) map[string]bool,
) ([]DefaultChildRowContract, error) {
	var contextual func(context.Context, string) (map[string]bool, error)
	if engineOwnedByObject != nil {
		contextual = func(_ context.Context, objectName string) (map[string]bool, error) {
			return engineOwnedByObject(objectName), nil
		}
	}
	return ResolveDefaultChildRowsContractsContext(ctx, snapshot, parent, rows, contextual)
}

// ResolveDefaultChildRowsContractsContext is the request-bound form used by
// independent Runtime. The established function above remains source
// compatible for released controlplane consumers; new callers get explicit
// context propagation and fail-closed metadata errors here.
func ResolveDefaultChildRowsContractsContext(
	ctx context.Context,
	snapshot DefaultChildRowsSchemaSnapshot,
	parent *models.ObjectMetadata,
	rows []models.DefaultChildRow,
	engineOwnedByObject func(context.Context, string) (map[string]bool, error),
) ([]DefaultChildRowContract, error) {
	if parent == nil || len(rows) == 0 {
		return nil, nil
	}

	var errs []string
	errs = append(errs, validateDefaultChildRowsCycles(ctx, snapshot, parent)...)

	contracts := make([]DefaultChildRowContract, 0, len(rows))
	schemaValidator := validation.NewSchemaValidator()
	for idx, row := range rows {
		contract, rowErrs := resolveDefaultChildRowContract(ctx, snapshot, parent, idx, row, engineOwnedByObject, schemaValidator)
		if len(rowErrs) > 0 {
			errs = append(errs, rowErrs...)
			continue
		}
		contracts = append(contracts, contract)
	}
	if len(errs) > 0 {
		return nil, fmt.Errorf("DefaultChildRows resolution failed:\n  - %s", strings.Join(errs, "\n  - "))
	}
	return contracts, nil
}

func resolveDefaultChildRowContract(
	ctx context.Context,
	snapshot DefaultChildRowsSchemaSnapshot,
	parent *models.ObjectMetadata,
	idx int,
	row models.DefaultChildRow,
	engineOwnedByObject func(context.Context, string) (map[string]bool, error),
	schemaValidator *validation.SchemaValidator,
) (DefaultChildRowContract, []string) {
	prefix := fmt.Sprintf("%s.DefaultChildRows[%d]", parent.APIName, idx)
	var errs []string

	if row.TargetObject == "" {
		return DefaultChildRowContract{}, []string{fmt.Sprintf("%s: targetObject is required", prefix)}
	}
	targetSchema := snapshot.GetSchema(ctx, row.TargetObject)
	if targetSchema == nil {
		return DefaultChildRowContract{}, []string{fmt.Sprintf("%s: targetObject %q not found in metadata", prefix, row.TargetObject)}
	}
	if row.TargetLookupField == "" {
		return DefaultChildRowContract{}, []string{fmt.Sprintf("%s: targetLookupField is required", prefix)}
	}

	targetFields := indexSchemaFields(targetSchema)
	var engineOwned map[string]bool
	if engineOwnedByObject != nil {
		var err error
		engineOwned, err = engineOwnedByObject(ctx, targetSchema.APIName)
		if err != nil {
			return DefaultChildRowContract{}, []string{fmt.Sprintf("%s: resolve engine-owned fields for %q: %v", prefix, targetSchema.APIName, err)}
		}
	}
	lookupField, ok := targetFields[strings.ToLower(row.TargetLookupField)]
	if !ok || lookupField == nil {
		return DefaultChildRowContract{}, []string{fmt.Sprintf("%s: targetLookupField %q does not exist on targetObject %q", prefix, row.TargetLookupField, row.TargetObject)}
	}
	if !constants.IsReferenceType(constants.NormalizeFieldType(string(lookupField.Type))) {
		errs = append(errs, fmt.Sprintf("%s: targetLookupField %q on %s is %q, want relationship type", prefix, row.TargetLookupField, row.TargetObject, lookupField.Type))
	}
	if len(lookupField.ReferenceTo) == 0 {
		errs = append(errs, fmt.Sprintf("%s: targetLookupField %q on %s has empty referenceTo", prefix, row.TargetLookupField, row.TargetObject))
	} else if !containsIgnoreCase(lookupField.ReferenceTo, parent.APIName) {
		errs = append(errs, fmt.Sprintf("%s: targetLookupField %q on %s does not reference parent object %q (referenceTo=%v)", prefix, row.TargetLookupField, row.TargetObject, parent.APIName, lookupField.ReferenceTo))
	}
	for _, msg := range validateDefaultChildTargetField(prefix+".targetLookupField", lookupField, lookupField.APIName, lookupField.APIName, engineOwned) {
		errs = append(errs, msg)
	}

	payload := make(models.SObject, len(row.FieldValues)+1)
	for authoredField, value := range row.FieldValues {
		if strings.EqualFold(authoredField, row.TargetLookupField) {
			errs = append(errs, fmt.Sprintf("%s.fieldValues[%q]: duplicates targetLookupField with different casing", prefix, authoredField))
			continue
		}
		if strings.Contains(authoredField, ".") {
			errs = append(errs, fmt.Sprintf("%s.fieldValues: target key %q contains a dotted path — flat field names only", prefix, authoredField))
			continue
		}
		field := targetFields[strings.ToLower(authoredField)]
		if field == nil {
			errs = append(errs, fmt.Sprintf("%s.fieldValues[%q]: target field does not exist on %q", prefix, authoredField, row.TargetObject))
			continue
		}
		for _, msg := range validateDefaultChildTargetField(prefix+fmt.Sprintf(".fieldValues[%q]", authoredField), field, field.APIName, lookupField.APIName, engineOwned) {
			errs = append(errs, msg)
		}
		payload[field.APIName] = value
	}

	if len(errs) > 0 {
		return DefaultChildRowContract{}, errs
	}

	payload[lookupField.APIName] = defaultChildRowsValidationParentID(parent.APIName)
	payload = applyFieldDefaults(payload, targetSchema)
	if err := schemaValidator.ValidateSchemaCompliance(payload, targetSchema, false, false, false); err != nil {
		errs = append(errs, fmt.Sprintf("%s: %v", prefix, err))
	}
	for _, msg := range validateDefaultChildRequiredFields(prefix, payload, targetSchema, lookupField.APIName, engineOwned) {
		errs = append(errs, msg)
	}
	if len(errs) > 0 {
		return DefaultChildRowContract{}, errs
	}

	return DefaultChildRowContract{
		TargetObject:      targetSchema.APIName,
		TargetLookupField: lookupField.APIName,
		InsertValues:      payload,
	}, nil
}

func validateDefaultChildRowsCycles(ctx context.Context, snapshot DefaultChildRowsSchemaSnapshot, root *models.ObjectMetadata) []string {
	if root == nil {
		return nil
	}
	var errs []string
	reported := map[string]struct{}{}
	var walk func(schema *models.ObjectMetadata, path []string, onStack map[string]int)
	walk = func(schema *models.ObjectMetadata, path []string, onStack map[string]int) {
		if schema == nil {
			return
		}
		apiName := strings.TrimSpace(schema.APIName)
		if apiName == "" {
			return
		}
		key := strings.ToLower(apiName)
		if pos, ok := onStack[key]; ok {
			cycle := append(append([]string(nil), path[pos:]...), apiName)
			cycleKey := strings.Join(cycle, "->")
			if _, seen := reported[cycleKey]; seen {
				return
			}
			reported[cycleKey] = struct{}{}
			errs = append(errs, fmt.Sprintf("DefaultChildRows recursive cycle detected: %s", strings.Join(cycle, " -> ")))
			return
		}

		nextPath := append(path, apiName)
		nextStack := make(map[string]int, len(onStack)+1)
		for existingKey, existingPos := range onStack {
			nextStack[existingKey] = existingPos
		}
		nextStack[key] = len(path)
		for _, row := range schema.DefaultChildRows {
			targetName := strings.TrimSpace(row.TargetObject)
			if targetName == "" {
				continue
			}
			walk(snapshot.GetSchema(ctx, targetName), nextPath, nextStack)
		}
	}

	walk(root, nil, map[string]int{})
	return errs
}

func validateDefaultChildTargetField(
	prefix string,
	field *models.FieldMetadata,
	authoredField string,
	targetLookupField string,
	engineOwned map[string]bool,
) []string {
	if field == nil {
		return nil
	}
	if strings.EqualFold(authoredField, constants.FieldID) || strings.EqualFold(authoredField, constants.FieldOwnerID) {
		return []string{fmt.Sprintf("%s: target %q is on the default-child-row blacklist (Id / OwnerId)", prefix, authoredField)}
	}
	if field.IsSystem {
		return []string{fmt.Sprintf("%s: target %q is IsSystem", prefix, field.APIName)}
	}
	if field.Formula != nil || field.Type == constants.FieldTypeFormula {
		return []string{fmt.Sprintf("%s: target %q is a formula field", prefix, field.APIName)}
	}
	if !field.Createable {
		return []string{fmt.Sprintf("%s: target %q is !Createable", prefix, field.APIName)}
	}
	if engineOwned != nil && engineOwned[strings.ToLower(field.APIName)] {
		return []string{fmt.Sprintf("%s: target %q is engine-owned", prefix, field.APIName)}
	}
	if strings.EqualFold(field.APIName, targetLookupField) && !strings.EqualFold(authoredField, targetLookupField) {
		return []string{fmt.Sprintf("%s: target %q collides with targetLookupField", prefix, authoredField)}
	}
	return nil
}

func validateDefaultChildRequiredFields(
	prefix string,
	payload models.SObject,
	targetSchema *models.ObjectMetadata,
	targetLookupField string,
	engineOwned map[string]bool,
) []string {
	var errs []string
	for i := range targetSchema.Fields {
		field := &targetSchema.Fields[i]
		if !field.Required {
			continue
		}
		if value, ok := payload[field.APIName]; ok && !isBlankValue(value) {
			continue
		}
		if strings.EqualFold(field.APIName, targetLookupField) {
			continue
		}
		if field.DefaultValue != nil {
			continue
		}
		if field.IsSystem {
			continue
		}
		if constants.NormalizeFieldType(string(field.Type)) == constants.FieldTypeAutonumber {
			continue
		}
		if engineOwned != nil && engineOwned[strings.ToLower(field.APIName)] {
			continue
		}
		errs = append(errs, fmt.Sprintf("%s: required target field %q is not satisfied by fieldValues, targetLookupField, metadata defaults, or generated fields", prefix, field.APIName))
	}
	return errs
}

func defaultChildRowsValidationParentID(parentAPIName string) string {
	return "__default_child_rows_parent__:" + parentAPIName
}

func containsIgnoreCase(values []string, want string) bool {
	for _, value := range values {
		if strings.EqualFold(value, want) {
			return true
		}
	}
	return false
}

// applyDefaultChildRows is the single runtime reader for metadata-declared
// constant child-row insert side effects. The containing parent object's
// schema owns the declarations; runtime stamps the new parent Id onto each
// child payload and routes the write back through PersistenceService.Insert so
// validators, events, and nested metadata hooks stay consistent.
func (ps *PersistenceService) applyDefaultChildRows(
	ctx context.Context,
	schema *models.ObjectMetadata,
	newRecord models.SObject,
	currentUser *models.UserSession,
) error {
	if schema == nil || len(schema.DefaultChildRows) == 0 {
		return nil
	}

	newRecordID, _ := newRecord[constants.FieldID].(string)
	if newRecordID == "" {
		return nil
	}

	contracts, err := ResolveDefaultChildRowsContractsContext(ctx, ps.metadata, schema, schema.DefaultChildRows, ps.engineOwnedFieldSet)
	if err != nil {
		return err
	}
	for _, contract := range contracts {
		child := make(models.SObject, len(contract.InsertValues))
		for key, value := range contract.InsertValues {
			child[key] = value
		}
		child[contract.TargetLookupField] = newRecordID
		if _, err := ps.Insert(defaultChildRowsInsertContext(ctx), contract.TargetObject, child, currentUser); err != nil {
			return err
		}
	}
	return nil
}
