"""/v1 请求体 Pydantic 模型(从 app.py 析出,2026-07-23 拆分)。"""
from __future__ import annotations

from typing import Literal, Optional
from uuid import UUID

from pydantic import BaseModel, Field

from core.software_contracts import default_capabilities, supported_capabilities


class TaskCreateRequest(BaseModel):
    # name 缺失/空值 + working_dir 有值 → 后端以“新对话”占位并自动生成标题。
    # DB 列仍 NOT NULL；显式 name 保持旧契约，可继续省略 working_dir 并用 name 作目录名。
    name: str = ""
    working_dir: str = ""
    # 兼容旧客户端：新调用方无需再传；旧的 name + auto_title=true 语义继续保留。
    auto_title: bool = False
    description: str = ""
    skill: str = ""
    model_profile: str = ""  # `family.variant`,留空 → cfg["default_model"];必须存在于 config/models/


class TaskPatchRequest(BaseModel):
    status: Optional[str] = None
    description: Optional[str] = None
    name: Optional[str] = None
    skill: Optional[str] = None
    model_profile: Optional[str] = None  # 切模型(c 模式 task 层 / A 粒度 — 下条 send 生效)


class SchedulePatchRequest(BaseModel):
    # 前端只读视图仅用 enabled(停用/启用);其余字段留着供"对话改不了时"的兜底直改,
    # 但前端不暴露编辑表单(建/改走对话,§8.5)。
    enabled: Optional[bool] = None


class MessageAttachmentRequest(BaseModel):
    path: str
    kind: str = "file"
    label: str = ""


class MessageRequest(BaseModel):
    content: str
    attachments: list[MessageAttachmentRequest] = Field(default_factory=list, max_length=10)
    # 该条消息触发的生图 / 生视频模型 variant key(config/media/doubao.yaml image/video 段)。
    # 空 → 对应 tool 走 yaml 第一个 variant;非空 → 本次 run 装配指定 variant。
    # 仅作用于本 run,不入 DB,UI 下拉的选择跟在消息 POST body 上。
    image_model: str = ""
    video_model: str = ""


class OptimizePromptRequest(BaseModel):
    text: str
    # 选择性传当前 UI 选中的 variant key,润色 meta-prompt 会把对应模型特性塞进去
    # (让 LLM 知道下游 tool 偏好,润色出更贴合 seedream / seedance 等的 prompt)。
    image_model: str = ""
    video_model: str = ""


class FileDeleteRequest(BaseModel):
    path: str
    recursive: bool = False


class FileRenameRequest(BaseModel):
    path: str        # 被重命名的目录 / 文件,相对 user_root
    new_name: str    # 新的 leaf 名(不是路径),不含 / \ ..


class FileMkdirRequest(BaseModel):
    path: str = ""   # 父目录,相对 user_root;空 → user_root
    name: str        # 新目录 leaf 名(不是路径)


class FileTransferRequest(BaseModel):
    paths: list[str]      # 多源,均相对 user_root
    dest_dir: str = ""    # 目标目录,相对 user_root,空 → user_root


class LoginRequest(BaseModel):
    user_id: str
    platform_key: str
    # 0016:平台可选注入的用户档案,缺省即旧行为(只填 user_id),向后兼容老调用方。
    name: Optional[str] = None       # 显示名 / 姓名
    user_name: Optional[str] = None  # 平台账号名


class PasswordLoginRequest(BaseModel):
    email: str
    password: str


class AdminCreateUserRequest(BaseModel):
    email: str
    password: str
    admin_token: str
    role: str = "user"  # 'user' / 'admin';admin 可访问 /static/admin.html 管理后台


class ChangePasswordRequest(BaseModel):
    old_password: str
    new_password: str


class KbCreateRequest(BaseModel):
    name: str  # 库名(中文/字母/数字/-/_,拒 dotfile 与路径字符,≤40 字符)


class ExternalSystemCreateRequest(BaseModel):
    definition_id: UUID
    name: str = ""
    credentials: dict[str, str] = Field(default_factory=dict)


class ExternalSystemCredentialsRequest(BaseModel):
    credentials: dict[str, str] = Field(default_factory=dict)


class SoftwareEnrollmentCreateRequest(BaseModel):
    expected_name: str = ""
    capabilities: list[str] = Field(default_factory=lambda: sorted(supported_capabilities()))
    ttl_seconds: int = 600


class SoftwareNodeEnrollRequest(BaseModel):
    enrollment_code: str
    node_name: str
    install_id: UUID
    node_version: str = ""
    os_version: str = ""
    capabilities: list[str]


class SoftwareNodeDisableRequest(BaseModel):
    disabled: bool = True


class SoftwareJobCreateRequest(BaseModel):
    idempotency_key: str
    capability: str = Field(default_factory=lambda: default_capabilities()[0])
    request: dict = Field(default_factory=dict)
    completion_action: Literal["report", "analyze"] = "report"
