package mapping

import (
	"context"

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

func ToWireClaimExternalEffectsRequest(ctx context.Context, request semantic.ClaimRequest) (*runtimev1.ClaimExternalEffectsRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateClaimRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ClaimExternalEffectsRequest{
		WorkerId: request.WorkerID, Limit: request.Limit, MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

func FromWireClaimExternalEffectsRequest(ctx context.Context, request *runtimev1.ClaimExternalEffectsRequest) (semantic.ClaimRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ClaimRequest{}, err
	}
	if request == nil {
		return semantic.ClaimRequest{}, invalidPlatformWire()
	}
	result := semantic.ClaimRequest{
		WorkerID: request.GetWorkerId(), Limit: request.GetLimit(), MinCatalogRevision: request.MinCatalogRevision,
	}
	if err := semantic.ValidateClaimRequest(ctx, result); err != nil {
		return semantic.ClaimRequest{}, err
	}
	return result, nil
}

func ToWireClaimExternalEffectsResponse(request semantic.ClaimRequest, response semantic.ClaimResponse) (*runtimev1.ClaimExternalEffectsResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateClaimResponseFor(request, response); err != nil {
		return nil, err
	}
	result := &runtimev1.ClaimExternalEffectsResponse{
		Effects:  make([]*runtimev1.ExternalEffectLease, 0, len(response.Effects)),
		Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName)),
	}
	for _, effect := range response.Effects {
		result.Effects = append(result.Effects, toWireExternalEffect(effect))
	}
	return result, nil
}

func FromWireClaimExternalEffectsResponse(request semantic.ClaimRequest, response *runtimev1.ClaimExternalEffectsResponse) (semantic.ClaimResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ClaimResponse{}, err
	}
	if response == nil || response.GetReceipts() == nil {
		return semantic.ClaimResponse{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.ClaimResponse{}, err
	}
	result := semantic.ClaimResponse{Effects: make([]semantic.Effect, 0, len(response.GetEffects())), Receipts: receipts}
	for _, effect := range response.GetEffects() {
		converted, ok := fromWireExternalEffect(effect)
		if !ok {
			return semantic.ClaimResponse{}, internalPlatformWire()
		}
		result.Effects = append(result.Effects, converted)
	}
	if err := semantic.ValidateClaimResponseFor(request, result); err != nil {
		return semantic.ClaimResponse{}, err
	}
	return result, nil
}

func ToWireCompleteExternalEffectRequest(ctx context.Context, request semantic.CompleteRequest) (*runtimev1.CompleteExternalEffectRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCompleteRequest(ctx, request); err != nil {
		return nil, err
	}
	result := &runtimev1.CompleteExternalEffectRequest{
		EffectId: request.EffectID, ClaimToken: request.ClaimToken,
		Results:         make([]*runtimev1.ExternalEffectOperationResult, 0, len(request.Results)),
		CatalogRevision: request.CatalogRevision,
	}
	for _, operation := range request.Results {
		outcome, ok := toWireExternalEffectOutcome(operation.Outcome)
		if !ok {
			return nil, invalidPlatformWire()
		}
		result.Results = append(result.Results, &runtimev1.ExternalEffectOperationResult{
			OperationId: operation.OperationID, RequestDigest: operation.RequestDigest,
			Outcome: outcome, StatusCode: operation.StatusCode,
			Body: append([]byte(nil), operation.Body...), FailureCode: operation.FailureCode,
		})
	}
	return result, nil
}

func FromWireCompleteExternalEffectRequest(ctx context.Context, request *runtimev1.CompleteExternalEffectRequest) (semantic.CompleteRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CompleteRequest{}, err
	}
	if request == nil {
		return semantic.CompleteRequest{}, invalidPlatformWire()
	}
	result := semantic.CompleteRequest{
		EffectID: request.GetEffectId(), ClaimToken: request.GetClaimToken(),
		Results: make([]semantic.OperationResult, 0, len(request.GetResults())), CatalogRevision: request.GetCatalogRevision(),
	}
	for _, operation := range request.GetResults() {
		if operation == nil {
			return semantic.CompleteRequest{}, invalidPlatformWire()
		}
		outcome, ok := fromWireExternalEffectOutcome(operation.GetOutcome())
		if !ok {
			return semantic.CompleteRequest{}, invalidPlatformWire()
		}
		result.Results = append(result.Results, semantic.OperationResult{
			OperationID: operation.GetOperationId(), RequestDigest: operation.GetRequestDigest(), Outcome: outcome,
			StatusCode: operation.GetStatusCode(), Body: append([]byte(nil), operation.GetBody()...),
			FailureCode: operation.GetFailureCode(),
		})
	}
	if err := semantic.ValidateCompleteRequest(ctx, result); err != nil {
		return semantic.CompleteRequest{}, err
	}
	return result, nil
}

func ToWireCompleteExternalEffectResponse(request semantic.CompleteRequest, response semantic.CompleteResponse) (*runtimev1.CompleteExternalEffectResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCompleteResponse(request, response); err != nil {
		return nil, err
	}
	state, ok := toWireExternalEffectCompletionState(response.State)
	if !ok {
		return nil, internalPlatformWire()
	}
	return &runtimev1.CompleteExternalEffectResponse{
		EffectId: response.EffectID, State: state,
		Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName)),
	}, nil
}

func FromWireCompleteExternalEffectResponse(request semantic.CompleteRequest, response *runtimev1.CompleteExternalEffectResponse) (semantic.CompleteResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CompleteResponse{}, err
	}
	if response == nil || response.GetReceipts() == nil {
		return semantic.CompleteResponse{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	state, ok := fromWireExternalEffectCompletionState(response.GetState())
	if !ok {
		return semantic.CompleteResponse{}, internalPlatformWire()
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.CompleteResponse{}, err
	}
	result := semantic.CompleteResponse{EffectID: response.GetEffectId(), State: state, Receipts: receipts}
	if err := semantic.ValidateCompleteResponse(request, result); err != nil {
		return semantic.CompleteResponse{}, err
	}
	return result, nil
}

func toWireExternalEffect(effect semantic.Effect) *runtimev1.ExternalEffectLease {
	result := &runtimev1.ExternalEffectLease{
		EffectId: effect.EffectID, ClaimToken: effect.ClaimToken,
		LeaseExpiresAtUnixMillis: effect.LeaseExpiresAtUnixMillis,
		Operations:               make([]*runtimev1.ExternalEffectOperation, 0, len(effect.Operations)),
	}
	for _, operation := range effect.Operations {
		result.Operations = append(result.Operations, &runtimev1.ExternalEffectOperation{
			OperationId: operation.OperationID, RequestDigest: operation.RequestDigest,
			Method: operation.Method, RelativePath: operation.RelativePath,
			Query: toWireExternalEffectQuery(operation.Query), Headers: toWireExternalEffectHeaders(operation.Headers),
			Body: append([]byte(nil), operation.Body...), CredentialRef: operation.CredentialRef,
			EndpointClass: operation.EndpointClass, RetryPolicy: toWireExternalEffectRetryPolicy(operation.RetryPolicy),
		})
	}
	return result
}

func fromWireExternalEffect(effect *runtimev1.ExternalEffectLease) (semantic.Effect, bool) {
	if effect == nil {
		return semantic.Effect{}, false
	}
	result := semantic.Effect{
		EffectID: effect.GetEffectId(), ClaimToken: effect.GetClaimToken(),
		LeaseExpiresAtUnixMillis: effect.GetLeaseExpiresAtUnixMillis(),
		Operations:               make([]semantic.Operation, 0, len(effect.GetOperations())),
	}
	for _, operation := range effect.GetOperations() {
		if operation == nil || operation.GetRetryPolicy() == nil {
			return semantic.Effect{}, false
		}
		query, queryOK := fromWireExternalEffectQuery(operation.GetQuery())
		headers, headersOK := fromWireExternalEffectHeaders(operation.GetHeaders())
		if !queryOK || !headersOK {
			return semantic.Effect{}, false
		}
		result.Operations = append(result.Operations, semantic.Operation{
			OperationID: operation.GetOperationId(), RequestDigest: operation.GetRequestDigest(), Method: operation.GetMethod(),
			RelativePath: operation.GetRelativePath(), Query: query, Headers: headers, Body: append([]byte(nil), operation.GetBody()...),
			CredentialRef: operation.GetCredentialRef(), EndpointClass: operation.GetEndpointClass(),
			RetryPolicy: fromWireExternalEffectRetryPolicy(operation.GetRetryPolicy()),
		})
	}
	return result, true
}

func toWireExternalEffectHeaders(values []semantic.Header) []*runtimev1.ExternalEffectHeader {
	result := make([]*runtimev1.ExternalEffectHeader, 0, len(values))
	for _, value := range values {
		result = append(result, &runtimev1.ExternalEffectHeader{Name: value.Name, Values: append([]string(nil), value.Values...)})
	}
	return result
}

func fromWireExternalEffectHeaders(values []*runtimev1.ExternalEffectHeader) ([]semantic.Header, bool) {
	result := make([]semantic.Header, 0, len(values))
	for _, value := range values {
		if value == nil {
			return nil, false
		}
		result = append(result, semantic.Header{Name: value.GetName(), Values: append([]string(nil), value.GetValues()...)})
	}
	return result, true
}

func toWireExternalEffectQuery(values []semantic.QueryParameter) []*runtimev1.ExternalEffectQueryParameter {
	result := make([]*runtimev1.ExternalEffectQueryParameter, 0, len(values))
	for _, value := range values {
		result = append(result, &runtimev1.ExternalEffectQueryParameter{Name: value.Name, Value: value.Value})
	}
	return result
}

func fromWireExternalEffectQuery(values []*runtimev1.ExternalEffectQueryParameter) ([]semantic.QueryParameter, bool) {
	result := make([]semantic.QueryParameter, 0, len(values))
	for _, value := range values {
		if value == nil {
			return nil, false
		}
		result = append(result, semantic.QueryParameter{Name: value.GetName(), Value: value.GetValue()})
	}
	return result, true
}

func toWireExternalEffectRetryPolicy(value semantic.RetryPolicy) *runtimev1.ExternalEffectRetryPolicy {
	return &runtimev1.ExternalEffectRetryPolicy{
		MaxAttempts: value.MaxAttempts, BaseDelayMillis: value.BaseDelayMillis,
		MultiplierMilli: value.MultiplierMilli, MaxDelayMillis: value.MaxDelayMillis,
		RetryableStatusCodes: append([]uint32(nil), value.RetryableStatusCodes...),
	}
}

func fromWireExternalEffectRetryPolicy(value *runtimev1.ExternalEffectRetryPolicy) semantic.RetryPolicy {
	return semantic.RetryPolicy{
		MaxAttempts: value.GetMaxAttempts(), BaseDelayMillis: value.GetBaseDelayMillis(),
		MultiplierMilli: value.GetMultiplierMilli(), MaxDelayMillis: value.GetMaxDelayMillis(),
		RetryableStatusCodes: append([]uint32(nil), value.GetRetryableStatusCodes()...),
	}
}

func toWireExternalEffectOutcome(value semantic.OperationOutcome) (runtimev1.ExternalEffectOperationOutcome, bool) {
	switch value {
	case semantic.OutcomeResponseReceived:
		return runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_RESPONSE_RECEIVED, true
	case semantic.OutcomeNoResponse:
		return runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_NO_RESPONSE, true
	case semantic.OutcomeNotDispatched:
		return runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_NOT_DISPATCHED, true
	case semantic.OutcomeSkipped:
		return runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_SKIPPED, true
	default:
		return runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_UNSPECIFIED, false
	}
}

func fromWireExternalEffectOutcome(value runtimev1.ExternalEffectOperationOutcome) (semantic.OperationOutcome, bool) {
	switch value {
	case runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_RESPONSE_RECEIVED:
		return semantic.OutcomeResponseReceived, true
	case runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_NO_RESPONSE:
		return semantic.OutcomeNoResponse, true
	case runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_NOT_DISPATCHED:
		return semantic.OutcomeNotDispatched, true
	case runtimev1.ExternalEffectOperationOutcome_EXTERNAL_EFFECT_OPERATION_OUTCOME_SKIPPED:
		return semantic.OutcomeSkipped, true
	default:
		return "", false
	}
}

func toWireExternalEffectCompletionState(value semantic.CompletionState) (runtimev1.ExternalEffectCompletionState, bool) {
	switch value {
	case semantic.CompletionFinalized:
		return runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_FINALIZED, true
	case semantic.CompletionRetrying:
		return runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_RETRYING, true
	case semantic.CompletionAmbiguous:
		return runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_AMBIGUOUS, true
	case semantic.CompletionFailed:
		return runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_FAILED, true
	default:
		return runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_UNSPECIFIED, false
	}
}

func fromWireExternalEffectCompletionState(value runtimev1.ExternalEffectCompletionState) (semantic.CompletionState, bool) {
	switch value {
	case runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_FINALIZED:
		return semantic.CompletionFinalized, true
	case runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_RETRYING:
		return semantic.CompletionRetrying, true
	case runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_AMBIGUOUS:
		return semantic.CompletionAmbiguous, true
	case runtimev1.ExternalEffectCompletionState_EXTERNAL_EFFECT_COMPLETION_STATE_FAILED:
		return semantic.CompletionFailed, true
	default:
		return "", false
	}
}
