package domain

import (
	"errors"
	"time"
)

const (
	StatusPending   = "pending"
	StatusQueued    = "queued"
	StatusRunning   = "running"
	StatusSucceeded = "succeeded"
	StatusFailed    = "failed"
	StatusCancelled = "cancelled"

	TemplateDemo                  = "demo"
	TemplateThreadsTokenRenew     = "threads_token_renew"
	TemplatePersonaAnalyzeAccount = "persona_analyze_account"
	TemplatePersonaAnalyzeText    = "persona_analyze_text"
	TemplateComposeMimic          = "compose_mimic"
	// TemplatePlayGenerateScript — 互回／串場：一次產完整劇本（背景 job，避免 HTTP 卡住）
	TemplatePlayGenerateScript = "play_generate_script"
	TemplateScoutScan          = "scout_scan"
	// TemplateRadarSweep — 雷達每日巡（每 active watch 一筆；手動觸發同 template）
	TemplateRadarSweep = "radar_sweep"
)

// Job 生命週期契約（所有模板必須遵守，worker / API 入列時）：
//  1. 建立／入列 → StatusQueued + notify（鈴鐺「已排程」）
//  2. ClaimNext → StatusRunning + notify（至少 ~5%）
//  3. 執行中每有進度 → MarkRunningProgress → notify（同一 job upsert 一則通知）
//  4. 終態 SucceedJob / FailJob → notify（已完成／失敗）
// 前端靠全域 JobLive 輪詢列表 + 通知；遠期 run_after 不進頂欄即時條。

var (
	ErrNotFound      = errors.New("job not found")
	ErrForbidden     = errors.New("job access denied")
	ErrIllegalStatus = errors.New("illegal job status transition")
	// ErrTooManyActive — owner already has a CPU-heavy job (e.g. persona account
	// scrape) in flight; caller should retry once it finishes.
	ErrTooManyActive = errors.New("too many active jobs")
)

type Job struct {
	ID              string `bson:"_id" json:"id"`
	OwnerUID        int64  `bson:"owner_uid" json:"owner_uid"`
	TemplateType    string `bson:"template_type" json:"template_type"`
	Status          string `bson:"status" json:"status"`
	ProgressSummary string `bson:"progress_summary" json:"progress_summary"`
	ProgressPercent int    `bson:"progress_percent" json:"progress_percent"`
	Error           string `bson:"error,omitempty" json:"error,omitempty"`
	WorkerID        string `bson:"worker_id,omitempty" json:"worker_id,omitempty"`
	LeaseOwner      string `bson:"lease_owner,omitempty" json:"lease_owner,omitempty"`
	LeaseExpiresAt  int64  `bson:"lease_expires_at,omitempty" json:"lease_expires_at,omitempty"`
	Attempt         int    `bson:"attempt,omitempty" json:"attempt,omitempty"`
	// RefID e.g. threads account id for renew / persona id for analyze
	RefID string `bson:"ref_id,omitempty" json:"ref_id,omitempty"`
	// Payload JSON body for job worker (e.g. {"username":"x"})
	Payload string `bson:"payload,omitempty" json:"payload,omitempty"`
	// RunAfter unix nanoseconds; worker only claims when now >= RunAfter (0 = due now)
	RunAfter    int64 `bson:"run_after,omitempty" json:"run_after,omitempty"`
	CreatedAt   int64 `bson:"created_at" json:"created_at"`
	UpdatedAt   int64 `bson:"updated_at" json:"updated_at"`
	CompletedAt int64 `bson:"completed_at,omitempty" json:"completed_at,omitempty"`
	// Version is incremented by every persisted mutation and makes lifecycle
	// writes compare-and-swap safe across gateway and worker instances.
	Version int64 `bson:"version" json:"version"`
}

// DefaultTokenRenewDelayNs — 連帳後約 30 天做第一次 renew（長效 token ~60 天）
const DefaultTokenRenewDelayNs = int64(30 * 24 * time.Hour)

// Job leases are renewed by the claiming service while work is running.
const (
	DefaultLeaseDuration   = time.Minute
	DefaultLeaseDurationNs = int64(DefaultLeaseDuration)
	DefaultHeartbeat       = 20 * time.Second
)

// TerminalRetentionNs — 終態任務（已完成／失敗／取消）保留多久後自動清除
const TerminalRetentionNs = int64(2 * 24 * time.Hour)

func NowNano() int64 { return time.Now().UTC().UnixNano() }

// IsTerminal — 不可再轉移的狀態
func IsTerminal(status string) bool {
	return status == StatusSucceeded || status == StatusFailed || status == StatusCancelled
}

// TerminalCutoff — 早於此時間的終態任務應被 purge
func TerminalCutoff(now int64) int64 {
	if now <= 0 {
		now = NowNano()
	}
	return now - TerminalRetentionNs
}

// CanTransition enforces JB-10 guarded status.
func CanTransition(from, to string) bool {
	switch from {
	case StatusPending:
		return to == StatusQueued || to == StatusRunning || to == StatusFailed
	case StatusQueued:
		return to == StatusRunning || to == StatusFailed || to == StatusCancelled
	case StatusRunning:
		return to == StatusSucceeded || to == StatusFailed || to == StatusCancelled
	default:
		return false // terminal
	}
}

// ApplyProgress enforces monotonic percent (JB-02).
func ApplyProgress(j *Job, percent int, summary string) error {
	if percent < j.ProgressPercent {
		return ErrIllegalStatus
	}
	if percent > 100 {
		percent = 100
	}
	j.ProgressPercent = percent
	if summary != "" {
		j.ProgressSummary = summary
	}
	j.UpdatedAt = NowNano()
	return nil
}

// List tab buckets（任務列表頁籤）
const (
	TabRecurring = "recurring" // 遠期定期／已排程未到期
	TabActive    = "active"    // 執行中、待領取（已到期）、取消中
	TabHistory   = "history"   // 已完成／失敗／取消
)

// NormalizeListTab — 空或未知 → active
func NormalizeListTab(tab string) string {
	switch tab {
	case TabRecurring, TabActive, TabHistory:
		return tab
	default:
		return TabActive
	}
}

// MatchListTab — 依狀態＋run_after 分桶（與 FE 一致）
func MatchListTab(j *Job, tab string, nowNs int64) bool {
	if j == nil {
		return false
	}
	if nowNs <= 0 {
		nowNs = NowNano()
	}
	switch NormalizeListTab(tab) {
	case TabHistory:
		return IsTerminal(j.Status)
	case TabRecurring:
		// 遠期排程：pending/queued 且 run_after 尚未到
		if j.Status != StatusPending && j.Status != StatusQueued {
			return false
		}
		return j.RunAfter > 0 && j.RunAfter > nowNs
	default: // active
		if j.Status == StatusRunning {
			return true
		}
		if j.Status == StatusPending || j.Status == StatusQueued {
			// 已到期或立刻可領
			return j.RunAfter <= 0 || j.RunAfter <= nowNs
		}
		return false
	}
}

// ClampPage — page 從 1；pageSize 預設 10、上限 50
func ClampPage(page, pageSize int) (int, int) {
	if page < 1 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 10
	}
	if pageSize > 50 {
		pageSize = 50
	}
	return page, pageSize
}
