package worker

import (
	"context"
	"log/slog"
	"strings"
	"time"

	webhookdomain "github.com/codex-k8s/kodex/libs/go/domain/webhook"
	floweventrepo "github.com/codex-k8s/kodex/services/jobs/worker/internal/domain/repository/flowevent"
	learningfeedbackrepo "github.com/codex-k8s/kodex/services/jobs/worker/internal/domain/repository/learningfeedback"
	runqueuerepo "github.com/codex-k8s/kodex/services/jobs/worker/internal/domain/repository/runqueue"
)

const defaultWorkerID = "worker"
const defaultStateInReviewLabel = webhookdomain.DefaultStateInReviewLabel
const defaultProductionNamespace = "kodex-prod"

// Config defines worker run-loop behavior.
type Config struct {
	// WorkerID uniquely identifies current worker instance.
	WorkerID string
	// ClaimLimit limits number of pending runs claimed per tick.
	ClaimLimit int
	// RunningCheckLimit limits running runs reconciled per tick.
	RunningCheckLimit int
	// StaleLeaseSweepLimit limits how many stale running leases are released per tick.
	StaleLeaseSweepLimit int
	// SlotsPerProject defines slot pool size per project scope.
	SlotsPerProject int
	// SlotLeaseTTL defines maximum duration of slot ownership.
	SlotLeaseTTL time.Duration
	// RunLeaseTTL defines maximum duration of running-run ownership by one worker.
	RunLeaseTTL time.Duration
	// RuntimePrepareRetryTimeout limits total retry time for runtime deploy preparation.
	RuntimePrepareRetryTimeout time.Duration
	// RuntimePrepareRetryInterval defines delay between retryable runtime deploy attempts.
	RuntimePrepareRetryInterval time.Duration
	// InteractionDispatchLimit limits interaction delivery attempts handled per tick.
	InteractionDispatchLimit int
	// InteractionExpiryLimit limits interaction expiry mutations handled per tick.
	InteractionExpiryLimit int
	// InteractionPendingAttemptTimeout defines when one pending delivery attempt can be reclaimed after worker loss.
	InteractionPendingAttemptTimeout time.Duration
	// InteractionRetryBaseInterval defines the first retry backoff delay for transport failures.
	InteractionRetryBaseInterval time.Duration
	// InteractionRetryMaxInterval caps exponential dispatch backoff for interaction retries.
	InteractionRetryMaxInterval time.Duration
	// InteractionMaxAttempts caps total dispatch attempts before marking delivery exhausted.
	InteractionMaxAttempts int
	// GitHubRateLimitWaitEnabledFallback is used only when runtime system settings are not wired.
	GitHubRateLimitWaitEnabledFallback bool
	// GitHubRateLimitSweepLimit limits how many due waits worker can reconcile per tick.
	GitHubRateLimitSweepLimit int
	// MissionControlWarmupInterval throttles per-project warmup execution.
	MissionControlWarmupInterval time.Duration
	// MissionControlWarmupProjectLimit limits warmup candidates inspected per tick.
	MissionControlWarmupProjectLimit int
	// MissionControlPendingCommandLimit limits Mission Control commands processed per tick.
	MissionControlPendingCommandLimit int
	// MissionControlClaimTTL bounds how long one worker owns a Mission Control command execution lease.
	MissionControlClaimTTL time.Duration
	// MissionControlRetryMaxAttempts bounds provider mutation retries per command.
	MissionControlRetryMaxAttempts int
	// MissionControlRetryBaseInterval defines the first retry delay for Mission Control provider mutations.
	MissionControlRetryBaseInterval time.Duration

	// ProjectLearningModeDefault is applied when the worker auto-creates projects from webhook payloads.
	ProjectLearningModeDefault bool
	// RunNamespacePrefix defines prefix for full-env run namespaces.
	RunNamespacePrefix string
	// RunNamespaceCleanupEnabled toggles namespace cleanup sweep execution.
	RunNamespaceCleanupEnabled bool
	// DefaultNamespaceTTL applies to full-env namespace retention when role-specific override is absent.
	DefaultNamespaceTTL time.Duration
	// NamespaceTTLByRole contains full-env namespace retention overrides per agent role key.
	NamespaceTTLByRole map[string]time.Duration
	// NamespaceLeaseSweepLimit limits how many expired managed namespaces are cleaned per tick.
	NamespaceLeaseSweepLimit int
	// StateInReviewLabel is applied to PR when run is ready for owner review.
	StateInReviewLabel string
	// ControlPlaneGRPCTarget is control-plane gRPC endpoint used by run jobs for callbacks.
	ControlPlaneGRPCTarget string
	// ControlPlaneMCPBaseURL is MCP endpoint passed to run job environment.
	ControlPlaneMCPBaseURL string
	// OpenAIAPIKey is injected into run pods for codex login.
	OpenAIAPIKey string
	// Context7APIKey enables Context7 documentation calls from run pods when set.
	Context7APIKey string
	// GitBotToken is injected into run pods for git transport only.
	GitBotToken string
	// GitBotUsername is GitHub username used with bot token for git transport auth.
	GitBotUsername string
	// GitBotMail is git author email configured in run pods.
	GitBotMail string
	// AgentDefaultModel is fallback model when run config labels do not override model.
	AgentDefaultModel string
	// AgentDefaultReasoningEffort is fallback reasoning profile when run config labels do not override reasoning.
	AgentDefaultReasoningEffort string
	// AgentDefaultLocale is fallback prompt locale.
	AgentDefaultLocale string
	// AgentBaseBranch is default base branch for PR flow.
	AgentBaseBranch string
	// JobImage is primary image for run Jobs.
	JobImage string
	// JobImageFallback is optional fallback image for run Jobs.
	JobImageFallback string
	// KubernetesNamespace is default worker namespace for run workloads.
	KubernetesNamespace string
	// ProductionNamespace is namespace used by production read-only postdeploy/ops runs.
	ProductionNamespace string
	// WorkerPodNamespace is the namespace where worker pods run and are listed for liveness fallback.
	WorkerPodNamespace string
	// AIRepairNamespace is namespace used by ai-repair workload runs.
	AIRepairNamespace string
	// AIRepairServiceAccount is service account used by ai-repair workload pod.
	AIRepairServiceAccount string
	// AIModelGPT54Label maps GitHub label to gpt-5.4 model.
	AIModelGPT54Label string
	// AIModelGPT53CodexLabel maps GitHub label to gpt-5.3-codex model.
	AIModelGPT53CodexLabel string
	// AIModelGPT53CodexSparkLabel maps GitHub label to gpt-5.3-codex-spark model.
	AIModelGPT53CodexSparkLabel string
	// AIModelGPT52CodexLabel maps GitHub label to gpt-5.2-codex model.
	AIModelGPT52CodexLabel string
	// AIModelGPT52Label maps GitHub label to gpt-5.2 model.
	AIModelGPT52Label string
	// AIModelGPT51CodexMaxLabel maps GitHub label to gpt-5.1-codex-max model.
	AIModelGPT51CodexMaxLabel string
	// AIModelGPT51CodexMiniLabel maps GitHub label to gpt-5.1-codex-mini model.
	AIModelGPT51CodexMiniLabel string
	// AIReasoningLowLabel maps GitHub label to low reasoning profile.
	AIReasoningLowLabel string
	// AIReasoningMediumLabel maps GitHub label to medium reasoning profile.
	AIReasoningMediumLabel string
	// AIReasoningHighLabel maps GitHub label to high reasoning profile.
	AIReasoningHighLabel string
	// AIReasoningExtraHighLabel maps GitHub label to extra-high reasoning profile.
	AIReasoningExtraHighLabel string
}

// Dependencies groups service collaborators to keep constructor signatures compact.
type Dependencies struct {
	// Runs provides queue and lifecycle operations over agent runs.
	Runs runqueuerepo.Repository
	// Events persists flow lifecycle events.
	Events floweventrepo.Repository
	// Feedback persists optional learning-mode explanations.
	Feedback learningfeedbackrepo.Repository
	// Launcher starts and reconciles Kubernetes jobs.
	Launcher Launcher
	// RuntimePreparer prepares runtime environment stack before run job launch.
	RuntimePreparer RuntimeEnvironmentPreparer
	// MCPTokenIssuer issues short-lived MCP token for run pods.
	MCPTokenIssuer MCPTokenIssuer
	// RunStatus updates one run-bound issue status comment.
	RunStatus RunStatusNotifier
	// Interactions claims and completes built-in interaction delivery lifecycle through control-plane.
	Interactions InteractionLifecycleClient
	// GitHubRateLimits claims and processes due GitHub rate-limit waits through control-plane.
	GitHubRateLimits GitHubRateLimitWaitProcessor
	// MissionControl coordinates Mission Control warmup and command execution through control-plane.
	MissionControl MissionControlClient
	// InteractionDispatcher sends interaction envelopes to the current adapter implementation.
	InteractionDispatcher InteractionDispatcher
	// Logger records worker diagnostics.
	Logger *slog.Logger
	// JobImageChecker checks whether image references are available before launch.
	JobImageChecker JobImageAvailabilityChecker
	// SystemSettings exposes hot-reloaded runtime feature switches.
	SystemSettings runtimeSystemSettings
}

// Service orchestrates pending runs to Kubernetes Jobs and final statuses.
type Service struct {
	cfg                      Config
	runs                     runqueuerepo.Repository
	events                   floweventrepo.Repository
	feedback                 learningfeedbackrepo.Repository
	launcher                 Launcher
	deployer                 RuntimeEnvironmentPreparer
	mcpTokens                MCPTokenIssuer
	runStatus                RunStatusNotifier
	interactions             InteractionLifecycleClient
	githubRateLimits         GitHubRateLimitWaitProcessor
	missionCtl               MissionControlClient
	dispatcher               InteractionDispatcher
	logger                   *slog.Logger
	labels                   runAgentLabelCatalog
	image                    JobImageSelectionPolicy
	systemSettings           runtimeSystemSettings
	lastMissionControlWarmup map[string]time.Time
	now                      func() time.Time
}

// JobImageAvailabilityChecker checks run Job image existence.
type JobImageAvailabilityChecker interface {
	IsImageAvailable(ctx context.Context, imageRef string) (bool, error)
	ResolvePreviousImage(ctx context.Context, imageRef string) (string, bool, error)
}

// JobImageSelectionPolicy defines primary/fallback image configuration for run job launches.
type JobImageSelectionPolicy struct {
	Primary  string
	Fallback string
	Checker  JobImageAvailabilityChecker
}

type runtimeSystemSettings interface {
	GitHubRateLimitWaitEnabled() bool
	QualityGovernanceEnabled() bool
}

// NewService creates worker orchestrator instance.
func NewService(cfg Config, deps Dependencies) *Service {
	if cfg.ClaimLimit <= 0 {
		cfg.ClaimLimit = 1
	}
	if cfg.RunningCheckLimit <= 0 {
		cfg.RunningCheckLimit = 100
	}
	if cfg.StaleLeaseSweepLimit <= 0 {
		cfg.StaleLeaseSweepLimit = 100
	}
	if cfg.SlotsPerProject <= 0 {
		cfg.SlotsPerProject = 1
	}
	if cfg.SlotLeaseTTL <= 0 {
		cfg.SlotLeaseTTL = 5 * time.Minute
	}
	if cfg.RuntimePrepareRetryTimeout <= 0 {
		cfg.RuntimePrepareRetryTimeout = 30 * time.Minute
	}
	if cfg.RunLeaseTTL <= 0 {
		cfg.RunLeaseTTL = cfg.RuntimePrepareRetryTimeout + 5*time.Minute
	}
	if cfg.RunLeaseTTL <= 0 {
		cfg.RunLeaseTTL = 45 * time.Minute
	}
	if cfg.RuntimePrepareRetryInterval <= 0 {
		cfg.RuntimePrepareRetryInterval = 3 * time.Second
	}
	if cfg.InteractionDispatchLimit <= 0 {
		cfg.InteractionDispatchLimit = 10
	}
	if cfg.InteractionExpiryLimit <= 0 {
		cfg.InteractionExpiryLimit = 10
	}
	if cfg.InteractionPendingAttemptTimeout <= 0 {
		cfg.InteractionPendingAttemptTimeout = 2 * time.Minute
	}
	if cfg.InteractionRetryBaseInterval <= 0 {
		cfg.InteractionRetryBaseInterval = 30 * time.Second
	}
	if cfg.InteractionRetryMaxInterval <= 0 {
		cfg.InteractionRetryMaxInterval = 15 * time.Minute
	}
	if cfg.InteractionMaxAttempts <= 0 {
		cfg.InteractionMaxAttempts = 3
	}
	if cfg.GitHubRateLimitSweepLimit <= 0 {
		cfg.GitHubRateLimitSweepLimit = 20
	}
	if cfg.MissionControlWarmupInterval <= 0 {
		cfg.MissionControlWarmupInterval = 15 * time.Minute
	}
	if cfg.MissionControlWarmupProjectLimit <= 0 {
		cfg.MissionControlWarmupProjectLimit = 20
	}
	if cfg.MissionControlPendingCommandLimit <= 0 {
		cfg.MissionControlPendingCommandLimit = 20
	}
	if cfg.MissionControlClaimTTL <= 0 {
		cfg.MissionControlClaimTTL = 2 * time.Minute
	}
	if cfg.MissionControlRetryMaxAttempts <= 0 {
		cfg.MissionControlRetryMaxAttempts = 3
	}
	if cfg.MissionControlRetryBaseInterval <= 0 {
		cfg.MissionControlRetryBaseInterval = 2 * time.Second
	}
	if cfg.WorkerID == "" {
		cfg.WorkerID = defaultWorkerID
	}
	if cfg.RunNamespacePrefix == "" {
		cfg.RunNamespacePrefix = defaultRunNamespacePrefix
	}
	if cfg.DefaultNamespaceTTL <= 0 {
		cfg.DefaultNamespaceTTL = 24 * time.Hour
	}
	if cfg.NamespaceLeaseSweepLimit <= 0 {
		cfg.NamespaceLeaseSweepLimit = 200
	}
	cfg.StateInReviewLabel = strings.TrimSpace(cfg.StateInReviewLabel)
	if cfg.StateInReviewLabel == "" {
		cfg.StateInReviewLabel = defaultStateInReviewLabel
	}
	cfg.ProductionNamespace = strings.TrimSpace(cfg.ProductionNamespace)
	if cfg.ProductionNamespace == "" {
		cfg.ProductionNamespace = defaultProductionNamespace
	}
	cfg.ControlPlaneGRPCTarget = strings.TrimSpace(cfg.ControlPlaneGRPCTarget)
	if cfg.ControlPlaneGRPCTarget == "" {
		cfg.ControlPlaneGRPCTarget = "kodex-control-plane:9090"
	}
	cfg.ControlPlaneMCPBaseURL = resolveControlPlaneMCPBaseURL(cfg.ControlPlaneMCPBaseURL, cfg.ControlPlaneGRPCTarget)
	cfg.OpenAIAPIKey = strings.TrimSpace(cfg.OpenAIAPIKey)
	cfg.Context7APIKey = strings.TrimSpace(cfg.Context7APIKey)
	cfg.GitBotToken = strings.TrimSpace(cfg.GitBotToken)
	cfg.GitBotUsername = strings.TrimSpace(cfg.GitBotUsername)
	if cfg.GitBotUsername == "" {
		cfg.GitBotUsername = "codex-bot"
	}
	cfg.GitBotMail = strings.TrimSpace(cfg.GitBotMail)
	if cfg.GitBotMail == "" {
		cfg.GitBotMail = "codex-bot@kodex.local"
	}
	cfg.AgentDefaultModel = strings.TrimSpace(cfg.AgentDefaultModel)
	if cfg.AgentDefaultModel == "" {
		cfg.AgentDefaultModel = modelGPT54
	}
	cfg.AgentDefaultReasoningEffort = strings.TrimSpace(strings.ToLower(cfg.AgentDefaultReasoningEffort))
	switch cfg.AgentDefaultReasoningEffort {
	case "extra-high", "extra_high", "extra high", "x-high":
		cfg.AgentDefaultReasoningEffort = "xhigh"
	}
	if cfg.AgentDefaultReasoningEffort == "" {
		cfg.AgentDefaultReasoningEffort = "high"
	}
	cfg.AgentDefaultLocale = strings.TrimSpace(cfg.AgentDefaultLocale)
	if cfg.AgentDefaultLocale == "" {
		cfg.AgentDefaultLocale = "ru"
	}
	cfg.AgentBaseBranch = strings.TrimSpace(cfg.AgentBaseBranch)
	if cfg.AgentBaseBranch == "" {
		cfg.AgentBaseBranch = "main"
	}
	cfg.JobImage = strings.TrimSpace(cfg.JobImage)
	cfg.JobImageFallback = strings.TrimSpace(cfg.JobImageFallback)
	cfg.KubernetesNamespace = strings.TrimSpace(cfg.KubernetesNamespace)
	if cfg.KubernetesNamespace == "" {
		cfg.KubernetesNamespace = "default"
	}
	cfg.ProductionNamespace = sanitizeDNSLabelValue(cfg.ProductionNamespace)
	if cfg.ProductionNamespace == "" {
		cfg.ProductionNamespace = cfg.KubernetesNamespace
	}
	cfg.WorkerPodNamespace = strings.TrimSpace(cfg.WorkerPodNamespace)
	if cfg.WorkerPodNamespace == "" {
		cfg.WorkerPodNamespace = cfg.KubernetesNamespace
	}
	cfg.AIRepairNamespace = sanitizeDNSLabelValue(cfg.AIRepairNamespace)
	if cfg.AIRepairNamespace == "" {
		cfg.AIRepairNamespace = cfg.ProductionNamespace
	}
	cfg.AIRepairServiceAccount = strings.TrimSpace(cfg.AIRepairServiceAccount)
	if cfg.AIRepairServiceAccount == "" {
		cfg.AIRepairServiceAccount = "kodex-control-plane"
	}
	labelCatalog := runAgentLabelCatalogFromConfig(cfg)
	cfg.NamespaceTTLByRole = normalizeNamespaceTTLByRole(cfg.NamespaceTTLByRole)
	if deps.Logger == nil {
		deps.Logger = slog.Default()
	}
	if deps.MCPTokenIssuer == nil {
		deps.MCPTokenIssuer = noopMCPTokenIssuer{}
	}
	if deps.RuntimePreparer == nil {
		deps.RuntimePreparer = noopRuntimeEnvironmentPreparer{}
	}
	if deps.RunStatus == nil {
		deps.RunStatus = noopRunStatusNotifier{}
	}
	if deps.Interactions == nil {
		deps.Interactions = noopInteractionLifecycleClient{}
	}
	if deps.GitHubRateLimits == nil {
		deps.GitHubRateLimits = noopGitHubRateLimitWaitProcessor{}
	}
	if deps.MissionControl == nil {
		deps.MissionControl = noopMissionControlClient{}
	}
	if deps.InteractionDispatcher == nil {
		deps.InteractionDispatcher = noopInteractionDispatcher{}
	}

	return &Service{
		cfg:              cfg,
		runs:             deps.Runs,
		events:           deps.Events,
		feedback:         deps.Feedback,
		launcher:         deps.Launcher,
		deployer:         deps.RuntimePreparer,
		mcpTokens:        deps.MCPTokenIssuer,
		runStatus:        deps.RunStatus,
		interactions:     deps.Interactions,
		githubRateLimits: deps.GitHubRateLimits,
		missionCtl:       deps.MissionControl,
		dispatcher:       deps.InteractionDispatcher,
		logger:           deps.Logger,
		labels:           labelCatalog,
		image: JobImageSelectionPolicy{
			Primary:  cfg.JobImage,
			Fallback: cfg.JobImageFallback,
			Checker:  deps.JobImageChecker,
		},
		systemSettings:           deps.SystemSettings,
		lastMissionControlWarmup: make(map[string]time.Time),
		now:                      time.Now,
	}
}
