package orchestrator

import (
	"strings"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
)

const (
	executionStepMetadataRole   = "role"
	executionStepMetadataStatus = "status"

	executionStepRoleSource   = "source"
	executionStepRoleConsumer = "consumer"
)

func setExecutionStepRole(step *model.ExecutionStep, role string) {
	role = strings.ToLower(strings.TrimSpace(role))
	setExecutionStepMetadata(step, executionStepMetadataRole, role)
}

func setExecutionStepStatus(step *model.ExecutionStep, state model.OperationState) {
	step.State = state
	setExecutionStepMetadata(step, executionStepMetadataStatus, string(state))
}

func executionStepRole(step *model.ExecutionStep) string {
	if step == nil {
		return ""
	}
	if role := strings.TrimSpace(step.Metadata[executionStepMetadataRole]); role != "" {
		return strings.ToLower(role)
	}
	if strings.EqualFold(step.Code, stepCodeCardPayout) {
		return executionStepRoleConsumer
	}
	return executionStepRoleSource
}

func isSourceExecutionStep(step *model.ExecutionStep) bool {
	return executionStepRole(step) == executionStepRoleSource
}

func sourceStepsConfirmed(plan *model.ExecutionPlan) bool {
	if plan == nil || len(plan.Steps) == 0 {
		return false
	}
	hasSource := false
	for _, step := range plan.Steps {
		if step == nil || !isSourceExecutionStep(step) {
			continue
		}
		if step.State == model.OperationStateSkipped {
			continue
		}
		hasSource = true
		if step.State != model.OperationStateSuccess {
			return false
		}
	}
	return hasSource
}

func findExecutionStepByTransferRef(plan *model.ExecutionPlan, transferRef string) *model.ExecutionStep {
	if plan == nil {
		return nil
	}
	transferRef = strings.TrimSpace(transferRef)
	if transferRef == "" {
		return nil
	}
	for _, step := range plan.Steps {
		if step == nil {
			continue
		}
		if strings.EqualFold(strings.TrimSpace(step.TransferRef), transferRef) {
			return step
		}
	}
	return nil
}

func updateExecutionStepFromTransfer(plan *model.ExecutionPlan, event *chainv1.TransferStatusChangedEvent) *model.ExecutionStep {
	if plan == nil || event == nil || event.GetTransfer() == nil {
		return nil
	}
	transfer := event.GetTransfer()
	transferRef := strings.TrimSpace(transfer.GetTransferRef())
	if transferRef == "" {
		return nil
	}
	if status := executionStepStatusFromTransferStatus(transfer.GetStatus()); status != "" {
		var updated *model.ExecutionStep
		for _, step := range plan.Steps {
			if step == nil {
				continue
			}
			if !strings.EqualFold(strings.TrimSpace(step.TransferRef), transferRef) {
				continue
			}
			if step.TransferRef == "" {
				step.TransferRef = transferRef
			}
			setExecutionStepStatus(step, status)
			if updated == nil {
				updated = step
			}
		}
		return updated
	}
	return nil
}

func executionStepStatusFromTransferStatus(status chainv1.TransferStatus) model.OperationState {
	switch status {

	case chainv1.TransferStatus_TRANSFER_CREATED:
		return model.OperationStatePlanned

	case chainv1.TransferStatus_TRANSFER_PROCESSING:
		return model.OperationStateProcessing

	case chainv1.TransferStatus_TRANSFER_WAITING:
		return model.OperationStateWaiting

	case chainv1.TransferStatus_TRANSFER_SUCCESS:
		return model.OperationStateSuccess

	case chainv1.TransferStatus_TRANSFER_FAILED:
		return model.OperationStateFailed

	case chainv1.TransferStatus_TRANSFER_CANCELLED:
		return model.OperationStateCancelled

	default:
		return model.OperationStatePlanned
	}
}

func setExecutionStepMetadata(step *model.ExecutionStep, key, value string) {
	if step == nil {
		return
	}
	key = strings.TrimSpace(key)
	if key == "" {
		return
	}
	value = strings.TrimSpace(value)
	if value == "" {
		if step.Metadata != nil {
			delete(step.Metadata, key)
			if len(step.Metadata) == 0 {
				step.Metadata = nil
			}
		}
		return
	}
	if step.Metadata == nil {
		step.Metadata = map[string]string{}
	}
	step.Metadata[key] = value
}
