package orchestrator

import (
	"fmt"
	"strings"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)

func ensureExecutionRefs(payment *model.Payment) *model.ExecutionRefs {
	if payment.Execution == nil {
		payment.Execution = &model.ExecutionRefs{}
	}
	return payment.Execution
}

func executionQuote(payment *model.Payment, quote *orchestratorv1.PaymentQuote) *orchestratorv1.PaymentQuote {
	if quote != nil {
		return quote
	}
	if payment != nil && payment.LastQuote != nil {
		return modelQuoteToProto(payment.LastQuote)
	}
	return &orchestratorv1.PaymentQuote{}
}

func ensureExecutionPlanForPlan(payment *model.Payment, plan *model.PaymentPlan) *model.ExecutionPlan {
	if payment == nil || plan == nil {
		return nil
	}
	execPlan := payment.ExecutionPlan
	if execPlan == nil {
		execPlan = &model.ExecutionPlan{}
		payment.ExecutionPlan = execPlan
	}
	existing := map[string]*model.ExecutionStep{}
	for _, step := range execPlan.Steps {
		if step == nil || strings.TrimSpace(step.Code) == "" {
			continue
		}
		existing[strings.TrimSpace(step.Code)] = step
	}
	steps := make([]*model.ExecutionStep, len(plan.Steps))
	for idx, planStep := range plan.Steps {
		code := planStepID(planStep, idx)
		step := existing[code]
		if step == nil {
			step = &model.ExecutionStep{Code: code}
		}
		if step.Description == "" {
			step.Description = describePlanStep(planStep)
		}
		step.Amount = cloneMoney(planStep.Amount)
		if step.Metadata == nil || strings.TrimSpace(step.Metadata[executionStepMetadataStatus]) == "" {
			setExecutionStepStatus(step, executionStepStatusPlanned)
		}
		steps[idx] = step
	}
	execPlan.Steps = steps
	return execPlan
}

func executionPlanComplete(plan *model.ExecutionPlan) bool {
	if plan == nil || len(plan.Steps) == 0 {
		return false
	}
	for _, step := range plan.Steps {
		if step == nil {
			continue
		}
		status := executionStepStatus(step)
		if status == executionStepStatusSkipped {
			continue
		}
		if status != executionStepStatusConfirmed {
			return false
		}
	}
	return true
}

func blockStepConfirmed(plan *model.PaymentPlan, execPlan *model.ExecutionPlan) bool {
	if plan == nil || execPlan == nil || len(plan.Steps) == 0 {
		return false
	}
	execSteps := executionStepsByCode(execPlan)
	for idx, step := range plan.Steps {
		if step == nil || step.Action != model.RailOperationBlock {
			continue
		}
		execStep := execSteps[planStepID(step, idx)]
		if execStep == nil {
			continue
		}
		if executionStepStatus(execStep) == executionStepStatusConfirmed {
			return true
		}
	}
	return false
}

func planStepID(step *model.PaymentStep, idx int) string {
	if step != nil {
		if val := strings.TrimSpace(step.StepID); val != "" {
			return val
		}
	}
	return fmt.Sprintf("plan_step_%d", idx)
}

func describePlanStep(step *model.PaymentStep) string {
	if step == nil {
		return ""
	}
	return strings.TrimSpace(fmt.Sprintf("%s %s", step.Rail, step.Action))
}

func planStepIdempotencyKey(payment *model.Payment, idx int, step *model.PaymentStep) string {
	base := ""
	if payment != nil {
		base = strings.TrimSpace(payment.IdempotencyKey)
		if base == "" {
			base = strings.TrimSpace(payment.PaymentRef)
		}
	}
	if base == "" {
		base = "payment"
	}
	if step == nil {
		return fmt.Sprintf("%s:plan:%d", base, idx)
	}
	stepID := strings.TrimSpace(step.StepID)
	if stepID == "" {
		stepID = fmt.Sprintf("%d", idx)
	}
	return fmt.Sprintf("%s:plan:%s:%s:%s", base, stepID, strings.ToLower(string(step.Rail)), strings.ToLower(string(step.Action)))
}

func failureCodeForStep(step *model.PaymentStep) model.PaymentFailureCode {
	if step == nil {
		return model.PaymentFailureCodePolicy
	}
	switch step.Rail {
	case model.RailLedger:
		if step.Action == model.RailOperationFXConvert {
			return model.PaymentFailureCodeFX
		}
		return model.PaymentFailureCodeLedger
	case model.RailCrypto:
		return model.PaymentFailureCodeChain
	default:
		return model.PaymentFailureCodePolicy
	}
}
