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,
	requireSuccess bool,
) (ready bool, waiting bool, blocked bool, err error) {

	if step == nil {
		return false, false, false,
			merrors.InvalidArgument("payment plan: step is required")
	}

	// ------------------------------------------------------------
	// DependsOn — это ПРОСТО готовность, не успех
	// ------------------------------------------------------------
	for _, dep := range step.DependsOn {
		key := strings.TrimSpace(dep)
		if key == "" {
			continue
		}

		execStep := execSteps[key]
		if execStep == nil {
			// шага ещё не было → ждём
			return false, true, false, nil
		}

		if execStep.State == model.OperationStateFailed ||
			execStep.State == model.OperationStateCancelled {
			// зависимость умерла → этот шаг уже невозможен
			return false, false, true, nil
		}

		if !execStep.ReadyForNext() {
			// шаг ещё в процессе → ждём
			return false, true, false, nil
		}
	}

	// ------------------------------------------------------------
	// Commit policies
	// ------------------------------------------------------------
	switch step.CommitPolicy {

	case model.CommitPolicyImmediate, model.CommitPolicyUnspecified:
		return true, false, false, nil

	case model.CommitPolicyAfterSuccess:
		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, true, false,
					merrors.InvalidArgument("commit dependency missing")
			}

			if execStep.State == model.OperationStateFailed ||
				execStep.State == model.OperationStateCancelled {
				return false, false, true, nil
			}

			if !execStep.IsSuccess() {
				return false, true, false, nil
			}
		}

		return true, false, false, nil

	case model.CommitPolicyAfterFailure:
		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, true, false,
					merrors.InvalidArgument("commit dependency missing")
			}

			if execStep.State == model.OperationStateFailed {
				continue
			}

			if execStep.IsTerminal() {
				// завершился не фейлом → блокируем
				return false, false, true, nil
			}

			// ещё выполняется → ждём
			return false, true, false, nil
		}

		return true, false, false, nil

	case model.CommitPolicyAfterCanceled:
		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, true, false,
					merrors.InvalidArgument("commit dependency missing")
			}

			if !execStep.IsTerminal() {
				return false, true, false, nil
			}
		}

		return true, false, false, nil

	default:
		return true, false, false, nil
	}
}

func commitAfterDependenciesSucceeded(step *model.PaymentStep, execSteps map[string]*model.ExecutionStep) bool {
	if step == nil {
		return false
	}

	commitAfter := step.CommitAfter
	if len(commitAfter) == 0 {
		commitAfter = step.DependsOn
	}
	if len(commitAfter) == 0 {
		return false
	}

	for _, dep := range commitAfter {
		key := strings.TrimSpace(dep)
		if key == "" {
			continue
		}

		execStep := execSteps[key]
		if execStep == nil {
			return false
		}

		switch execStep.State {
		case model.OperationStateSuccess,
			model.OperationStateSkipped:
			continue
		default:
			return false
		}
	}

	return true
}

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, waiting, blocked, err :=
			stepDependenciesReady(step, execSteps, planSteps, true)

		if err != nil || blocked {
			// payout definitely cannot run
			return false
		}

		if waiting {
			// dependencies exist but are not finished yet
			// payout must NOT run
			return false
		}

		// only true when dependencies are REALLY satisfied
		return ready
	}

	return false
}
