"""
agentpaas.engine.sample_kb — 桌面模式首启时创建「示例知识库」。

目的：老师打开「知识库」页面就能看到一个**已建好索引、立即可搜、
可挂接给智能体**的演示库（含使用指南 + 一门《数据结构》迷你讲义），
在导入自己的文献之前先理解知识库能做什么。

素材源：``agentexample/sample_knowledge/``（与内置包同样的仓库内
资源解析方式；pip 安装环境找不到素材时静默跳过）。

行为：
- 素材拷贝到 ``{data_dir}/sample_kb/``（用户数据目录，删库不伤仓库）；
- 直接生成 ``rag_page_index.json``（pageindex 索引 — 按 ``##`` 小节
  切分，零外部依赖、秒级完成），让检索/问答开箱即用，无需用户
  手动点「建立索引」；
- 在 ``knowledge_bases`` / ``kb_files`` 表登记（归属第一个活跃租户）。

幂等：root_dir 已登记则跳过。绝不抛异常 —— 启动钩子不能因演示数据
失败而拦住服务。
"""
from __future__ import annotations

import json
import logging
import os
import re
import shutil
from pathlib import Path
from typing import List, Optional

logger = logging.getLogger(__name__)

SAMPLE_KB_NAME = "示例知识库（数据结构课程）"
SAMPLE_KB_DESCRIPTION = (
    "系统自带的演示资料库：知识库使用指南 + 《数据结构》迷你讲义。"
    "可直接搜索、问答、挂接给智能体；体验后可删除。"
)


def _find_sample_src() -> Optional[Path]:
    """Walk up from this file to find ``agentexample/sample_knowledge/``."""
    here = Path(__file__).resolve().parent
    for parent in [here, *here.parents]:
        cand = parent / "agentexample" / "sample_knowledge"
        if cand.is_dir():
            return cand
    return None


def _split_sections(text: str) -> List[dict]:
    """把 markdown 按 ``##`` 小节切分为 pageindex 条目（无小节则整文一条）。

    返回 [{"summary": 小节标题行, "text": 小节正文}]。
    """
    lines = text.splitlines()
    sections: List[dict] = []
    cur_title = ""
    cur_buf: List[str] = []
    for ln in lines:
        if ln.startswith("## "):
            if cur_buf and "".join(cur_buf).strip():
                sections.append({"summary": cur_title, "text": "\n".join(cur_buf).strip()})
            cur_title = ln[3:].strip()
            cur_buf = []
        else:
            cur_buf.append(ln)
    if cur_buf and "".join(cur_buf).strip():
        sections.append({"summary": cur_title, "text": "\n".join(cur_buf).strip()})
    if not sections:
        sections = [{"summary": "", "text": text.strip()}]
    return sections


def _build_page_index_json(kb_dir: Path) -> int:
    """为 kb_dir 下的 .md 文件生成 rag_page_index.json，返回条目数。

    schema 与 knowledge.py::_page_index_search 消费的一致：
    [{id, source, page, summary, text, length}]。summary 缺省时检索函数
    把 summary+text 拼起来打分，所以标题为空也安全。
    """
    entries: List[dict] = []
    for md in sorted(kb_dir.glob("*.md")):
        try:
            text = md.read_text(encoding="utf-8")
        except OSError:
            continue
        # 文件首个 # 标题并入每个小节摘要，让"按文件名/章节名搜索"也命中
        m = re.match(r"^#\s+(.+)$", text.lstrip().splitlines()[0] if text.strip() else "")
        doc_title = m.group(1).strip() if m else md.stem
        for page_no, sec in enumerate(_split_sections(text), 1):
            summary = f"{doc_title} — {sec['summary']}" if sec["summary"] else doc_title
            entries.append({
                "id": f"{md.stem}#p{page_no}",
                "source": md.name,
                "page": page_no,
                "summary": summary,
                "text": sec["text"],
                "length": len(sec["text"]),
            })
    if entries:
        (kb_dir / "rag_page_index.json").write_text(
            json.dumps(entries, ensure_ascii=False, indent=1), encoding="utf-8")
    return len(entries)


def _resolve_desktop_tenant(db) -> str:
    """定位示例库应归属的租户。

    桌面老库往往积累了多个历史租户（lambda-space/launch/local…），
    "最老租户"和 config.json 的 key 租户都可能不是浏览器实际登录的
    那个（实测两次踩坑：示例库分别建给了 lambda-space 和 launch，
    而浏览器 localStorage 用的是拥有 7 个智能体的 local 租户，
    「知识库」页因租户过滤显示 0 个）。优先级：
    1. 活跃智能体最多的租户 —— 日常使用集中地，浏览器会话最可能在这
    2. ~/.agentpaas/config.json 里 api_key 对应的租户（全新安装的正解，
       此时该租户也还没有智能体，1 查不到）
    3. 退化：最老的活跃租户
    """
    row = db.fetchone(
        "SELECT tenant_id, COUNT(*) AS n FROM agents WHERE status = 'active' "
        "GROUP BY tenant_id ORDER BY n DESC LIMIT 1")
    if row:
        return row["tenant_id"]
    try:
        with open(os.path.expanduser("~/.agentpaas/config.json"), encoding="utf-8") as f:
            api_key = (json.load(f) or {}).get("api_key", "")
        if api_key:
            row = db.fetchone(
                "SELECT tenant_id FROM api_keys WHERE key_prefix = ? AND status = 'active'",
                (api_key[:8],))
            if row:
                return row["tenant_id"]
    except Exception:
        pass
    row = db.fetchone(
        "SELECT id FROM tenants WHERE status = 'active' ORDER BY created_at LIMIT 1")
    return (row or {}).get("id", "")


def ensure_sample_kb(data_dir: str) -> None:
    """桌面首启：创建示例知识库（幂等、绝不抛异常）。"""
    try:
        _ensure_sample_kb_inner(data_dir)
    except Exception as e:  # pragma: no cover — 启动钩子不许崩
        logger.warning(f"sample KB setup skipped: {e}")


def _ensure_sample_kb_inner(data_dir: str) -> None:
    from agentpaas.db.session import get_db
    from agentpaas.db.models import gen_id, now_utc

    src = _find_sample_src()
    if not src:
        logger.debug("sample_knowledge source not found (non-repo install); skip")
        return

    kb_dir = Path(data_dir) / "sample_kb"
    db = get_db()

    # 幂等：该目录已登记为知识库 → 不重复创建（用户删了库也不再复活，
    # 以 DB 登记而非目录存在性为准，避免"删不掉的示例"惹人烦）。
    existing = db.fetchone(
        "SELECT id FROM knowledge_bases WHERE root_dir = ?",
        (str(kb_dir.resolve()) if kb_dir.exists() else str(kb_dir),))
    if existing:
        return
    # 也兜一层名字查重（root_dir 因 data_dir 迁移变化时不重复建）
    if db.fetchone("SELECT id FROM knowledge_bases WHERE name = ?", (SAMPLE_KB_NAME,)):
        return

    tenant_id = _resolve_desktop_tenant(db)
    if not tenant_id:
        logger.debug("no active tenant yet; sample KB deferred to next startup")
        return

    # 拷素材 + 建 pageindex
    kb_dir.mkdir(parents=True, exist_ok=True)
    copied = 0
    for f in sorted(src.glob("*.md")):
        shutil.copy2(f, kb_dir / f.name)
        copied += 1
    if not copied:
        return
    n_entries = _build_page_index_json(kb_dir)

    # 登记 DB
    kb_id = gen_id("kb_")
    now = now_utc()
    db.execute(
        "INSERT INTO knowledge_bases (id, tenant_id, name, root_dir, description, "
        "created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
        (kb_id, tenant_id, SAMPLE_KB_NAME, str(kb_dir.resolve()),
         SAMPLE_KB_DESCRIPTION, now, now))
    for f in sorted(kb_dir.glob("*.md")):
        db.execute(
            "INSERT INTO kb_files (id, kb_id, file_path, file_name, file_type, size, "
            "status, added_at) VALUES (?, ?, ?, ?, 'md', ?, 'indexed', ?)",
            (gen_id("kbf_"), kb_id, str(f.resolve()), f.name, f.stat().st_size, now))
    db.commit()
    logger.info(
        f"sample KB created: {SAMPLE_KB_NAME} ({copied} files, "
        f"{n_entries} index entries) at {kb_dir}")
