package mapping

import (
	"context"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationresultv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
)

const supportedApplicationResultSemanticMappingRevision uint32 = 1

type applicationResultSemanticMappingRevisioner interface {
	ApplicationResultSemanticMappingRevision() uint32
}

func ToWireGetApplicationResultRequest(ctx context.Context, request semantic.GetResultRequest) (*runtimev1.GetApplicationResultRequest, error) {
	if err := requireApplicationResultMapping(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateGetResultRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.GetApplicationResultRequest{
		CommandId:          request.CommandID,
		ExecutionRefDigest: request.ExecutionRefDigest,
	}, nil
}

func FromWireGetApplicationResultRequest(ctx context.Context, request *runtimev1.GetApplicationResultRequest) (semantic.GetResultRequest, error) {
	if err := requireApplicationResultMapping(); err != nil {
		return semantic.GetResultRequest{}, err
	}
	if request == nil {
		return semantic.GetResultRequest{}, invalidApplicationResultWireRequest()
	}
	result := semantic.GetResultRequest{
		CommandID:          request.GetCommandId(),
		ExecutionRefDigest: request.GetExecutionRefDigest(),
	}
	if err := semantic.ValidateGetResultRequest(ctx, result); err != nil {
		return semantic.GetResultRequest{}, err
	}
	return result, nil
}

func ToWireGetApplicationResultResponse(request semantic.GetResultRequest, response semantic.GetResultResponse) (*runtimev1.GetApplicationResultResponse, error) {
	if err := requireApplicationResultMapping(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateGetResultResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.GetApplicationResultResponse{
		CommandId:          response.CommandID,
		ExecutionRefDigest: response.ExecutionRefDigest,
		State:              applicationResultStateToWire(response.State),
		Result:             applicationResultToWire(response.Result),
	}, nil
}

func FromWireGetApplicationResultResponse(request semantic.GetResultRequest, response *runtimev1.GetApplicationResultResponse) (semantic.GetResultResponse, error) {
	if err := requireApplicationResultMapping(); err != nil {
		return semantic.GetResultResponse{}, err
	}
	if response == nil {
		return semantic.GetResultResponse{}, internalApplicationResultWireResponse()
	}
	result := semantic.GetResultResponse{
		CommandID:          response.GetCommandId(),
		ExecutionRefDigest: response.GetExecutionRefDigest(),
		State:              applicationResultStateFromWire(response.GetState()),
		Result:             applicationResultFromWire(response.GetResult()),
	}
	if err := semantic.ValidateGetResultResponse(request, result); err != nil {
		return semantic.GetResultResponse{}, err
	}
	return result, nil
}

func applicationResultToWire(result *semantic.ApplicationResult) *runtimev1.ApplicationResult {
	if result == nil {
		return nil
	}
	return &runtimev1.ApplicationResult{
		CommandId:            result.CommandID,
		ExecutionRefDigest:   result.ExecutionRefDigest,
		Outcome:              applicationResultOutcomeToWire(result.Outcome),
		ResultDigest:         result.ResultDigest,
		RuntimeCommitReceipt: result.RuntimeCommitReceipt,
		FailureCode:          applicationResultFailureCodeToWire(result.FailureCode),
	}
}

func applicationResultFromWire(result *runtimev1.ApplicationResult) *semantic.ApplicationResult {
	if result == nil {
		return nil
	}
	return &semantic.ApplicationResult{
		CommandID:            result.GetCommandId(),
		ExecutionRefDigest:   result.GetExecutionRefDigest(),
		Outcome:              applicationResultOutcomeFromWire(result.GetOutcome()),
		ResultDigest:         result.GetResultDigest(),
		RuntimeCommitReceipt: result.GetRuntimeCommitReceipt(),
		FailureCode:          applicationResultFailureCodeFromWire(result.GetFailureCode()),
	}
}

func applicationResultStateToWire(state semantic.ResultState) runtimev1.ApplicationResultState {
	switch state {
	case semantic.ResultPending:
		return runtimev1.ApplicationResultState_APPLICATION_RESULT_STATE_PENDING
	case semantic.ResultTerminal:
		return runtimev1.ApplicationResultState_APPLICATION_RESULT_STATE_TERMINAL
	default:
		return runtimev1.ApplicationResultState_APPLICATION_RESULT_STATE_UNSPECIFIED
	}
}

func applicationResultStateFromWire(state runtimev1.ApplicationResultState) semantic.ResultState {
	switch state {
	case runtimev1.ApplicationResultState_APPLICATION_RESULT_STATE_PENDING:
		return semantic.ResultPending
	case runtimev1.ApplicationResultState_APPLICATION_RESULT_STATE_TERMINAL:
		return semantic.ResultTerminal
	default:
		return ""
	}
}

func applicationResultOutcomeToWire(outcome semantic.Outcome) runtimev1.ApplicationResultOutcome {
	switch outcome {
	case semantic.OutcomeApplied:
		return runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_APPLIED
	case semantic.OutcomeNoop:
		return runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_NOOP
	case semantic.OutcomeRejected:
		return runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_REJECTED
	case semantic.OutcomeFailed:
		return runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_FAILED
	default:
		return runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_UNSPECIFIED
	}
}

func applicationResultOutcomeFromWire(outcome runtimev1.ApplicationResultOutcome) semantic.Outcome {
	switch outcome {
	case runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_APPLIED:
		return semantic.OutcomeApplied
	case runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_NOOP:
		return semantic.OutcomeNoop
	case runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_REJECTED:
		return semantic.OutcomeRejected
	case runtimev1.ApplicationResultOutcome_APPLICATION_RESULT_OUTCOME_FAILED:
		return semantic.OutcomeFailed
	default:
		return ""
	}
}

func applicationResultFailureCodeToWire(code semantic.FailureCode) runtimev1.ApplicationResultFailureCode {
	switch code {
	case semantic.FailureApplicationRejected:
		return runtimev1.ApplicationResultFailureCode_APPLICATION_RESULT_FAILURE_CODE_APPLICATION_REJECTED
	case semantic.FailureRuntimeExecutionFailed:
		return runtimev1.ApplicationResultFailureCode_APPLICATION_RESULT_FAILURE_CODE_RUNTIME_EXECUTION_FAILED
	default:
		return runtimev1.ApplicationResultFailureCode_APPLICATION_RESULT_FAILURE_CODE_UNSPECIFIED
	}
}

func applicationResultFailureCodeFromWire(code runtimev1.ApplicationResultFailureCode) semantic.FailureCode {
	switch code {
	case runtimev1.ApplicationResultFailureCode_APPLICATION_RESULT_FAILURE_CODE_UNSPECIFIED:
		return semantic.FailureNone
	case runtimev1.ApplicationResultFailureCode_APPLICATION_RESULT_FAILURE_CODE_APPLICATION_REJECTED:
		return semantic.FailureApplicationRejected
	case runtimev1.ApplicationResultFailureCode_APPLICATION_RESULT_FAILURE_CODE_RUNTIME_EXECUTION_FAILED:
		return semantic.FailureRuntimeExecutionFailed
	default:
		return semantic.FailureCode("INVALID")
	}
}

func requireApplicationResultMapping() error {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return err
	}
	return requireApplicationResultSemanticMappingRevisions(
		semantic.ExecutionRef{},
		semantic.ApplicationResult{},
		semantic.GetResultRequest{},
		semantic.GetResultResponse{},
	)
}

func requireApplicationResultSemanticMappingRevisions(values ...any) error {
	for _, value := range values {
		revisioned, ok := value.(applicationResultSemanticMappingRevisioner)
		if !ok || revisioned.ApplicationResultSemanticMappingRevision() != supportedApplicationResultSemanticMappingRevision {
			return incompatibleFailureFor(platformCapability(semantic.CapabilityName))
		}
	}
	return nil
}

func invalidApplicationResultWireRequest() error {
	return &commonsemantic.Failure{
		Category:         commonsemantic.FailureInvalidRequest,
		InvalidComponent: commonsemantic.InvalidComponentCommandData,
	}
}

func internalApplicationResultWireResponse() error {
	return &commonsemantic.Failure{Category: commonsemantic.FailureInternal}
}
