package orchestrator

import (
	"sort"
	"strings"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/merrors"
)

func planExecutionOrder(plan *model.PaymentPlan) ([]int, map[string]int, error) {
	if plan == nil || len(plan.Steps) == 0 {
		return nil, nil, merrors.InvalidArgument("payment plan: steps are required")
	}
	idToIndex := map[string]int{}
	for idx, step := range plan.Steps {
		if step == nil {
			return nil, nil, merrors.InvalidArgument("payment plan: step is required")
		}
		id := planStepID(step, idx)
		if _, exists := idToIndex[id]; exists {
			return nil, nil, merrors.InvalidArgument("payment plan: duplicate step id")
		}
		idToIndex[id] = idx
	}

	indegree := make([]int, len(plan.Steps))
	adj := make([][]int, len(plan.Steps))
	for idx, step := range plan.Steps {
		for _, dep := range step.DependsOn {
			key := strings.TrimSpace(dep)
			if key == "" {
				continue
			}
			depIdx, ok := idToIndex[key]
			if !ok {
				return nil, nil, merrors.InvalidArgument("payment plan: dependency missing")
			}
			adj[depIdx] = append(adj[depIdx], idx)
			indegree[idx]++
		}
	}

	queue := make([]int, 0, len(plan.Steps))
	for idx := range indegree {
		if indegree[idx] == 0 {
			queue = append(queue, idx)
		}
	}
	sort.Ints(queue)

	order := make([]int, 0, len(plan.Steps))
	for len(queue) > 0 {
		current := queue[0]
		queue = queue[1:]
		order = append(order, current)
		for _, next := range adj[current] {
			indegree[next]--
			if indegree[next] == 0 {
				queue = append(queue, next)
			}
		}
		sort.Ints(queue)
	}

	if len(order) != len(plan.Steps) {
		return nil, nil, merrors.InvalidArgument("payment plan: dependency cycle detected")
	}
	return order, idToIndex, nil
}

func executionStepsByCode(plan *model.ExecutionPlan) map[string]*model.ExecutionStep {
	result := map[string]*model.ExecutionStep{}
	if plan == nil {
		return result
	}
	for _, step := range plan.Steps {
		if step == nil {
			continue
		}
		if code := strings.TrimSpace(step.Code); code != "" {
			result[code] = step
		}
	}
	return result
}

func planStepsByID(plan *model.PaymentPlan) map[string]*model.PaymentStep {
	result := map[string]*model.PaymentStep{}
	if plan == nil {
		return result
	}
	for idx, step := range plan.Steps {
		if step == nil {
			continue
		}
		id := planStepID(step, idx)
		if id == "" {
			continue
		}
		result[id] = step
	}
	return result
}

func stepDependenciesReady(step *model.PaymentStep, execSteps map[string]*model.ExecutionStep, planSteps map[string]*model.PaymentStep, requireConfirmed bool) (bool, bool, error) {
	if step == nil {
		return false, false, merrors.InvalidArgument("payment plan: step is required")
	}
	for _, dep := range step.DependsOn {
		key := strings.TrimSpace(dep)
		if key == "" {
			continue
		}
		execStep := execSteps[key]
		if execStep == nil {
			return false, false, merrors.InvalidArgument("payment plan: dependency missing")
		}
		depStep := planSteps[key]
		needsConfirm := requireConfirmed
		if depStep != nil && depStep.Action == model.RailOperationObserveConfirm {
			needsConfirm = true
		}
		status := executionStepStatus(execStep)
		switch status {
		case executionStepStatusFailed, executionStepStatusCancelled:
			return false, true, nil
		case executionStepStatusConfirmed, executionStepStatusSkipped:
			continue
		case executionStepStatusSubmitted:
			if needsConfirm {
				return false, false, nil
			}
			continue
		default:
			return false, false, nil
		}
	}

	if step.CommitPolicy != model.CommitPolicyAfterSuccess {
		return true, false, nil
	}

	commitAfter := step.CommitAfter
	if len(commitAfter) == 0 {
		commitAfter = step.DependsOn
	}
	for _, dep := range commitAfter {
		key := strings.TrimSpace(dep)
		if key == "" {
			continue
		}
		execStep := execSteps[key]
		if execStep == nil {
			return false, false, merrors.InvalidArgument("payment plan: commit dependency missing")
		}
		status := executionStepStatus(execStep)
		switch status {
		case executionStepStatusFailed, executionStepStatusCancelled:
			return false, true, nil
		case executionStepStatusConfirmed, executionStepStatusSkipped:
			continue
		default:
			return false, false, nil
		}
	}
	return true, false, nil
}

func cardPayoutDependenciesConfirmed(plan *model.PaymentPlan, execPlan *model.ExecutionPlan) bool {
	if execPlan == nil {
		return false
	}
	if plan == nil || len(plan.Steps) == 0 {
		return sourceStepsConfirmed(execPlan)
	}
	execSteps := executionStepsByCode(execPlan)
	planSteps := planStepsByID(plan)
	for _, step := range plan.Steps {
		if step == nil {
			continue
		}
		if step.Rail != model.RailCardPayout || step.Action != model.RailOperationSend {
			continue
		}
		ready, blocked, err := stepDependenciesReady(step, execSteps, planSteps, true)
		if err != nil || blocked {
			return false
		}
		return ready
	}
	return false
}
