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"

	executionStepStatusPlanned   = "planned"
	executionStepStatusSubmitted = "submitted"
	executionStepStatusConfirmed = "confirmed"
	executionStepStatusFailed    = "failed"
	executionStepStatusCancelled = "cancelled"
	executionStepStatusSkipped   = "skipped"
)

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

func setExecutionStepStatus(step *model.ExecutionStep, status string) {
	status = strings.ToLower(strings.TrimSpace(status))
	setExecutionStepMetadata(step, executionStepMetadataStatus, status)
}

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 executionStepStatus(step *model.ExecutionStep) string {
	if step == nil {
		return ""
	}
	status := strings.TrimSpace(step.Metadata[executionStepMetadataStatus])
	if status == "" {
		return executionStepStatusPlanned
	}
	return strings.ToLower(status)
}

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
		}
		status := executionStepStatus(step)
		if status == executionStepStatusSkipped {
			continue
		}
		hasSource = true
		if status != executionStepStatusConfirmed {
			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
	}
	step := findExecutionStepByTransferRef(plan, transferRef)
	if step == nil {
		return nil
	}
	if step.TransferRef == "" {
		step.TransferRef = transferRef
	}
	if status := executionStepStatusFromTransferStatus(transfer.GetStatus()); status != "" {
		setExecutionStepStatus(step, status)
	}
	return step
}

func executionStepStatusFromTransferStatus(status chainv1.TransferStatus) string {
	switch status {
	case chainv1.TransferStatus_TRANSFER_CONFIRMED:
		return executionStepStatusConfirmed
	case chainv1.TransferStatus_TRANSFER_FAILED:
		return executionStepStatusFailed
	case chainv1.TransferStatus_TRANSFER_CANCELLED:
		return executionStepStatusCancelled
	case chainv1.TransferStatus_TRANSFER_SIGNING,
		chainv1.TransferStatus_TRANSFER_PENDING,
		chainv1.TransferStatus_TRANSFER_SUBMITTED:
		return executionStepStatusSubmitted
	default:
		return ""
	}
}

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
}
