// Package acp 实现 ACP（Agent Client Protocol）Client 模块。
// 模块结构（spec §13 决策 #12）：单 package + 多文件，与现有 chat/jobs/workspace 一致。
//
// 三个聚合根：
//   - AgentKind：admin 注册的 agent 类型（claude_code/gemini/...），含加密 env
//   - Session：用户启动的一个 ACP 会话，绑定 workspace + agent kind + (issue/req?)
//   - Event：JSON-RPC 双向消息流，每条入库
package acp

import (
	"context"
	"time"

	"github.com/google/uuid"
)

// SessionStatus 是 acp_sessions.status 枚举。
type SessionStatus string

const (
	SessionStarting SessionStatus = "starting"
	SessionRunning  SessionStatus = "running"
	SessionCrashed  SessionStatus = "crashed"
	SessionExited   SessionStatus = "exited"
)

// IsActive 判定 session 是否仍占资源（worktree、subprocess、限流计数）。
func (s SessionStatus) IsActive() bool {
	return s == SessionStarting || s == SessionRunning
}

// EventDirection 标识事件流向。in = client→agent，out = agent→client。
type EventDirection string

const (
	DirectionIn  EventDirection = "in"
	DirectionOut EventDirection = "out"
)

// RPCKind 标识 JSON-RPC 消息类型。
type RPCKind string

const (
	RPCRequest      RPCKind = "request"
	RPCResponse     RPCKind = "response"
	RPCNotification RPCKind = "notification"
	RPCError        RPCKind = "error"
)

// ClientType 标识 agent CLI 的客户端类型，决定 spawn 时注入哪个配置目录
// 重定向变量（CLAUDE_CONFIG_DIR / CODEX_HOME / GEMINI_CLI_HOME）以及前端
// 渲染哪套配置表单。generic 仅注入 HOME。
type ClientType string

const (
	ClientClaudeCode ClientType = "claude_code"
	ClientCodex      ClientType = "codex"
	ClientGemini     ClientType = "gemini"
	ClientGeneric    ClientType = "generic"
)

// Valid 判定 client_type 是否为已知枚举值。
func (t ClientType) Valid() bool {
	switch t {
	case ClientClaudeCode, ClientCodex, ClientGemini, ClientGeneric:
		return true
	}
	return false
}

// ACW 自家 MCP 注入约定：session 创建时签发的 system token 与 MCP 公网地址
// 经 extraEnv 注入子进程，同时在 session/new 握手时作为 http MCP server 下发。
const (
	EnvMCPToken = "ACW_MCP_TOKEN"
	EnvMCPURL   = "ACW_MCP_URL"
	// AcwMcpServerName 是自家 MCP 在 mcpServers 里的保留名，第三方条目不可占用。
	AcwMcpServerName = "acw"
)

// McpServerType 是第三方 MCP server 的传输类型。stdio 是 ACP 基线（所有 agent
// 必须支持）；http / sse 需 agent 在 initialize 中声明 mcpCapabilities 才下发。
type McpServerType string

const (
	McpStdio McpServerType = "stdio"
	McpHTTP  McpServerType = "http"
	McpSSE   McpServerType = "sse"
)

// McpServerSpec 是 admin 在 AgentKind 上配置的一条 MCP server。session/new
// 握手时转换为 ACP wire 格式随 mcpServers 下发。Env / Headers 可能含密钥，
// 整个列表加密落库（encrypted_mcp_servers）；明文仅返回给 admin 编辑。
type McpServerSpec struct {
	Name    string            `json:"name"`
	Type    McpServerType     `json:"type"`
	Command string            `json:"command,omitempty"` // stdio
	Args    []string          `json:"args,omitempty"`    // stdio
	Env     map[string]string `json:"env,omitempty"`     // stdio
	URL     string            `json:"url,omitempty"`     // http / sse
	Headers map[string]string `json:"headers,omitempty"` // http / sse
}

// AgentKind 是 admin 注册的 agent 类型记录。EncryptedEnv 是 AES-GCM 密文，
// 仅 supervisor spawn 时解密为 map 注入子进程。其他场景不解密、不出 API。
// EncryptedMCPServers 同为密文，但明文经 API 返回给 admin 供编辑。
type AgentKind struct {
	ID                  uuid.UUID
	Name                string
	DisplayName         string
	Description         string
	BinaryPath          string
	Args                []string
	EncryptedEnv        []byte
	Enabled             bool
	ToolAllowlist       []string // 命中的工具调用自动放行；含 "*" 表示放行全部可识别的工具（无法识别名称的调用仍走人工审批）
	ClientType          ClientType
	EncryptedMCPServers []byte
	CreatedBy           uuid.UUID
	CreatedAt           time.Time
	UpdatedAt           time.Time
	// 成本核算：关联的 llm_models 行（价格来源）+ 每 kind 默认预算上限。
	// 全部 NULLABLE：未配置 model 时成本按 0/agent 自报；未配置上限时无限制。
	ModelID             *uuid.UUID
	MaxCostUSD          *float64
	MaxTokens           *int64
	MaxWallClockSeconds *int32
}

// ConfigFile 是 AgentKind 维度的 agent CLI 配置文件（如 .claude/settings.json、
// .codex/config.toml）。RelPath 相对受管 home 目录；EncryptedContent 是 AES-GCM
// 密文（内容可能含 auth token），spawn 前解密物化到受管目录。
type ConfigFile struct {
	ID               uuid.UUID
	AgentKindID      uuid.UUID
	RelPath          string
	EncryptedContent []byte
	UpdatedBy        uuid.UUID
	CreatedAt        time.Time
	UpdatedAt        time.Time
}

// Session 是一次 ACP 会话。AgentSessionID 是 agent 侧的 session 标识（来自
// session/new response），与本表 ID 不同，仅用于 ACP 协议 method 内填充。
type Session struct {
	ID             uuid.UUID
	WorkspaceID    uuid.UUID
	ProjectID      uuid.UUID
	AgentKindID    uuid.UUID
	UserID         uuid.UUID
	IssueID        *uuid.UUID
	RequirementID  *uuid.UUID
	AgentSessionID *string
	Branch         string
	CwdPath        string
	IsMainWorktree bool
	Status         SessionStatus
	PID            *int32
	ExitCode       *int32
	LastError      *string
	StartedAt      time.Time
	EndedAt        *time.Time
	// LastStopReason 是 agent 最近一次回合完成时上报的 stopReason 原始字符串
	// （nil = 尚无完成的回合）。归一化枚举见 NormalizeStopReason，但落库为原值。
	LastStopReason *string
	// OrchestratorStepID 非 nil 时表示该 session 由编排器某个 step 驱动；onExit /
	// 启动 reaper 据此把崩溃 session 映射回 step 并触发再驱动。manual session 为 nil。
	OrchestratorStepID *uuid.UUID
	// 成本核算运行时累加器（落库快照）。
	PromptTokens     int64
	CompletionTokens int64
	ThinkingTokens   int64
	TotalCostUSD     float64
	LastActivityAt   time.Time
	// 创建时快照的有效预算上限（session 覆盖 > agent-kind 默认 > 全局默认）。
	BudgetMaxCostUSD          *float64
	BudgetMaxTokens           *int64
	BudgetMaxWallClockSeconds *int32
	// TerminatedReason 非 nil 时记录会话被强制终止的原因（budget_*/reaper_*/manual）。
	TerminatedReason *string
}

// BudgetCaps 是一次会话的有效预算上限快照。nil 字段 = 该维度无限制。
type BudgetCaps struct {
	MaxCostUSD          *float64
	MaxTokens           *int64
	MaxWallClockSeconds *int32
}

// BreachReason 标识预算被突破的维度，落 acp_sessions.terminated_reason。
type BreachReason string

const (
	BreachCost      BreachReason = "budget_cost"
	BreachTokens    BreachReason = "budget_tokens"
	BreachWallClock BreachReason = "budget_wall_clock"
)

// SessionUsageRecord 是 acp_session_usage 表一行（per-turn 账目，镜像 llm_usage）。
type SessionUsageRecord struct {
	ID               int64
	SessionID        uuid.UUID
	UserID           uuid.UUID
	ProjectID        uuid.UUID
	AgentKindID      uuid.UUID
	ModelID          *uuid.UUID
	PromptTokens     int64
	CompletionTokens int64
	ThinkingTokens   int64
	CostUSD          float64
	SourceEventID    *int64
	CreatedAt        time.Time
}

// AcpUsageUserRow 按用户聚合的 ACP 用量。
type AcpUsageUserRow struct {
	UserID                                         uuid.UUID
	PromptTokens, CompletionTokens, ThinkingTokens int64
	CostUSD                                        float64
}

// AcpUsageModelRow 按模型聚合的 ACP 用量。
type AcpUsageModelRow struct {
	ModelID                                        uuid.UUID
	PromptTokens, CompletionTokens, ThinkingTokens int64
	CostUSD                                        float64
}

// AcpUsageDayRow 按天聚合的 ACP 用量。
type AcpUsageDayRow struct {
	Day                                            time.Time
	PromptTokens, CompletionTokens, ThinkingTokens int64
	CostUSD                                        float64
}

// ===== run-history dashboard 读模型 =====

// DashboardFilter 限定 run-history 汇总的范围。UserID 非 nil 时仅统计该用户的
// 会话（owner scope）；nil 表示全量（admin scope）。ProjectID/RequirementID
// 为可选过滤；From/To 为半开时间窗 [From, To)。
type DashboardFilter struct {
	UserID        *uuid.UUID
	ProjectID     *uuid.UUID
	RequirementID *uuid.UUID
	From          time.Time
	To            time.Time
}

// SessionRollup 是 run-history 汇总：会话计数 + 成功率 + 总成本 + 平均时长。
type SessionRollup struct {
	Total              int64
	Succeeded          int64
	Crashed            int64
	Active             int64
	TotalCostUSD       float64
	TotalTokens        int64
	AvgDurationSeconds float64
}

// SuccessRate 返回 succeeded/total（total=0 时为 0），便于 handler/MCP 直接使用。
func (r SessionRollup) SuccessRate() float64 {
	if r.Total == 0 {
		return 0
	}
	return float64(r.Succeeded) / float64(r.Total)
}

// SessionDayRow 是 run-history 按天的时间序列一行。
type SessionDayRow struct {
	Day          time.Time
	Total        int64
	Succeeded    int64
	Crashed      int64
	TotalCostUSD float64
}

// SessionTimelineBucket 是单会话事件按 (direction, rpc_kind, method) 分桶的一行。
type SessionTimelineBucket struct {
	Direction  string
	RPCKind    string
	Method     string
	EventCount int64
	FirstAt    time.Time
	LastAt     time.Time
}

// ReaperSession 是 reaper 需要的最小 session 投影。
type ReaperSession struct {
	ID                  uuid.UUID
	UserID              uuid.UUID
	StartedAt           time.Time
	LastActivityAt      time.Time
	MaxWallClockSeconds *int32
}

// StopReason 是回合完成的归一化原因枚举。agent 可能上报枚举外的 vendor-specific
// 值；这些值归一化为 StopOther，但原始字符串仍会原样落库（stop_reason /
// last_stop_reason），避免信息丢失。
type StopReason string

const (
	StopEndTurn   StopReason = "end_turn"
	StopMaxTokens StopReason = "max_tokens"
	StopRefusal   StopReason = "refusal"
	StopCancelled StopReason = "cancelled"
	StopOther     StopReason = "other"
)

// NormalizeStopReason 把 agent 上报的原始 stopReason 映射到归一化枚举。
// 未知值（含空串）映射为 StopOther。
func NormalizeStopReason(raw string) StopReason {
	switch StopReason(raw) {
	case StopEndTurn:
		return StopEndTurn
	case StopMaxTokens:
		return StopMaxTokens
	case StopRefusal:
		return StopRefusal
	case StopCancelled:
		return StopCancelled
	default:
		return StopOther
	}
}

// TurnStatus 是 acp_turns.status 枚举。
type TurnStatus string

const (
	TurnInProgress TurnStatus = "in_progress"
	TurnCompleted  TurnStatus = "completed"
	TurnAborted    TurnStatus = "aborted"
)

// Turn 是一次 agent 回合的审计记录。一个 session 内 turn_index 单调递增（0-based）。
// PromptRequestID 是发起该回合的 session/prompt 的 JSON-RPC id（用于响应相关联）。
// StopReason 在完成前为 nil；存储 agent 上报的原始字符串。
type Turn struct {
	ID              int64
	SessionID       uuid.UUID
	TurnIndex       int
	PromptRequestID *string
	Status          TurnStatus
	StopReason      *string
	UpdateCount     int
	StartedAt       time.Time
	CompletedAt     *time.Time
}

// Event 是一条入库的 JSON-RPC 消息。Payload 是 JSON-RPC envelope 完整 JSON
// 字节（不解析为结构体，便于无损 replay）；超过 64KB 的会按 §3.5 规则裁剪。
type Event struct {
	ID          int64
	SessionID   uuid.UUID
	Direction   EventDirection
	RPCKind     RPCKind
	Method      *string
	Payload     []byte
	PayloadSize int32
	Truncated   bool
	CreatedAt   time.Time
}

// Caller 表示发起请求的用户上下文，与 PM 模块的 Caller 同语义。
type Caller struct {
	UserID  uuid.UUID
	IsAdmin bool
}

// AgentKindService 暴露 AgentKind 的应用服务方法。env 走"完整 map 替换"
// 语义：每次 PATCH 传完整 env，nil 表示不修改；非 nil 整体加密重写。
type AgentKindService interface {
	Create(ctx context.Context, c Caller, in CreateAgentKindInput) (*AgentKind, error)
	List(ctx context.Context, c Caller) ([]*AgentKind, error)
	Get(ctx context.Context, c Caller, id uuid.UUID) (*AgentKind, error)
	Update(ctx context.Context, c Caller, id uuid.UUID, in UpdateAgentKindInput) (*AgentKind, error)
	Delete(ctx context.Context, c Caller, id uuid.UUID) error

	// 配置文件管理（admin only）。与 env 不同：内容明文返回给 admin 供编辑，
	// 存储层加密。content 是明文。
	ListConfigFiles(ctx context.Context, c Caller, kindID uuid.UUID) ([]*ConfigFileContent, error)
	UpsertConfigFile(ctx context.Context, c Caller, kindID uuid.UUID, relPath, content string) (*ConfigFileContent, error)
	DeleteConfigFile(ctx context.Context, c Caller, kindID uuid.UUID, relPath string) error
}

// ConfigFileContent 是解密后的配置文件视图（service → handler）。
type ConfigFileContent struct {
	RelPath   string
	Content   string
	UpdatedAt time.Time
}

// SessionService 暴露 Session 聚合根的应用服务方法。
type SessionService interface {
	Create(ctx context.Context, c Caller, in CreateSessionInput) (*Session, error)
	Get(ctx context.Context, c Caller, id uuid.UUID) (*Session, error)
	List(ctx context.Context, c Caller, f SessionListFilter) ([]*Session, error)
	Terminate(ctx context.Context, c Caller, id uuid.UUID) error

	// Dashboard 返回 run-history 汇总 + 按天序列。非 admin 调用强制按 caller
	// scope（UserID 覆盖为 caller），admin 才能查看全量。
	Dashboard(ctx context.Context, c Caller, in DashboardInput) (*DashboardResult, error)
	// Timeline 返回单会话的事件时间线（先经 Get 做 owner/admin 访问校验）。
	Timeline(ctx context.Context, c Caller, sessionID uuid.UUID) ([]SessionTimelineBucket, error)
}

// DashboardInput 是 run-history 汇总的入参（过滤维度由 service 在 scope 内归一化）。
type DashboardInput struct {
	ProjectID     *uuid.UUID
	RequirementID *uuid.UUID
	From          time.Time
	To            time.Time
}

// DashboardResult 是 run-history 汇总响应：总览 + 按天序列。
type DashboardResult struct {
	Rollup SessionRollup
	ByDay  []SessionDayRow
	From   time.Time
	To     time.Time
}

// TurnRunner 是编排器驱动一个阻塞回合所需的窄接口（不扩大 SessionService 的
// HTTP/MCP 暴露面）。sessionService 同时实现 SessionService 与 TurnRunner。
//
//   - SendPromptAndWait 发送一条 session/prompt 并阻塞等待回合完成，返回原始
//     stopReason（来自 relay.Call 的 server-initiated 请求响应）。
//   - ResumeSession 在崩溃 session 的同一 cwd/worktree 上重新拉起 agent。
type TurnRunner interface {
	SendPromptAndWait(ctx context.Context, sessionID uuid.UUID, prompt string) (stopReason string, err error)
	ResumeSession(ctx context.Context, c Caller, sessionID uuid.UUID) (*Session, error)
}

// SessionListFilter 控制 sessions 列表的过滤维度。
// All 仅 admin 可用（查看全部用户）；RequirementID nil 表示不按需求过滤。
type SessionListFilter struct {
	All           bool
	RequirementID *uuid.UUID
}

// CreateSessionInput 是创建 session 的入参。
// branch / issue_number / requirement_number 三选一或全空（spec §8.1）。
type CreateSessionInput struct {
	WorkspaceID       uuid.UUID
	AgentKindID       uuid.UUID
	Branch            *string
	IssueNumber       *int
	RequirementNumber *int
	InitialPrompt     *string
	// OrchestratorStepID 非 nil 时把 session 与编排器 step 反向关联（仅由 orchestrator
	// 内部调用设置；HTTP/MCP 入口不暴露）。
	OrchestratorStepID *uuid.UUID
}

// CreateAgentKindInput 携带创建必需字段。Args / Env 可为 nil。
// ClientType 为空时默认 generic。
type CreateAgentKindInput struct {
	Name          string
	DisplayName   string
	Description   string
	BinaryPath    string
	Args          []string
	Env           map[string]string
	Enabled       bool
	ToolAllowlist []string
	ClientType    ClientType
	MCPServers    []McpServerSpec
	// 成本核算：model 价格来源 + 默认预算上限（均可选）。
	ModelID             *uuid.UUID
	MaxCostUSD          *float64
	MaxTokens           *int64
	MaxWallClockSeconds *int32
}

// UpdateAgentKindInput 是 patch 输入。Name 不可改（创建后稳定，引用约束）。
// Env 三态：nil = 不修改；空 map = 清空全部 env；非空 = 整体替换。
type UpdateAgentKindInput struct {
	DisplayName   *string
	Description   *string
	BinaryPath    *string
	Args          []string          // nil = 不改；非 nil = 替换
	Env           map[string]string // nil = 不改；非 nil（含空）= 替换
	Enabled       *bool
	ToolAllowlist []string        // nil = 不改；非 nil（含空）= 替换
	ClientType    *ClientType     // nil = 不改
	MCPServers    []McpServerSpec // nil = 不改；非 nil（含空）= 替换
	// 成本核算字段：三态指针，nil = 不改。SetModelID 控制 ModelID 是否参与更新
	// （ModelID 本身可被显式置空，故用独立 flag 区分"不改"与"清空"）。
	SetModelID          bool
	ModelID             *uuid.UUID
	SetMaxCostUSD       bool
	MaxCostUSD          *float64
	SetMaxTokens        bool
	MaxTokens           *int64
	SetMaxWallClock     bool
	MaxWallClockSeconds *int32
}

// PermissionStatus 是 acp_permission_requests.status 枚举。
type PermissionStatus string

const (
	PermissionPending  PermissionStatus = "pending"
	PermissionApproved PermissionStatus = "approved"
	PermissionDenied   PermissionStatus = "denied"
	PermissionAuto     PermissionStatus = "auto"
	PermissionExpired  PermissionStatus = "expired"
)

// PermissionRequest 是一条代理发起的工具调用授权请求记录。
type PermissionRequest struct {
	ID             uuid.UUID
	SessionID      uuid.UUID
	AgentRequestID string
	ToolName       string
	ToolCall       []byte // 原始 toolCall JSON
	Options        []byte // 原始 options JSON
	Status         PermissionStatus
	ChosenOptionID *string
	DecidedBy      *uuid.UUID
	DecidedAt      *time.Time
	CreatedAt      time.Time
}
