package domain

import (
	"time"
)

// SagaStatus represents the status of a saga execution.
type SagaStatus string

const (
	// SagaStatusPending indicates the saga hasn't started yet.
	SagaStatusPending SagaStatus = "pending"
	// SagaStatusRunning indicates the saga is executing.
	SagaStatusRunning SagaStatus = "running"
	// SagaStatusCompleted indicates the saga finished successfully.
	SagaStatusCompleted SagaStatus = "completed"
	// SagaStatusFailed indicates the saga failed and may need compensation.
	SagaStatusFailed SagaStatus = "failed"
	// SagaStatusCompensating indicates compensation is in progress.
	SagaStatusCompensating SagaStatus = "compensating"
	// SagaStatusCompensated indicates compensation completed successfully.
	SagaStatusCompensated SagaStatus = "compensated"
)

// IsValid returns true if the status is known.
func (s SagaStatus) IsValid() bool {
	switch s {
	case SagaStatusPending, SagaStatusRunning, SagaStatusCompleted,
		SagaStatusFailed, SagaStatusCompensating, SagaStatusCompensated:
		return true
	}
	return false
}

// IsTerminal returns true if the status is a final state.
func (s SagaStatus) IsTerminal() bool {
	return s == SagaStatusCompleted || s == SagaStatusFailed || s == SagaStatusCompensated
}

// StepStatus represents the status of a saga step.
type StepStatus string

const (
	StepStatusPending   StepStatus = "pending"
	StepStatusRunning   StepStatus = "running"
	StepStatusCompleted StepStatus = "completed"
	StepStatusFailed    StepStatus = "failed"
	StepStatusSkipped   StepStatus = "skipped"
)

// IsValid returns true if the status is known.
func (s StepStatus) IsValid() bool {
	switch s {
	case StepStatusPending, StepStatusRunning, StepStatusCompleted,
		StepStatusFailed, StepStatusSkipped:
		return true
	}
	return false
}

// IsTerminal returns true if the status is a final state.
func (s StepStatus) IsTerminal() bool {
	return s == StepStatusCompleted || s == StepStatusFailed || s == StepStatusSkipped
}

// BackoffType represents the type of retry backoff.
type BackoffType string

const (
	BackoffNone        BackoffType = "none"
	BackoffLinear      BackoffType = "linear"
	BackoffExponential BackoffType = "exponential"
)

// RetryPolicy defines how a step should be retried on failure.
type RetryPolicy struct {
	// MaxAttempts is the maximum number of retry attempts (including initial).
	MaxAttempts int `json:"max_attempts"`
	// BackoffType is the type of backoff between retries.
	BackoffType BackoffType `json:"backoff_type"`
	// InitialDelay is the initial delay between retries.
	InitialDelay time.Duration `json:"initial_delay"`
	// MaxDelay is the maximum delay between retries.
	MaxDelay time.Duration `json:"max_delay"`
}

// DefaultRetryPolicy returns a sensible default retry policy.
func DefaultRetryPolicy() RetryPolicy {
	return RetryPolicy{
		MaxAttempts:  3,
		BackoffType:  BackoffExponential,
		InitialDelay: 5 * time.Second,
		MaxDelay:     60 * time.Second,
	}
}

// SagaStep represents a single step in a saga.
type SagaStep struct {
	// ID is the unique identifier for this step instance.
	ID string `json:"id"`
	// SagaID is the saga this step belongs to.
	SagaID string `json:"saga_id"`
	// Name is the step name (unique within the saga).
	Name string `json:"name"`
	// Status is the current step status.
	Status StepStatus `json:"status"`
	// Action is the step action type (api, wait_pipeline, wait_build, shell).
	Action string `json:"action"`
	// DependsOn lists step names that must complete before this step can run.
	DependsOn []string `json:"depends_on,omitempty"`
	// RetryPolicy defines the retry behavior for this step.
	RetryPolicy RetryPolicy `json:"retry_policy"`
	// Compensate is the name of the compensation step to run on rollback.
	Compensate string `json:"compensate,omitempty"`
	// Config contains action-specific configuration.
	Config map[string]any `json:"config,omitempty"`
	// Output contains the step output after completion.
	Output map[string]any `json:"output,omitempty"`
	// Error contains the error message if the step failed.
	Error string `json:"error,omitempty"`
	// RetryCount is the number of times this step has been retried.
	RetryCount int `json:"retry_count"`
	// StartedAt is when the step started executing.
	StartedAt *time.Time `json:"started_at,omitempty"`
	// CompletedAt is when the step finished (success or failure).
	CompletedAt *time.Time `json:"completed_at,omitempty"`
}

// CanRun returns true if this step is ready to execute.
func (s *SagaStep) CanRun(completedSteps map[string]bool) bool {
	if s.Status != StepStatusPending {
		return false
	}
	for _, dep := range s.DependsOn {
		if !completedSteps[dep] {
			return false
		}
	}
	return true
}

// Saga represents a multi-step workflow with compensation support.
type Saga struct {
	// ID is the unique identifier for this saga.
	ID string `json:"id"`
	// Name is the saga name (from the definition).
	Name string `json:"name"`
	// Status is the current saga status.
	Status SagaStatus `json:"status"`
	// Definition is the YAML definition used to create this saga.
	Definition string `json:"definition,omitempty"`
	// Vars contains template variables for step configuration.
	Vars map[string]string `json:"vars,omitempty"`
	// Outputs contains outputs from completed steps, keyed by step name.
	Outputs map[string]map[string]any `json:"outputs,omitempty"`
	// CurrentStep is the name of the currently executing step.
	CurrentStep string `json:"current_step,omitempty"`
	// RetryCount is the number of times the saga has been retried.
	RetryCount int `json:"retry_count"`
	// MaxRetries is the maximum number of saga-level retries.
	MaxRetries int `json:"max_retries"`
	// Error contains the error message if the saga failed.
	Error string `json:"error,omitempty"`
	// Steps contains all steps in this saga.
	Steps []SagaStep `json:"steps,omitempty"`
	// CreatedAt is when the saga was created.
	CreatedAt time.Time `json:"created_at"`
	// UpdatedAt is when the saga was last updated.
	UpdatedAt time.Time `json:"updated_at"`
	// CompletedAt is when the saga finished (success, failure, or compensation).
	CompletedAt *time.Time `json:"completed_at,omitempty"`
}

// CompletedSteps returns a map of step names that have completed (successfully or skipped).
// Skipped steps are considered complete for dependency resolution purposes.
func (s *Saga) CompletedSteps() map[string]bool {
	completed := make(map[string]bool)
	for _, step := range s.Steps {
		if step.Status == StepStatusCompleted || step.Status == StepStatusSkipped {
			completed[step.Name] = true
		}
	}
	return completed
}

// GetStep returns a step by name, or nil if not found.
func (s *Saga) GetStep(name string) *SagaStep {
	for i := range s.Steps {
		if s.Steps[i].Name == name {
			return &s.Steps[i]
		}
	}
	return nil
}

// FailedStep returns the first failed step, or nil if none failed.
func (s *Saga) FailedStep() *SagaStep {
	for i := range s.Steps {
		if s.Steps[i].Status == StepStatusFailed {
			return &s.Steps[i]
		}
	}
	return nil
}

// RunnableSteps returns steps that are ready to execute.
func (s *Saga) RunnableSteps() []SagaStep {
	completed := s.CompletedSteps()
	var runnable []SagaStep
	for _, step := range s.Steps {
		if step.CanRun(completed) {
			runnable = append(runnable, step)
		}
	}
	return runnable
}

// SagaFilters specifies criteria for listing sagas.
type SagaFilters struct {
	// Name filters by saga name.
	Name string
	// Status filters by saga status.
	Status SagaStatus
	// Since filters sagas created after this time.
	Since time.Time
	// Limit is the maximum number of sagas to return.
	Limit int
}

// DefaultSagaFilters returns filters with default values.
func DefaultSagaFilters() SagaFilters {
	return SagaFilters{
		Limit: 50,
	}
}

// Normalize applies defaults and limits to the filters.
func (f *SagaFilters) Normalize() {
	if f.Limit <= 0 {
		f.Limit = 50
	}
	if f.Limit > 200 {
		f.Limit = 200
	}
}
