package orchestrator

import (
	"fmt"
	"strings"

	"github.com/google/uuid"
	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/model/account_role"
	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.ExecutionPlan != nil {
		return payment.ExecutionPlan
	}

	exec := &model.ExecutionPlan{
		Steps: make([]*model.ExecutionStep, 0, len(plan.Steps)),
	}

	for _, step := range plan.Steps {
		if step == nil {
			continue
		}

		exec.Steps = append(exec.Steps, &model.ExecutionStep{
			Code:         step.StepID,
			State:        model.OperationStatePlanned,
			OperationRef: uuid.New().String(),
		})
	}

	return exec
}

func executionPlanComplete(plan *model.ExecutionPlan) bool {
	if plan == nil || len(plan.Steps) == 0 {
		return false
	}
	for _, step := range plan.Steps {
		if step == nil {
			continue
		}
		if step.State == model.OperationStateSkipped {
			continue
		}
		if step.State != model.OperationStateSuccess {
			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 execStep.State == model.OperationStateSuccess {
			return true
		}
	}
	return false
}

func roleHintsForStep(plan *model.PaymentPlan, idx int) (*account_role.AccountRole, *account_role.AccountRole) {
	if plan == nil || idx <= 0 {
		return nil, nil
	}
	for i := idx - 1; i >= 0; i-- {
		step := plan.Steps[i]
		if step == nil {
			continue
		}
		if step.Rail != model.RailLedger || step.Action != model.RailOperationMove {
			continue
		}
		if step.ToRole != nil && strings.TrimSpace(string(*step.ToRole)) != "" {
			role := *step.ToRole
			return &role, nil
		}
	}
	return nil, nil
}

func linkRailObservation(payment *model.Payment, rail model.Rail, referenceID, dependsOn string) {
	if payment == nil || payment.PaymentPlan == nil {
		return
	}
	ref := strings.TrimSpace(referenceID)
	if ref == "" {
		return
	}
	plan := payment.PaymentPlan
	execPlan := ensureExecutionPlanForPlan(payment, plan)
	if execPlan == nil {
		return
	}
	dep := strings.TrimSpace(dependsOn)
	for idx, planStep := range plan.Steps {
		if planStep == nil {
			continue
		}
		if planStep.Rail != rail || planStep.Action != model.RailOperationObserveConfirm {
			continue
		}
		if dep != "" {
			matched := false
			for _, entry := range planStep.DependsOn {
				if strings.EqualFold(strings.TrimSpace(entry), dep) {
					matched = true
					break
				}
			}
			if !matched {
				continue
			}
		}
		if idx >= len(execPlan.Steps) {
			continue
		}
		execStep := execPlan.Steps[idx]
		if execStep == nil {
			execStep = &model.ExecutionStep{Code: planStepID(planStep, idx), Description: describePlanStep(planStep)}
			execPlan.Steps[idx] = execStep
		}
		if execStep.TransferRef == "" {
			execStep.TransferRef = ref
		}
	}
}

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
	}
}
