// Package jobs implements the background job runner: a hybrid scheduler that
// drives idempotent periodic runners (workspace fetch, worktree prune,
// attachment cleanup, job reaper, job purge) directly via tickers, and routes
// retry-sensitive event handlers (webhook delivery) through a DB-backed jobs
// table with SELECT FOR UPDATE SKIP LOCKED dispatch.
package jobs

import (
	"context"
	"encoding/json"
	"errors"
	"time"

	"github.com/google/uuid"
)

// JobType identifies a kind of work that a Handler knows how to execute.
// Runners (periodic) are NOT job types — they tick directly. Handlers
// (event-driven) are.
type JobType string

const (
	// TypeWebhookDeliver delivers a notify.Message to the configured webhook URL.
	TypeWebhookDeliver JobType = "webhook.deliver"
	// TypeSecretReencrypt re-encrypts every encrypted-column row that is still on
	// an old key version onto the current active version (master-key rotation).
	// Enqueued by the rotate-key admin endpoint; idempotent + resumable.
	TypeSecretReencrypt JobType = "secret.reencrypt"
)

// JobStatus matches the CHECK constraint on jobs.status.
type JobStatus string

const (
	StatusPending   JobStatus = "pending"
	StatusRunning   JobStatus = "running"
	StatusCompleted JobStatus = "completed"
	StatusDead      JobStatus = "dead"
)

// Job mirrors a row in the jobs table.
type Job struct {
	ID          uuid.UUID
	Type        JobType
	Payload     json.RawMessage
	Status      JobStatus
	ScheduledAt time.Time
	Attempts    int32
	MaxAttempts int32
	LeasedAt    *time.Time
	LeasedBy    *string
	LastError   *string
	CompletedAt *time.Time
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

// MaxPayloadBytes caps payload size at 64 KB. Enforced at enqueue time.
const MaxPayloadBytes = 64 * 1024

// Handler executes a single Job. Returning a permanentError sends the job
// straight to dead-letter; any other error triggers retry per the worker's
// backoff schedule. Returning nil marks the job completed.
type Handler interface {
	Type() JobType
	Handle(ctx context.Context, job Job) error
}

// permanentError marks an error as non-retryable (4xx-equivalent).
type permanentError struct{ err error }

// Permanent wraps err so the worker routes the job to dead-letter without
// further retries.
func Permanent(err error) error {
	if err == nil {
		return nil
	}
	return &permanentError{err: err}
}

func (e *permanentError) Error() string { return e.err.Error() }
func (e *permanentError) Unwrap() error { return e.err }

// IsPermanent reports whether err was wrapped by Permanent.
func IsPermanent(err error) bool {
	var pe *permanentError
	return errors.As(err, &pe)
}

// ErrLeaseLost is returned by the terminal/retry write-back methods
// (Complete, FailWithRetry, DeadLetter) when the fencing predicate matches no
// row: the job is no longer leased by this worker (e.g. the reaper re-leased it
// to another worker after the lease timeout). The caller must treat this as a
// no-op — the write was correctly dropped to avoid clobbering the new run's
// state — and must NOT surface it as a failure that triggers further retries.
var ErrLeaseLost = errors.New("job lease lost; write-back skipped")

// RunnerConfig is the per-runner configuration loaded from config.yaml.
// Zero Interval means "use the runner's documented default".
type RunnerConfig struct {
	Enabled  bool
	Interval time.Duration
}

// WorktreePruneConfig extends RunnerConfig with prune-specific knobs.
type WorktreePruneConfig struct {
	Enabled       bool
	Interval      time.Duration
	IdleThreshold time.Duration
	WarningLead   time.Duration
}

// AttachmentCleanupConfig extends RunnerConfig with retention.
type AttachmentCleanupConfig struct {
	Enabled   bool
	Interval  time.Duration
	Retention time.Duration
}

// JobReaperConfig extends RunnerConfig with the lease timeout used to detect
// crashed workers.
type JobReaperConfig struct {
	Enabled      bool
	Interval     time.Duration
	LeaseTimeout time.Duration
}

// JobPurgeConfig extends RunnerConfig with completed/dead retention.
type JobPurgeConfig struct {
	Enabled            bool
	Interval           time.Duration
	CompletedRetention time.Duration
}

// AcpEventsPurgeConfig extends RunnerConfig with acp_events retention.
type AcpEventsPurgeConfig struct {
	Enabled   bool
	Interval  time.Duration
	Retention time.Duration
}

// MCPTokensPurgeConfig extends RunnerConfig with mcp_tokens retention.
type MCPTokensPurgeConfig struct {
	Enabled   bool
	Interval  time.Duration
	Retention time.Duration
}

// AcpSessionReaperConfig controls the ACP session reaper schedule.
type AcpSessionReaperConfig struct {
	Enabled  bool
	Interval time.Duration
}

// Config is the full jobs.Module configuration.
type Config struct {
	Enabled           bool
	Workers           int
	ShutdownGrace     time.Duration
	WorkspaceFetch    RunnerConfig
	WorktreePrune     WorktreePruneConfig
	AttachmentCleanup AttachmentCleanupConfig
	JobReaper         JobReaperConfig
	JobPurge          JobPurgeConfig
	AcpEventsPurge    AcpEventsPurgeConfig
	MCPTokensPurge    MCPTokensPurgeConfig
	AcpSessionReaper  AcpSessionReaperConfig
	// OrchestratorSchedule controls the task-decomposition parallel scheduler tick
	// (autonomy roadmap §10). Registered via RunnerFns key "orchestrator_schedule".
	OrchestratorSchedule RunnerConfig
	// AuditRetention controls the audit-log retention purge tick (autonomy
	// roadmap §11). Registered via RunnerFns key "audit_retention".
	AuditRetention RunnerConfig
}
