// Package config 提供基于 viper 的应用配置加载，按 默认值 → 配置文件 → 环境变量 的优先级合并。
package config

import (
	"encoding/hex"
	"fmt"
	"log/slog"
	"strings"
	"time"

	"github.com/spf13/viper"
)

// MasterKey wraps a 32-byte AES master key with redacted formatting to prevent
// accidental log/fmt exposure.
type MasterKey []byte

// String redacts when used with %s / fmt.Println.
func (k MasterKey) String() string { return "[REDACTED 32B]" }

// GoString redacts when used with %#v.
func (k MasterKey) GoString() string { return "[REDACTED 32B]" }

// MarshalJSON redacts when serialized to JSON.
func (k MasterKey) MarshalJSON() ([]byte, error) { return []byte(`"[REDACTED]"`), nil }

// LogValue redacts when logged via slog.
func (k MasterKey) LogValue() slog.Value { return slog.StringValue("[REDACTED 32B]") }

// Config 是应用全局配置的根结构。
type Config struct {
	Env          string             `mapstructure:"env"`
	DataDir      string             `mapstructure:"data_dir"`
	MasterKey    MasterKey          `mapstructure:"-"` // 解码后的 32 字节，单独从 master_key 解析
	HTTP         HTTPConfig         `mapstructure:"http"`
	DB           DBConfig           `mapstructure:"db"`
	Bootstrap    BootstrapConfig    `mapstructure:"bootstrap"`
	Workspace    Workspace          `mapstructure:"workspace"`
	Git          Git                `mapstructure:"git"`
	Chat         Chat               `mapstructure:"chat"`
	Storage      Storage            `mapstructure:"storage"`
	Jobs         JobsConfig         `mapstructure:"jobs"`
	Notify       NotifyConfig       `mapstructure:"notify"`
	Acp          AcpConfig          `mapstructure:"acp"`
	MCP          MCPConfig          `mapstructure:"mcp"`
	Run          RunConfig          `mapstructure:"run"`
	Terminal     TerminalConfig     `mapstructure:"terminal"`
	VCS          VCSConfig          `mapstructure:"vcs"`
	Orchestrator OrchestratorConfig `mapstructure:"orchestrator"`
	Crypto       CryptoConfig       `mapstructure:"crypto"`
	User         UserConfig         `mapstructure:"user"`
	CodeIndex    CodeIndexConfig    `mapstructure:"code_index"`
	Docs         DocsConfig         `mapstructure:"docs"`
	Metrics      MetricsConfig      `mapstructure:"metrics"`
}

// MetricsConfig 控制 Prometheus /metrics 端点。Enabled=false 时不挂载该路由。
// AuthToken 非空时要求 Bearer/?token 匹配，避免成本/会话数对未授权方泄露。
type MetricsConfig struct {
	Enabled   bool   `mapstructure:"enabled"`
	AuthToken string `mapstructure:"auth_token"`
}

// CodeIndexConfig controls the pgvector code index + project memory embeddings.
// EmbeddingEndpointID/EmbeddingModel select an OpenAI-compatible or Gemini
// endpoint for embeddings (Anthropic has no embeddings API). When the endpoint
// is empty, the feature degrades: search_code is disabled and memory_search
// falls back to keyword/tag matching.
type CodeIndexConfig struct {
	Enabled             bool   `mapstructure:"enabled"`
	EmbeddingEndpointID string `mapstructure:"embedding_endpoint_id"` // llm_endpoints.id (UUID); empty disables embeddings
	EmbeddingModel      string `mapstructure:"embedding_model"`       // model id producing PlatformEmbeddingDims (1536) vectors
	ChunkWindowLines    int    `mapstructure:"chunk_window_lines"`
	ChunkOverlapLines   int    `mapstructure:"chunk_overlap_lines"`
	MaxFileBytes        int    `mapstructure:"max_file_bytes"`
	SearchTopKCap       int    `mapstructure:"search_top_k_cap"`
	GrepMaxMatches      int    `mapstructure:"grep_max_matches"`
	GrepMaxFileBytes    int    `mapstructure:"grep_max_file_bytes"`
}

// DocsConfig controls auto doc / PR-summary generation on commit.
type DocsConfig struct {
	Enabled bool `mapstructure:"enabled"`
}

// CryptoConfig 选择 secret 加密的 key provider（env|vault|kms）。key 版本元数据
// 落在 crypto_keys 表；key 材料留在 env/KMS。vault/kms 的客户端尚未接入，选中时
// 退回 env provider（见 app.go），以保持启动不破。
type CryptoConfig struct {
	Provider string `mapstructure:"provider"` // env（默认）| vault | kms
	// Vault/KMS 的连接参数预留位（接入真实客户端时填充）。
	Vault VaultCfg `mapstructure:"vault"`
	KMS   KMSCfg   `mapstructure:"kms"`
}

// VaultCfg 是 HashiCorp Vault transit 的预留配置。
type VaultCfg struct {
	Address string `mapstructure:"address"`
	Token   string `mapstructure:"token"`
	KeyName string `mapstructure:"key_name"`
}

// KMSCfg 是云 KMS 的预留配置。
type KMSCfg struct {
	KeyID  string `mapstructure:"key_id"`
	Region string `mapstructure:"region"`
}

// UserConfig 控制 user 模块：当前仅登录暴力破解节流。
type UserConfig struct {
	LoginThrottle LoginThrottleCfg `mapstructure:"login_throttle"`
}

// LoginThrottleCfg 控制登录失败计数窗口。Enabled=false 时不节流（开发/测试）。
type LoginThrottleCfg struct {
	Enabled     bool          `mapstructure:"enabled"`
	MaxFailures int           `mapstructure:"max_failures"`
	Window      time.Duration `mapstructure:"window"`
}

// OrchestratorConfig 控制编排器模块：开关、每阶段最大尝试、回合超时、子任务深度与
// fan-out 上限。每阶段默认 agent-kind 由 PhaseAgentKinds（agent-kind 名→阶段）映射，
// app 层按名解析为 UUID 注入。
type OrchestratorConfig struct {
	Enabled             bool          `mapstructure:"enabled"`
	MaxAttemptsPerPhase int           `mapstructure:"max_attempts_per_phase"`
	TurnTimeout         time.Duration `mapstructure:"turn_timeout"`
	MaxDepth            int           `mapstructure:"max_depth"`
	FanOutLimit         int           `mapstructure:"fan_out_limit"`
	// PhaseAgentKinds 把阶段名映射到 agent-kind 名（如 planning→"claude"）。app 层在
	// 装配时按名解析为 agent_kind_id，作为 run.config 未覆盖时的默认值。空 map 表示无
	// 默认：每个 run 必须在 config.PhaseAgentKinds 显式提供。
	PhaseAgentKinds map[string]string `mapstructure:"phase_agent_kinds"`
	// Scheduler 控制任务分解的并行调度器（autonomy roadmap §10）。
	Scheduler SchedulerConfig `mapstructure:"scheduler"`
}

// SchedulerConfig 控制任务分解并行调度器：拓扑选取就绪子任务、扇出到并行 ACP session。
type SchedulerConfig struct {
	Enabled  bool          `mapstructure:"enabled"`
	Interval time.Duration `mapstructure:"interval"`
	// MaxFanoutPerTick 每个 project 每 tick 最多派发的 session 数（<=0 默认 8）。
	MaxFanoutPerTick int `mapstructure:"max_fanout_per_tick"`
	// MaxConcurrentPerProject project 内活跃 session 软上限（<=0 不预检，仅靠 acp 层硬上限）。
	MaxConcurrentPerProject int `mapstructure:"max_concurrent_per_project"`
}

// HTTPConfig 描述 HTTP 服务监听参数及可选的 TLS / CORS / 安全头中间件。
type HTTPConfig struct {
	Addr     string         `mapstructure:"addr"`
	TLS      TLSConfig      `mapstructure:"tls"`
	CORS     CORSCfg        `mapstructure:"cors"`
	Security SecurityHdrCfg `mapstructure:"security"`
}

// TLSConfig 控制是否以 HTTPS 提供服务。Enabled=true 时 cmd/server 用
// ListenAndServeTLS，读取 CertFile/KeyFile。
type TLSConfig struct {
	Enabled  bool   `mapstructure:"enabled"`
	CertFile string `mapstructure:"cert_file"`
	KeyFile  string `mapstructure:"key_file"`
}

// CORSCfg 控制跨域中间件。默认 off，避免误配破坏 SPA + WS 握手。
type CORSCfg struct {
	Enabled          bool     `mapstructure:"enabled"`
	AllowedOrigins   []string `mapstructure:"allowed_origins"`
	AllowedMethods   []string `mapstructure:"allowed_methods"`
	AllowedHeaders   []string `mapstructure:"allowed_headers"`
	AllowCredentials bool     `mapstructure:"allow_credentials"`
	MaxAgeSeconds    int      `mapstructure:"max_age_seconds"`
}

// SecurityHdrCfg 控制安全响应头中间件。HSTS 仅在 TLS 启用时下发（由 app 层
// 据 TLS.Enabled 传入）。
type SecurityHdrCfg struct {
	Enabled               bool   `mapstructure:"enabled"`
	HSTSMaxAgeSeconds     int    `mapstructure:"hsts_max_age_seconds"`
	FrameOptions          string `mapstructure:"frame_options"`
	ReferrerPolicy        string `mapstructure:"referrer_policy"`
	ContentSecurityPolicy string `mapstructure:"content_security_policy"`
}

// DBConfig 描述数据库连接参数。
type DBConfig struct {
	DSN string `mapstructure:"dsn"`
}

// BootstrapConfig 描述首次启动注入的管理员账号。
type BootstrapConfig struct {
	AdminEmail    string `mapstructure:"admin_email"`
	AdminPassword string `mapstructure:"admin_password"`
}

// Workspace 控制 workspace 模块的磁盘与超时（数据目录、每个 git verb 的超时）。
// DataDir 是 workspace clone/worktree 落盘根目录；五个 timeout 直接灌进 git.Config。
type Workspace struct {
	DataDir      string        `mapstructure:"data_dir"`
	CloneTimeout time.Duration `mapstructure:"clone_timeout"`
	FetchTimeout time.Duration `mapstructure:"fetch_timeout"`
	PushTimeout  time.Duration `mapstructure:"push_timeout"`
	OpsTimeout   time.Duration `mapstructure:"ops_timeout"`
}

// Git 控制 git CLI 二进制位置（默认 "git"）。
type Git struct {
	Binary string `mapstructure:"binary"`
}

// VCSConfig 配置 git-host（VCS）REST 客户端与合并网关策略。v1 仅支持 Gitea。
// RequireCIPass 默认 false：未接 CI 的仓库不会因 ci_state=unknown 永久卡住合并。
type VCSConfig struct {
	Gitea         GiteaConfig `mapstructure:"gitea"`
	RequireCIPass bool        `mapstructure:"require_ci_pass"`
}

// GiteaConfig 是 Gitea provider 的参数。Token 是平台级 PAT（建议组织/管理员
// token），BaseURL 默认指向自托管实例 git.jerryyan.net。
type GiteaConfig struct {
	BaseURL     string        `mapstructure:"base_url"`
	Token       string        `mapstructure:"token"`
	Timeout     time.Duration `mapstructure:"timeout"`
	MergeMethod string        `mapstructure:"merge_method"`
}

// Chat 配置 chat 模块的附件、流式、历史与定时清理策略。
type Chat struct {
	Attachment ChatAttachment `mapstructure:"attachment"`
	Stream     ChatStream     `mapstructure:"stream"`
	History    ChatHistory    `mapstructure:"history"`
}

// ChatAttachment 控制单文件 / 单消息上限与允许的 MIME 白名单。
type ChatAttachment struct {
	MaxFileSizeMB    int64    `mapstructure:"max_file_size_mb"`
	MaxMessageSizeMB int64    `mapstructure:"max_message_size_mb"`
	MimeWhitelist    []string `mapstructure:"mime_whitelist"`
}

// ChatStream 控制 SSE ring buffer 保留时长与取消宽限期。
type ChatStream struct {
	RingBufferRetentionSeconds int `mapstructure:"ring_buffer_retention_seconds"`
}

// ChatHistory 控制 token 预算的安全余量百分比（如 5 表示保留 5% 余量）。
type ChatHistory struct {
	SafetyMarginPct float64 `mapstructure:"safety_margin_pct"`
}

// Storage 选择附件落盘驱动与对应参数。
type Storage struct {
	Driver  string         `mapstructure:"driver"` // 当前仅支持 "localfs"
	LocalFS StorageLocalFS `mapstructure:"localfs"`
}

// StorageLocalFS 是 driver=localfs 时的根目录。
type StorageLocalFS struct {
	BasePath string `mapstructure:"base_path"`
}

// JobsConfig 控制后台 job runner 的总开关、worker 池与各定时任务参数。
type JobsConfig struct {
	Enabled           bool                 `mapstructure:"enabled"`
	Workers           int                  `mapstructure:"workers"`
	ShutdownGrace     time.Duration        `mapstructure:"shutdown_grace"`
	WorkspaceFetch    RunnerCfg            `mapstructure:"workspace_fetch"`
	WorktreePrune     WorktreePruneCfg     `mapstructure:"worktree_prune"`
	AttachmentCleanup AttachmentCleanupCfg `mapstructure:"attachment_cleanup"`
	JobReaper         JobReaperCfg         `mapstructure:"job_reaper"`
	JobPurge          JobPurgeCfg          `mapstructure:"job_purge"`
	AcpEventsPurge    AcpEventsPurgeCfg    `mapstructure:"acp_events_purge"`
	MCPTokensPurge    MCPTokensPurgeCfg    `mapstructure:"mcp_tokens_purge"`
	AcpSessionReaper  AcpSessionReaperCfg  `mapstructure:"acp_session_reaper"`
	AuditRetention    AuditRetentionCfg    `mapstructure:"audit_retention"`
}

// AuditRetentionCfg 控制审计日志保留期清理：删除早于 Retention 的 audit_logs 行。
type AuditRetentionCfg struct {
	Enabled   bool          `mapstructure:"enabled"`
	Interval  time.Duration `mapstructure:"interval"`
	Retention time.Duration `mapstructure:"retention"`
}

type RunnerCfg struct {
	Enabled  bool          `mapstructure:"enabled"`
	Interval time.Duration `mapstructure:"interval"`
}

type WorktreePruneCfg struct {
	Enabled       bool          `mapstructure:"enabled"`
	Interval      time.Duration `mapstructure:"interval"`
	IdleThreshold time.Duration `mapstructure:"idle_threshold"`
	WarningLead   time.Duration `mapstructure:"warning_lead"`
}

type AttachmentCleanupCfg struct {
	Enabled   bool          `mapstructure:"enabled"`
	Interval  time.Duration `mapstructure:"interval"`
	Retention time.Duration `mapstructure:"retention"`
}

type JobReaperCfg struct {
	Enabled      bool          `mapstructure:"enabled"`
	Interval     time.Duration `mapstructure:"interval"`
	LeaseTimeout time.Duration `mapstructure:"lease_timeout"`
}

type JobPurgeCfg struct {
	Enabled            bool          `mapstructure:"enabled"`
	Interval           time.Duration `mapstructure:"interval"`
	CompletedRetention time.Duration `mapstructure:"completed_retention"`
}

type AcpEventsPurgeCfg struct {
	Enabled   bool          `mapstructure:"enabled"`
	Interval  time.Duration `mapstructure:"interval"`
	Retention time.Duration `mapstructure:"retention"`
}

type MCPTokensPurgeCfg struct {
	Enabled   bool          `mapstructure:"enabled"`
	Interval  time.Duration `mapstructure:"interval"`
	Retention time.Duration `mapstructure:"retention"`
}

// AcpSessionReaperCfg 控制 ACP 会话回收器：墙钟超时 + 空闲超时终止。
type AcpSessionReaperCfg struct {
	Enabled          bool          `mapstructure:"enabled"`
	Interval         time.Duration `mapstructure:"interval"`
	WallClockTimeout time.Duration `mapstructure:"wall_clock_timeout"`
	IdleTimeout      time.Duration `mapstructure:"idle_timeout"`
}

// NotifyConfig 是 notify 模块的可选外发通道配置：webhook（jobs 异步投递）、
// email（SMTP）、im（per-user 加密 webhook）。email/im 默认关闭，未配置时
// 对应 Notifier 不注册到 Dispatcher，行为与历史一致。
type NotifyConfig struct {
	Webhook WebhookCfg `mapstructure:"webhook"`
	Email   EmailCfg   `mapstructure:"email"`
	IM      IMCfg      `mapstructure:"im"`
}

// WebhookCfg 是 webhook 通道的配置。enabled=false 时上层 dispatcher 仍会丢弃所有 webhook 消息。
type WebhookCfg struct {
	Enabled bool          `mapstructure:"enabled"`
	URL     string        `mapstructure:"url"`
	Secret  string        `mapstructure:"secret"`
	Timeout time.Duration `mapstructure:"timeout"`
	Topics  []string      `mapstructure:"topics"`
}

// EmailCfg 是 SMTP email 通道的配置。Enabled=false（默认）时不注册 EmailNotifier，
// 不发任何邮件。Username/Password 为空表示无认证 SMTP（如本地中继）。
type EmailCfg struct {
	Enabled  bool   `mapstructure:"enabled"`
	SMTPHost string `mapstructure:"smtp_host"`
	Port     int    `mapstructure:"port"`
	From     string `mapstructure:"from"`
	Username string `mapstructure:"username"`
	Password string `mapstructure:"password"`
}

// IMCfg 是 IM（通用 webhook，如 Slack incoming webhook）通道的配置。
// Enabled=false（默认）时不注册 IMNotifier。投递地址是 per-user 的
// im_webhook_url（加密落库于 notification_prefs），此处仅做全局开关。
type IMCfg struct {
	Enabled bool `mapstructure:"enabled"`
}

// AcpConfig 控制 ACP 模块的并发限流、子进程超时、WS 心跳与事件保留。
type AcpConfig struct {
	Enabled            bool          `mapstructure:"enabled"`
	MaxActiveGlobal    int           `mapstructure:"max_active_global"`
	MaxActivePerUser   int           `mapstructure:"max_active_per_user"`
	SpawnTimeout       time.Duration `mapstructure:"spawn_timeout"`
	KillGrace          time.Duration `mapstructure:"kill_grace"`
	StderrBufferLines  int           `mapstructure:"stderr_buffer_lines"`
	StderrTailBytes    int           `mapstructure:"stderr_tail_bytes"`
	WSPingInterval     time.Duration `mapstructure:"ws_ping_interval"`
	WSPongTimeout      time.Duration `mapstructure:"ws_pong_timeout"`
	WSSendBuffer       int           `mapstructure:"ws_send_buffer"`
	EventMaxPayload    int           `mapstructure:"event_max_payload_bytes"`
	EventTruncateField int           `mapstructure:"event_truncate_field_bytes"`
	EventRetentionDays int           `mapstructure:"event_retention_days"`
	ShutdownGrace      time.Duration `mapstructure:"shutdown_grace"`
	PermissionTimeout  time.Duration `mapstructure:"permission_timeout"`
	// BriefTokenBudget 是服务端组装 ACP session 初始 prompt 简报的 token 硬上限。
	BriefTokenBudget int `mapstructure:"brief_token_budget"`
	// DefaultProjectBudgetUSD 是 per-project ACP 累计花费的全局软上限（0 = 不限）。
	// 当 session/agent-kind 均未设置成本上限时作为预算判定基线。
	DefaultProjectBudgetUSD float64 `mapstructure:"default_project_budget_usd"`
	// Sandbox 控制 per-session 执行沙箱（none|uid|container）。默认 none：
	// Windows 开发 / CI / 任何非 Linux 机器都跑 none，行为与历史一致。
	Sandbox SandboxCfg `mapstructure:"sandbox"`
}

// SandboxCfg 控制 ACP 子进程的 per-session 隔离。mode=none 时全部字段被忽略。
type SandboxCfg struct {
	Mode    string `mapstructure:"mode"`     // none（默认）| uid | container
	BaseUID int    `mapstructure:"base_uid"` // 低权 UID 基址；per-session uid = base + offset
	// ProxyURL 注入子进程 env 的 HTTP(S)_PROXY（egress 白名单代理）；空 = 不限制。
	ProxyURL string `mapstructure:"proxy_url"`
	// 资源上限（OS rlimit / cgroup）。0 = 不设该项。
	AddressSpaceBytes uint64 `mapstructure:"address_space_bytes"`
	NProc             uint64 `mapstructure:"nproc"`
	CPUSeconds        uint64 `mapstructure:"cpu_seconds"`
	PIDs              uint64 `mapstructure:"pids"`
	DiskBytes         uint64 `mapstructure:"disk_bytes"`
}

// RunConfig 控制 workspace run profiles 模块：进程托管的终止宽限、日志环形缓冲
// 容量、WS 发送缓冲、关停宽限，以及传递给被托管命令的宿主环境变量白名单。
type RunConfig struct {
	Enabled        bool          `mapstructure:"enabled"`
	KillGrace      time.Duration `mapstructure:"kill_grace"`
	OutBufferLines int           `mapstructure:"out_buffer_lines"`
	OutTailBytes   int           `mapstructure:"out_tail_bytes"`
	WSSendBuffer   int           `mapstructure:"ws_send_buffer"`
	ShutdownGrace  time.Duration `mapstructure:"shutdown_grace"`
	// EnvWhitelist 是允许从宿主进程透传给被托管命令的环境变量名白名单。
	// APP_MASTER_KEY、DB DSN、git 凭据等敏感变量绝不应出现在此名单中。
	EnvWhitelist []string `mapstructure:"env_whitelist"`
	// Exec 控制一次性 exec 原语（run_command / run_tests MCP 工具）。
	Exec ExecConfig `mapstructure:"exec"`
}

// ExecConfig 控制 agent 自验证用的一次性 exec（run_command / run_tests）：
// 默认 / 最大超时、输出捕获上限，以及命令二进制白名单。AllowedCommands 为空表示
// 显式不限制命令；默认配置保持非空，生产环境不应放空。
type ExecConfig struct {
	DefaultTimeout time.Duration `mapstructure:"default_timeout"`
	MaxTimeout     time.Duration `mapstructure:"max_timeout"`
	MaxOutputBytes int           `mapstructure:"max_output_bytes"`
	// AllowedCommands 是允许执行的命令 base name 白名单（如 go / npm / pnpm）。
	// 为空时不做命令限制（仍受 env 白名单、worktree 路径、超时与树终止约束）。
	AllowedCommands []string `mapstructure:"allowed_commands"`
}

// TerminalConfig 控制仅限管理员的网页端终端模块：开关、启动的 shell、并发会话上限、
// 整组终止宽限，以及透传给 shell 的宿主环境变量白名单（沿用 RunConfig 的安全取舍）。
type TerminalConfig struct {
	Enabled     bool          `mapstructure:"enabled"`
	Shell       string        `mapstructure:"shell"`
	MaxSessions int           `mapstructure:"max_sessions"`
	KillGrace   time.Duration `mapstructure:"kill_grace"`
	// EnvWhitelist 是允许透传给交互式 shell 的环境变量名白名单。
	// APP_MASTER_KEY、DB DSN、git 凭据等敏感变量绝不应出现在此名单中。
	EnvWhitelist []string `mapstructure:"env_whitelist"`
}

// MCPConfig 控制 MCP 模块的开关、对外 URL（注入 ACP 子进程 env）、system token TTL 与速率限制。
type MCPConfig struct {
	Enabled        bool            `mapstructure:"enabled"`
	PublicURL      string          `mapstructure:"public_url"`
	SystemTokenTTL time.Duration   `mapstructure:"system_token_ttl"`
	RateLimit      MCPRateLimitCfg `mapstructure:"rate_limit"`
}

// MCPRateLimitCfg 控制 token bucket 容量。两层独立桶：per-token + 全局。
type MCPRateLimitCfg struct {
	PerTokenPerMinute int `mapstructure:"per_token_per_minute"`
	GlobalPerMinute   int `mapstructure:"global_per_minute"`
}

// Load 从（按优先级递增）默认值 → 配置文件（如有）→ 环境变量 加载配置。
// configFile 可为空字符串，仅用环境变量。
func Load(configFile string) (*Config, error) {
	v := viper.New()

	// 默认值
	v.SetDefault("env", "development")
	v.SetDefault("data_dir", "./data")
	v.SetDefault("http.addr", ":8080")
	v.SetDefault("workspace.data_dir", "./data")
	v.SetDefault("workspace.clone_timeout", "30m")
	v.SetDefault("workspace.fetch_timeout", "5m")
	v.SetDefault("workspace.push_timeout", "5m")
	v.SetDefault("workspace.ops_timeout", "60s")
	v.SetDefault("git.binary", "git")
	v.SetDefault("vcs.gitea.base_url", "https://git.jerryyan.net")
	v.SetDefault("vcs.gitea.timeout", "15s")
	v.SetDefault("vcs.gitea.merge_method", "merge")
	v.SetDefault("vcs.require_ci_pass", false)
	v.SetDefault("chat.attachment.max_file_size_mb", 20)
	v.SetDefault("chat.attachment.max_message_size_mb", 50)
	v.SetDefault("chat.attachment.mime_whitelist", []string{
		"image/png", "image/jpeg", "image/webp", "image/gif",
		"application/pdf", "text/*",
	})
	v.SetDefault("chat.stream.ring_buffer_retention_seconds", 300)
	v.SetDefault("chat.history.safety_margin_pct", 5.0)
	v.SetDefault("storage.driver", "localfs")
	v.SetDefault("storage.localfs.base_path", "./data/uploads")
	v.SetDefault("jobs.enabled", true)
	v.SetDefault("jobs.workers", 2)
	v.SetDefault("jobs.shutdown_grace", "30s")
	v.SetDefault("jobs.workspace_fetch.enabled", true)
	v.SetDefault("jobs.workspace_fetch.interval", "5m")
	v.SetDefault("jobs.worktree_prune.enabled", true)
	v.SetDefault("jobs.worktree_prune.interval", "24h")
	v.SetDefault("jobs.worktree_prune.idle_threshold", "720h")
	v.SetDefault("jobs.worktree_prune.warning_lead", "72h")
	v.SetDefault("jobs.attachment_cleanup.enabled", true)
	v.SetDefault("jobs.attachment_cleanup.interval", "24h")
	v.SetDefault("jobs.attachment_cleanup.retention", "720h")
	v.SetDefault("jobs.job_reaper.enabled", true)
	v.SetDefault("jobs.job_reaper.interval", "1m")
	// 15m：必须大于 orchestrator.turn_timeout(10m) 才能容纳一次完整的 agent 回合——
	// jobs handler 超时 = lease_timeout*9/10 = 13.5m > 10m。否则编排器步骤会被中途 reap
	// 并重复执行（见 app.go 启动期不变量校验）。
	v.SetDefault("jobs.job_reaper.lease_timeout", "15m")
	v.SetDefault("jobs.job_purge.enabled", true)
	v.SetDefault("jobs.job_purge.interval", "24h")
	v.SetDefault("jobs.job_purge.completed_retention", "168h")
	v.SetDefault("jobs.acp_events_purge.enabled", true)
	v.SetDefault("jobs.acp_events_purge.interval", "24h")
	v.SetDefault("jobs.acp_events_purge.retention", "168h")
	v.SetDefault("jobs.mcp_tokens_purge.enabled", true)
	v.SetDefault("jobs.mcp_tokens_purge.interval", "24h")
	v.SetDefault("jobs.mcp_tokens_purge.retention", "168h")
	v.SetDefault("jobs.acp_session_reaper.enabled", true)
	v.SetDefault("jobs.acp_session_reaper.interval", "1m")
	v.SetDefault("jobs.acp_session_reaper.wall_clock_timeout", "4h")
	v.SetDefault("jobs.acp_session_reaper.idle_timeout", "30m")
	// 审计日志保留期清理：默认 90 天，每日扫描一次。
	v.SetDefault("jobs.audit_retention.enabled", true)
	v.SetDefault("jobs.audit_retention.interval", "24h")
	v.SetDefault("jobs.audit_retention.retention", "2160h")
	// Prometheus /metrics：默认开启（绑定内网 / 反代鉴权场景留空 token）。
	v.SetDefault("metrics.enabled", true)
	v.SetDefault("metrics.auth_token", "")
	v.SetDefault("notify.webhook.enabled", false)
	v.SetDefault("notify.webhook.timeout", "10s")
	// 默认转发的 webhook topic：phase 流转事件供下游自动化（如触发 ACP run）订阅。
	v.SetDefault("notify.webhook.topics", []string{"requirement.phase_change"})
	// email / im 通道默认关闭，未显式启用时不注册对应 Notifier。
	v.SetDefault("notify.email.enabled", false)
	v.SetDefault("notify.email.port", 587)
	v.SetDefault("notify.im.enabled", false)
	v.SetDefault("acp.enabled", true)
	v.SetDefault("acp.max_active_global", 20)
	v.SetDefault("acp.max_active_per_user", 3)
	v.SetDefault("acp.spawn_timeout", "30s")
	v.SetDefault("acp.kill_grace", "5s")
	v.SetDefault("acp.stderr_buffer_lines", 100)
	v.SetDefault("acp.stderr_tail_bytes", 2000)
	v.SetDefault("acp.ws_ping_interval", "30s")
	v.SetDefault("acp.ws_pong_timeout", "60s")
	v.SetDefault("acp.ws_send_buffer", 256)
	v.SetDefault("acp.event_max_payload_bytes", 65536)
	v.SetDefault("acp.event_truncate_field_bytes", 4096)
	v.SetDefault("acp.event_retention_days", 7)
	v.SetDefault("acp.shutdown_grace", "30s")
	v.SetDefault("acp.permission_timeout", "5m")
	v.SetDefault("acp.brief_token_budget", 6000)
	v.SetDefault("acp.default_project_budget_usd", 0)
	// 沙箱默认 none：Windows 开发 / CI / 非 Linux 机器均不隔离，行为与历史一致。
	v.SetDefault("acp.sandbox.mode", "none")
	v.SetDefault("acp.sandbox.base_uid", 0)
	v.SetDefault("acp.sandbox.proxy_url", "")
	v.SetDefault("acp.sandbox.address_space_bytes", 0)
	v.SetDefault("acp.sandbox.nproc", 0)
	v.SetDefault("acp.sandbox.cpu_seconds", 0)
	v.SetDefault("acp.sandbox.pids", 0)
	v.SetDefault("acp.sandbox.disk_bytes", 0)
	// secret key provider：默认 env（沿用 APP_MASTER_KEY 为 version 1）。
	v.SetDefault("crypto.provider", "env")
	// 登录暴力破解节流：默认开启，15 分钟窗口内 10 次失败即 429。
	v.SetDefault("user.login_throttle.enabled", true)
	v.SetDefault("user.login_throttle.max_failures", 10)
	v.SetDefault("user.login_throttle.window", "15m")
	// HTTP TLS / CORS / 安全头：默认 off（HTTP，无 CORS）；安全头默认开启（透传安全）。
	v.SetDefault("http.tls.enabled", false)
	v.SetDefault("http.cors.enabled", false)
	v.SetDefault("http.cors.allow_credentials", true)
	v.SetDefault("http.cors.max_age_seconds", 600)
	v.SetDefault("http.security.enabled", true)
	v.SetDefault("http.security.frame_options", "DENY")
	v.SetDefault("http.security.referrer_policy", "no-referrer")
	v.SetDefault("orchestrator.enabled", true)
	v.SetDefault("orchestrator.max_attempts_per_phase", 3)
	v.SetDefault("orchestrator.turn_timeout", "10m")
	v.SetDefault("orchestrator.max_depth", 3)
	v.SetDefault("orchestrator.fan_out_limit", 3)
	v.SetDefault("orchestrator.phase_agent_kinds", map[string]string{})
	// 任务分解并行调度器：默认关闭（须显式开启，配合 jobs 周期 RunnerFn 驱动）。
	v.SetDefault("orchestrator.scheduler.enabled", false)
	v.SetDefault("orchestrator.scheduler.interval", "30s")
	v.SetDefault("orchestrator.scheduler.max_fanout_per_tick", 8)
	v.SetDefault("orchestrator.scheduler.max_concurrent_per_project", 4)
	v.SetDefault("mcp.enabled", true)
	v.SetDefault("mcp.public_url", "http://localhost:8080/mcp")
	v.SetDefault("mcp.system_token_ttl", "24h")
	v.SetDefault("mcp.rate_limit.per_token_per_minute", 100)
	v.SetDefault("mcp.rate_limit.global_per_minute", 1000)
	v.SetDefault("run.enabled", true)
	v.SetDefault("run.kill_grace", "5s")
	v.SetDefault("run.out_buffer_lines", 1000)
	v.SetDefault("run.out_tail_bytes", 2000)
	v.SetDefault("run.ws_send_buffer", 256)
	v.SetDefault("run.shutdown_grace", "30s")
	v.SetDefault("run.env_whitelist", []string{
		"PATH", "HOME", "LANG", "LC_ALL", "TZ", "USER", "SHELL",
		"SystemRoot", "TEMP", "TMP", "USERPROFILE", "APPDATA", "LOCALAPPDATA",
		"PATHEXT", "ComSpec", "NUMBER_OF_PROCESSORS",
	})
	v.SetDefault("run.exec.default_timeout", "60s")
	v.SetDefault("run.exec.max_timeout", "600s")
	v.SetDefault("run.exec.max_output_bytes", 1<<20)
	v.SetDefault("run.exec.allowed_commands", []string{
		"go", "npm", "pnpm", "yarn", "node",
		"python", "python3", "pytest", "uv",
		"cargo", "rustc", "make", "cmake", "ctest",
		"git",
	})
	v.SetDefault("terminal.enabled", true)
	v.SetDefault("terminal.shell", "/bin/bash") // Windows 开发环境经 APP_TERMINAL_SHELL 覆盖
	v.SetDefault("terminal.max_sessions", 5)
	v.SetDefault("terminal.kill_grace", "5s")
	// 透传 PATH/HOME/TERM + npm 全局安装所需（NPM_CONFIG_PREFIX 指向持久卷），
	// 以便容器内 `npm i -g <acp-cli>` 可用。敏感变量不在名单内。
	v.SetDefault("terminal.env_whitelist", []string{
		"PATH", "HOME", "LANG", "LC_ALL", "TZ", "USER", "SHELL", "TERM",
		"NPM_CONFIG_PREFIX", "NODE_PATH", "NODE_ENV",
		"SystemRoot", "TEMP", "TMP", "USERPROFILE", "APPDATA", "LOCALAPPDATA",
		"PATHEXT", "ComSpec", "NUMBER_OF_PROCESSORS",
	})
	// 代码索引 / 项目记忆：默认开启，但无 embedding endpoint 时自动降级为关键字回退。
	v.SetDefault("code_index.enabled", true)
	v.SetDefault("code_index.embedding_endpoint_id", "")
	v.SetDefault("code_index.embedding_model", "text-embedding-3-small")
	v.SetDefault("code_index.chunk_window_lines", 60)
	v.SetDefault("code_index.chunk_overlap_lines", 10)
	v.SetDefault("code_index.max_file_bytes", 512*1024)
	v.SetDefault("code_index.search_top_k_cap", 50)
	v.SetDefault("code_index.grep_max_matches", 200)
	v.SetDefault("code_index.grep_max_file_bytes", 1<<20)
	// 自动文档/PR 摘要：默认关闭（避免每次提交都调用 LLM 产生成本）。
	v.SetDefault("docs.enabled", false)

	if configFile != "" {
		v.SetConfigFile(configFile)
		if err := v.ReadInConfig(); err != nil {
			return nil, fmt.Errorf("read config file: %w", err)
		}
	}

	v.SetEnvPrefix("APP")
	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	v.AutomaticEnv()

	// 显式绑定无默认值的嵌套键，以便 Unmarshal 能从环境变量取值。
	// （AutomaticEnv 仅对 v.Get* 直接查询生效；Unmarshal 只看 viper 已知的键集合。）
	for _, key := range []string{
		"db.dsn",
		"bootstrap.admin_email",
		"bootstrap.admin_password",
		"master_key",
		"vcs.gitea.token",
	} {
		if err := v.BindEnv(key); err != nil {
			return nil, fmt.Errorf("bind env %s: %w", key, err)
		}
	}

	var cfg Config
	if err := v.Unmarshal(&cfg); err != nil {
		return nil, fmt.Errorf("unmarshal config: %w", err)
	}

	// MasterKey 是 hex 字符串，需要单独解码
	rawKey := v.GetString("master_key")
	if rawKey == "" {
		return nil, fmt.Errorf("APP_MASTER_KEY required")
	}
	key, err := hex.DecodeString(rawKey)
	if err != nil {
		return nil, fmt.Errorf("APP_MASTER_KEY invalid hex: %w", err)
	}
	if len(key) != 32 {
		return nil, fmt.Errorf("APP_MASTER_KEY must be 32 bytes (got %d)", len(key))
	}
	cfg.MasterKey = MasterKey(key)

	if cfg.DB.DSN == "" {
		return nil, fmt.Errorf("APP_DB_DSN required")
	}
	return &cfg, nil
}
