package runtimeworker

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/nexuscrm/automation"
	"github.com/nexuscrm/persistence"
	contractmodels "github.com/pingcap-inc/nexuscrm-contract/pkg/models"
	applicationresultsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationresultv1"
	flowcommandsemanticv3 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowcommandv3"
)

// runtimeFlowCommandApplicationExecutor is the V3-only Flow adapter. It
// returns a typed ApplicationResult completion only when the Flow engine has
// supplied a complete terminal application disposition. Every other condition
// remains an ordinary callback error so the transaction owner cannot mint a
// terminal result from incomplete evidence.
type runtimeFlowCommandApplicationExecutor struct {
	flows runtimeFlowCommandEngine
	users runtimeFlowCommandSystemUserLoader
}

type runtimeFlowCommandApplicationFailureBody struct {
	ExecutionID   string                                    `json:"executionId"`
	CauseCode     string                                    `json:"causeCode"`
	CauseCategory contractmodels.FlowExecutionCauseCategory `json:"causeCategory"`
}

func (e *runtimeFlowCommandApplicationExecutor) ExecutePlatformApplicationCommand(
	ctx context.Context,
	command PlatformCommandExecution,
) (persistence.RuntimePlatformCommandApplicationCompletion, error) {
	if command.PayloadFormatVersion != flowcommandsemanticv3.PayloadFormatVersion {
		return persistence.RuntimePlatformCommandApplicationCompletion{}, permanentPlatformCommandError(
			errors.New("runtime Flow application command executor is not configured for the command"),
		)
	}
	payload, err := flowcommandsemanticv3.Decode(command.Payload)
	if err != nil {
		return persistence.RuntimePlatformCommandApplicationCompletion{}, permanentPlatformCommandError(err)
	}
	// OutboundCommand does not yet own V3 external effects. Refuse the allow
	// mode before selecting or invoking a Flow; suppression is explicit and is
	// already bound into ExecutionRef.InputDigest by the V3 codec.
	if payload.ExternalEffectsMode != flowcommandsemanticv3.SuppressExternalEffects {
		return persistence.RuntimePlatformCommandApplicationCompletion{}, permanentPlatformCommandError(
			errors.New("runtime Flow application command external effects are not enabled"),
		)
	}
	inputs, err := decodeRuntimeFlowCommandInputs(payload.CanonicalInputs)
	if err != nil {
		return persistence.RuntimePlatformCommandApplicationCompletion{}, permanentPlatformCommandError(err)
	}
	return executeRuntimeFlowApplicationCommand(ctx, command, e.flows, e.users, payload, inputs)
}

func executeRuntimeFlowApplicationCommand(
	ctx context.Context,
	command PlatformCommandExecution,
	flows runtimeFlowCommandEngine,
	users runtimeFlowCommandSystemUserLoader,
	payload flowcommandsemanticv3.Payload,
	inputs map[string]interface{},
) (persistence.RuntimePlatformCommandApplicationCompletion, error) {
	empty := persistence.RuntimePlatformCommandApplicationCompletion{}
	definitionRef := payload.ExecutionRef.DefinitionRef
	definitionVersion := payload.ExecutionRef.DefinitionVersion
	outcome, _, executeErr := invokeRuntimeFlowCommand(
		ctx,
		command,
		flows,
		users,
		definitionRef,
		definitionVersion,
		inputs,
		false,
	)
	var recoveryRequested *automation.ConnectorInboundRemoteIdentityRecoveryRequestedError
	if errors.As(executeErr, &recoveryRequested) {
		return empty, &PlatformCommandRecoveryError{
			Request: automation.ConnectorInboundRemoteIdentityRecoveryRequest{
				Selector: recoveryRequested.Selector,
			},
		}
	}
	if outcome != nil && outcome.Dependency != nil {
		return empty, runtimeFlowApplicationRetryError(
			"runtime Flow application command returned an unsupported deferred dependency",
		)
	}
	// The Flow engine returns terminal failed outcomes together with their
	// execution error. Classify that typed evidence before the error, matching
	// the retained V1/V2 contract; otherwise a proved rejection/failure would be
	// retried as an ordinary callback error and could end poisoned without its
	// ApplicationResult.
	if outcome != nil && outcome.Result != nil &&
		(!outcome.Result.IsSuccess || outcome.Result.IsRetriable) {
		return mapRuntimeFlowApplicationCompletion(outcome)
	}
	if executeErr != nil {
		return empty, executeErr
	}
	return mapRuntimeFlowApplicationCompletion(outcome)
}

func mapRuntimeFlowApplicationCompletion(
	outcome *contractmodels.FlowExecutionTypedOutcome,
) (persistence.RuntimePlatformCommandApplicationCompletion, error) {
	empty := persistence.RuntimePlatformCommandApplicationCompletion{}
	if outcome == nil || outcome.Result == nil {
		return empty, permanentPlatformCommandError(
			errors.New("runtime Flow application command returned no execution outcome"),
		)
	}
	executionID := strings.TrimSpace(outcome.ExecutionID)
	if executionID == "" || executionID != outcome.ExecutionID {
		return empty, permanentPlatformCommandError(
			errors.New("runtime Flow application command returned no canonical Flow execution identity"),
		)
	}
	if outcome.Result.IsRetriable || (outcome.Cause != nil && outcome.Cause.Retriable) {
		return empty, runtimeFlowApplicationRetryError(
			"runtime Flow application command returned a retriable execution outcome",
		)
	}
	if outcome.Result.IsSuccess {
		if outcome.Cause != nil || outcome.Dependency != nil || outcome.Result.IsCustomError ||
			len(outcome.Result.Errors) != 0 {
			return empty, permanentPlatformCommandError(
				errors.New("runtime Flow application command returned a malformed successful outcome"),
			)
		}
		outputs, err := canonicalRuntimeFlowCommandJSON(outcome.Result.OutputValues)
		if err != nil {
			return empty, permanentPlatformCommandError(
				fmt.Errorf("runtime Flow application command output is not canonical JSON: %w", err),
			)
		}
		body, err := canonicalRuntimeFlowCommandJSON(runtimeFlowCommandResult{
			ExecutionID:      executionID,
			CanonicalOutputs: outputs,
		})
		if err != nil {
			return empty, permanentPlatformCommandError(
				fmt.Errorf("runtime Flow application command result is not canonical JSON: %w", err),
			)
		}
		return persistence.RuntimePlatformCommandApplicationCompletion{
			Outcome:     applicationresultsemantic.OutcomeApplied,
			FailureCode: applicationresultsemantic.FailureNone,
			Body:        json.RawMessage(body),
		}, nil
	}
	if outcome.Cause == nil {
		return empty, permanentPlatformCommandError(
			errors.New("runtime Flow application command returned a failed result without a typed cause"),
		)
	}
	causeCode := strings.TrimSpace(outcome.Cause.Code)
	if causeCode == "" || causeCode != outcome.Cause.Code {
		return empty, permanentPlatformCommandError(
			errors.New("runtime Flow application command returned a failed result without a canonical cause code"),
		)
	}
	if outcome.Cause.Category == contractmodels.FlowExecutionCauseCategoryConcurrency {
		return empty, runtimeFlowApplicationRetryError(
			"runtime Flow application command returned a concurrent execution outcome",
		)
	}
	if !runtimeFlowApplicationCauseCategoryKnown(outcome.Cause.Category) {
		return empty, permanentPlatformCommandError(
			errors.New("runtime Flow application command returned an unknown cause category"),
		)
	}
	body, err := canonicalRuntimeFlowCommandJSON(runtimeFlowCommandApplicationFailureBody{
		ExecutionID:   executionID,
		CauseCode:     causeCode,
		CauseCategory: outcome.Cause.Category,
	})
	if err != nil {
		return empty, permanentPlatformCommandError(
			fmt.Errorf("runtime Flow application command failure body is not canonical JSON: %w", err),
		)
	}
	completion := persistence.RuntimePlatformCommandApplicationCompletion{
		Outcome:     applicationresultsemantic.OutcomeFailed,
		FailureCode: applicationresultsemantic.FailureRuntimeExecutionFailed,
		Body:        json.RawMessage(body),
	}
	if outcome.Result.IsCustomError || runtimeFlowApplicationCauseIsRejection(outcome.Cause.Category) {
		completion.Outcome = applicationresultsemantic.OutcomeRejected
		completion.FailureCode = applicationresultsemantic.FailureApplicationRejected
	}
	return completion, nil
}

func runtimeFlowApplicationCauseIsRejection(category contractmodels.FlowExecutionCauseCategory) bool {
	switch category {
	case contractmodels.FlowExecutionCauseCategoryValidation,
		contractmodels.FlowExecutionCauseCategoryAuthorization,
		contractmodels.FlowExecutionCauseCategoryNotFound:
		return true
	default:
		return false
	}
}

func runtimeFlowApplicationCauseCategoryKnown(category contractmodels.FlowExecutionCauseCategory) bool {
	switch category {
	case contractmodels.FlowExecutionCauseCategoryValidation,
		contractmodels.FlowExecutionCauseCategoryConfiguration,
		contractmodels.FlowExecutionCauseCategoryAuthorization,
		contractmodels.FlowExecutionCauseCategoryNotFound,
		contractmodels.FlowExecutionCauseCategoryPersistence,
		contractmodels.FlowExecutionCauseCategoryExternal,
		contractmodels.FlowExecutionCauseCategoryInternal,
		contractmodels.FlowExecutionCauseCategoryUnknown:
		return true
	default:
		return false
	}
}

func runtimeFlowApplicationRetryError(message string) error {
	return &PlatformCommandExecutionError{
		Cause:     errors.New(message),
		Permanent: false,
	}
}

var _ platformCommandApplicationExecutor = (*runtimeFlowCommandApplicationExecutor)(nil)
