"""
agentpaas.engine.builtin_packs — auto-install built-in AgentPacks on first startup.

Called from api/app.py ``_on_startup()`` in desktop mode (idempotent; skips
packs that are already installed).  Never raises — logs warnings on failure so
the server can still start even if a pack fails to install.

Built-in pack sources: ``agentexample/agentpacks/`` (relative to repo root,
resolved by walking up from this file).
"""
from __future__ import annotations

import logging
import os
import tempfile
import zipfile
from pathlib import Path

logger = logging.getLogger(__name__)

BUILTIN_PACK_IDS: list[str] = [
    # 科研
    "research.top-journal-reviewer",
    "research.literature-mapper",
    "research.grant-planner",
    "research.multi-reviewer",
    "research.paper-polisher",
    "research.rebuttal-assistant",
    "research.thesis-advisor",
    # 教学
    "teaching.course-designer",
    "teaching.exam-builder",
    "teaching.assignment-grader",
    # 学术服务
    "service.academic-letters",
    # 通用
    "workspace.assistant",
]


def _find_packs_src() -> Path | None:
    """Walk up from this file to find ``agentexample/agentpacks/``."""
    here = Path(__file__).resolve().parent
    for _ in range(8):
        candidate = here / "agentexample" / "agentpacks"
        if candidate.is_dir():
            return candidate
        here = here.parent
    # CWD fallback (running from repo root)
    cwd_candidate = Path(os.getcwd()) / "agentexample" / "agentpacks"
    if cwd_candidate.is_dir():
        return cwd_candidate
    return None


def _zip_pack(pack_id: str, pack_dir: Path, dest_dir: Path) -> Path:
    zip_path = dest_dir / f"{pack_id}.zip"
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for p in pack_dir.rglob("*"):
            if p.is_file():
                zf.write(p, p.relative_to(pack_dir))
    return zip_path


def ensure_builtin_packs_installed(packs_dir: str) -> None:
    """Install any built-in packs that are not already present in *packs_dir*.

    Safe to call every startup:
    - Each pack is skipped if its ``id`` is already installed (any version).
    - Exceptions per-pack are caught and logged; remaining packs are still
      attempted.
    - The directory is created if it doesn't exist yet.
    """
    from agentpaas.engine.agentpack_store import install_from_zip, get_installed

    packs_src = _find_packs_src()
    if not packs_src:
        logger.debug(
            "ensure_builtin_packs_installed: agentexample/agentpacks/ not found "
            "(this is normal when running outside the repo); skipping auto-install"
        )
        return

    os.makedirs(packs_dir, exist_ok=True)

    with tempfile.TemporaryDirectory() as td:
        for pack_id in BUILTIN_PACK_IDS:
            try:
                if get_installed(packs_dir, pack_id):
                    logger.debug(f"builtin pack already installed: {pack_id}")
                    continue

                pack_dir = packs_src / pack_id
                if not pack_dir.is_dir():
                    logger.debug(
                        f"builtin pack source dir not found: {pack_dir}; skipping"
                    )
                    continue

                zip_path = _zip_pack(pack_id, pack_dir, Path(td))
                # 内置包是第一方、随产品分发的可信包：trusted=True 允许其声明
                # shell（如 workspace.assistant 的 Claude Code 式 Bash 能力）——
                # shell 拒绝策略针对的是不受信的三方包。运行期仍有 guard
                # (dangerousCommandBlock/highRiskConfirmation) 兜底。
                pack = install_from_zip(str(zip_path), packs_dir, trusted=True)
                logger.info(
                    f"auto-installed builtin pack: {pack_id} v{pack.version}"
                )
            except Exception as exc:  # pragma: no cover — startup must not crash
                logger.warning(
                    f"failed to auto-install builtin pack {pack_id}: {exc}"
                )
