package automation

import (
	"context"

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

// ContractFlowExecutor is the neutral FlowExecutor contract over the
// Salesforce-shaped runtime result used inside the automation engine.
type ContractFlowExecutor struct {
	executor *FlowExecutor
}

func NewContractFlowExecutor(executor *FlowExecutor) *ContractFlowExecutor {
	return &ContractFlowExecutor{executor: executor}
}

func (e *ContractFlowExecutor) ExecuteFlow(ctx context.Context, flowAPIName string, inputs map[string]any, user *models.UserSession) (*contract.FlowExecutionResult, error) {
	result, err := e.executor.ExecuteFlow(ctx, flowAPIName, inputs, user)
	return contractFlowExecutionResultFromModel(result), err
}

func (e *ContractFlowExecutor) ResumeInterview(ctx context.Context, interviewID string, user *models.UserSession) error {
	return e.executor.ResumeInterview(ctx, interviewID, user)
}

func contractFlowExecutionResultFromModel(result *models.FlowExecutionResult) *contract.FlowExecutionResult {
	if result == nil {
		return nil
	}
	return &contract.FlowExecutionResult{
		ActionName:    result.ActionName,
		IsSuccess:     result.IsSuccess,
		Errors:        contractFlowExecutionErrorsFromModel(result.Errors),
		OutputValues:  contractFlowOutputValuesFromModel(result.OutputValues),
		IsCustomError: result.IsCustomError,
		IsRetriable:   result.IsRetriable,
	}
}

func contractFlowExecutionErrorsFromModel(errors []models.FlowExecutionError) []contract.FlowExecutionError {
	if len(errors) == 0 {
		return nil
	}
	out := make([]contract.FlowExecutionError, 0, len(errors))
	for _, err := range errors {
		out = append(out, contract.FlowExecutionError{
			Code:    err.StatusCode,
			Message: err.Message,
			Fields:  append([]string(nil), err.Fields...),
		})
	}
	return out
}

func contractFlowOutputValuesFromModel(values map[string]interface{}) map[string]any {
	if len(values) == 0 {
		return nil
	}
	out := make(map[string]any, len(values))
	for key, value := range values {
		out[key] = contractFlowOutputValueFromModel(value)
	}
	return out
}

func contractFlowOutputValueFromModel(value interface{}) any {
	switch typed := value.(type) {
	case contract.Record:
		return contractFlowRecordFromModel(models.SObject(typed))
	case models.SObject:
		return contractFlowRecordFromModel(typed)
	case map[string]interface{}:
		// Only convert an untyped map to a neutral record when it carries the
		// authoritative _objectType marker. Using RecordObjectType here (its
		// attributes.type fallback) would misread a plain JSON flow-output value
		// like {"attributes":{"type":"x","source":"y"}} as a record and drop the
		// user's payload — the same class of regression fixed in the v63 projector.
		if models.IsNeutralRecordMap(typed) {
			return contractFlowRecordFromModel(models.SObject(typed))
		}
		out := make(map[string]any, len(typed))
		for key, value := range typed {
			out[key] = contractFlowOutputValueFromModel(value)
		}
		return out
	case []contract.Record:
		out := make([]contract.Record, 0, len(typed))
		for _, item := range typed {
			out = append(out, contractFlowRecordFromModel(models.SObject(item)))
		}
		return out
	case []models.SObject:
		out := make([]contract.Record, 0, len(typed))
		for _, item := range typed {
			out = append(out, contractFlowRecordFromModel(item))
		}
		return out
	case []interface{}:
		out := make([]any, 0, len(typed))
		for _, item := range typed {
			out = append(out, contractFlowOutputValueFromModel(item))
		}
		return out
	default:
		return value
	}
}

func contractFlowRecordFromModel(record models.SObject) contract.Record {
	if record == nil {
		return nil
	}
	out := make(contract.Record, len(record))
	for key, value := range record {
		if models.IsRecordMetadataKey(key) {
			continue
		}
		out[key] = contractFlowOutputValueFromModel(value)
	}
	if objectType := models.RecordObjectType(record); objectType != "" {
		out[models.ObjectTypeMetadataKey] = objectType
	}
	return out
}
