package ssched

import (
	"time"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/ostate"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

// Runtime selects runnable orchestration steps and reconciles step runtime states.
type Runtime interface {
	Schedule(in Input) (*Output, error)
}

// Input is the scheduler payload.
type Input struct {
	Steps          []xplan.Step
	StepExecutions []agg.StepExecution
	Retry          RetryPolicy
}

// RetryPolicy configures per-step retry limits.
type RetryPolicy struct {
	MaxAttempts          uint32
	MaxAttemptsByStepRef map[string]uint32
}

// RunnableStep is a step selected for execution.
type RunnableStep struct {
	StepRef  string
	StepCode string
	Attempt  uint32
}

// BlockedReason classifies why a step is not runnable.
type BlockedReason string

const (
	BlockedWaitingDependencies BlockedReason = "waiting_dependencies"
	BlockedInProgress          BlockedReason = "in_progress"
	BlockedNeedsAttention      BlockedReason = "needs_attention"
	BlockedRetryExhausted      BlockedReason = "retry_exhausted"
	BlockedDependencyMismatch  BlockedReason = "dependency_mismatch"
)

// BlockedStep is a step that cannot run in the current scheduling tick.
type BlockedStep struct {
	StepRef  string
	StepCode string
	Reason   BlockedReason
}

// Output is the scheduler decision for one tick.
type Output struct {
	StepExecutions []agg.StepExecution
	Runnable       []RunnableStep
	Blocked        []BlockedStep
	Skipped        []string
}

// Dependencies configures scheduler integrations.
type Dependencies struct {
	Logger       mlogger.Logger
	StateMachine ostate.StateMachine
	Now          func() time.Time
}

func New(deps ...Dependencies) Runtime {
	var dep Dependencies
	if len(deps) > 0 {
		dep = deps[0]
	}
	logger := dep.Logger
	if logger == nil {
		logger = zap.NewNop()
	}
	logger = logger.Named("scheduler")
	stateMachine := dep.StateMachine
	if stateMachine == nil {
		stateMachine = ostate.New(ostate.Dependencies{Logger: logger})
	}
	now := dep.Now
	if now == nil {
		now = func() time.Time {
			return time.Now().UTC()
		}
	}
	return &svc{
		logger:       logger,
		stateMachine: stateMachine,
		now:          now,
	}
}
