#!/usr/bin/env python3
"""
qaagent67lite 一键部署脚本

用法:
  # 1. 创建 instance.yml 指定数据目录
  # 2. 将原始文件放入 {baseDir}/raw/
  # 3. 运行:
  python scripts/setup.py

自动执行:
  Step 1: 提取文本 (PDF/Word/PPT → txt)
  Step 2: 建 BM25 索引 (关键词检索)
  Step 3: Wiki 编译 (LLM 知识编译)
  Step 4: 验证
"""
import json
import os
import sys
import time
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
AGENT_DIR = SCRIPT_DIR.parent
PROJECT_ROOT = AGENT_DIR.parent.parent

sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(AGENT_DIR.parent))


def load_config():
    """加载配置 (agent-config.yml + instance.yml)"""
    import yaml

    config = {}
    agent_config = AGENT_DIR / "agent-config.yml"
    if agent_config.exists():
        with open(agent_config) as f:
            config = yaml.safe_load(f) or {}

    # instance.yml 覆盖
    for candidate in [
        os.environ.get("INSTANCE_CONFIG", ""),
        str(SCRIPT_DIR / "instance.yml"),
        str(AGENT_DIR / "instance.yml"),
    ]:
        if candidate and Path(candidate).is_file():
            with open(candidate) as f:
                instance = yaml.safe_load(f) or {}
            # 深度合并
            for k, v in instance.items():
                if k in config and isinstance(config[k], dict) and isinstance(v, dict):
                    config[k].update(v)
                else:
                    config[k] = v
            break

    return config


def setup():
    cfg = load_config()
    knowledge = cfg.get("knowledge", {})
    wiki_cfg = cfg.get("wiki", {})

    base_dir = Path(knowledge.get("baseDir", "./knowledge"))
    raw_dir = Path(knowledge.get("rawDir", str(base_dir / "raw")))
    processed_dir = Path(knowledge.get("processedDir", str(base_dir / "processed")))
    wiki_dir = Path(wiki_cfg.get("dir", "./wiki"))

    print(f"{'='*60}")
    print(f"  qaagent67lite 一键部署")
    print(f"{'='*60}")
    print(f"  知识库: {base_dir}")
    print(f"  原始文件: {raw_dir}")
    print(f"  Wiki: {wiki_dir}")
    print()

    # 检查原始文件
    if not raw_dir.exists():
        print(f"  ❌ 原始文件目录不存在: {raw_dir}")
        print(f"  请先将文件放入 {raw_dir}/")
        return

    raw_files = list(raw_dir.rglob("*"))
    raw_files = [f for f in raw_files if f.is_file() and not f.name.startswith(".")]
    print(f"  📁 发现 {len(raw_files)} 个原始文件")

    if len(raw_files) == 0:
        print(f"  ❌ 无文件可处理")
        return

    if len(raw_files) > 500:
        print(f"  ⚠️  文件数 > 500，建议使用 qaagent67lambda (带向量检索)")

    # Step 1: 提取文本
    print(f"\n  📄 Step 1/3: 提取文本...")
    processed_dir.mkdir(parents=True, exist_ok=True)
    extracted = 0
    for f in raw_files:
        out_path = processed_dir / f"{f.stem}.txt"
        if out_path.exists():
            extracted += 1
            continue
        try:
            if f.suffix.lower() == ".pdf":
                _extract_pdf(f, out_path)
            elif f.suffix.lower() in (".docx", ".doc"):
                _extract_docx(f, out_path)
            elif f.suffix.lower() in (".txt", ".md", ".csv", ".json", ".yaml", ".yml"):
                import shutil
                shutil.copy2(f, out_path)
            else:
                continue
            extracted += 1
        except Exception as e:
            print(f"    ⚠️ {f.name}: {e}")
    print(f"  ✅ 提取完成: {extracted} 个文件")

    # Step 2: BM25 索引
    print(f"\n  🔍 Step 2/3: 建 BM25 索引...")
    t0 = time.time()
    try:
        _build_bm25_index(processed_dir, base_dir, cfg)
        print(f"  ✅ BM25 索引完成 ({time.time()-t0:.1f}s)")
    except Exception as e:
        print(f"  ❌ BM25 索引失败: {e}")

    # Step 3: Wiki 编译
    print(f"\n  📚 Step 3/3: Wiki 编译...")
    t0 = time.time()
    try:
        _wiki_compile(processed_dir, wiki_dir)
        print(f"  ✅ Wiki 编译完成 ({time.time()-t0:.1f}s)")
    except Exception as e:
        print(f"  ⚠️ Wiki 编译失败 (可后续补): {e}")

    # 验证
    print(f"\n{'='*60}")
    print(f"  ✅ 部署完成!")
    print(f"  BM25 索引: {base_dir / 'rag_index.json'}")
    wiki_pages = len(list(wiki_dir.rglob("*.md"))) if wiki_dir.exists() else 0
    print(f"  Wiki 页面: {wiki_pages}")
    print(f"\n  启动: python3 -m agentpaas agent create --name lite-qa \\")
    print(f"          --config agentexample/qaagent67lite/agent-config.yml")
    print(f"        python3 -m agentpaas chat lite-qa")
    print(f"{'='*60}")


def _extract_pdf(pdf_path, out_path):
    """PDF → txt"""
    try:
        import PyPDF2
        with open(pdf_path, "rb") as f:
            reader = PyPDF2.PdfReader(f)
            text = "\n\n".join(
                page.extract_text() or "" for page in reader.pages
            )
        with open(out_path, "w", encoding="utf-8") as f:
            f.write(text)
    except ImportError:
        # 降级到 pdftotext CLI
        import subprocess
        subprocess.run(["pdftotext", str(pdf_path), str(out_path)],
                      capture_output=True, timeout=30)


def _extract_docx(docx_path, out_path):
    """Word → txt"""
    try:
        import docx
        doc = docx.Document(str(docx_path))
        text = "\n\n".join(p.text for p in doc.paragraphs if p.text.strip())
        with open(out_path, "w", encoding="utf-8") as f:
            f.write(text)
    except ImportError:
        raise RuntimeError("需要 python-docx: pip install python-docx")


def _build_bm25_index(processed_dir, base_dir, cfg):
    """构建 BM25 索引 (简化版，不需要 embedding)"""
    import re
    from collections import Counter

    chunk_size = cfg.get("rag", {}).get("chunkSize", 512)
    chunk_overlap = cfg.get("rag", {}).get("chunkOverlap", 64)

    chunks = []
    keywords_index = {}

    for txt_file in sorted(processed_dir.glob("*.txt")):
        with open(txt_file, "r", encoding="utf-8", errors="replace") as f:
            content = f.read()

        if not content.strip():
            continue

        # 分块
        file_chunks = _chunk_text(content, chunk_size, chunk_overlap)
        for i, chunk in enumerate(file_chunks):
            chunk_id = f"{txt_file.stem}_chunk_{i}"
            chunks.append({
                "source": txt_file.stem,
                "chunk_id": chunk_id,
                "text": chunk,
            })

            # 关键词索引
            words = re.findall(r'[\u4e00-\u9fff]+|[a-zA-Z]+', chunk.lower())
            word_counts = Counter(words)
            for word, count in word_counts.items():
                if len(word) >= 2:
                    if word not in keywords_index:
                        keywords_index[word] = []
                    keywords_index[word].append({
                        "chunk_id": chunk_id,
                        "count": count,
                    })

    # 保存
    index_path = base_dir / "rag_index.json"
    with open(index_path, "w", encoding="utf-8") as f:
        json.dump({"chunks": chunks, "total": len(chunks)}, f, ensure_ascii=False)

    kw_path = base_dir / "rag_index.keywords.json"
    with open(kw_path, "w", encoding="utf-8") as f:
        json.dump(keywords_index, f, ensure_ascii=False)

    print(f"    {len(chunks)} 个文本块, {len(keywords_index)} 个关键词")


def _chunk_text(text, size=512, overlap=64):
    """按字符数分块"""
    chunks = []
    start = 0
    while start < len(text):
        end = start + size
        chunk = text[start:end]
        if chunk.strip():
            chunks.append(chunk.strip())
        start = end - overlap
    return chunks


def _wiki_compile(processed_dir, wiki_dir):
    """调用 WikiIngest 编译 wiki"""
    from lambdagent.builtin_tools.wiki_tools import wiki_ingest

    wiki_dir.mkdir(parents=True, exist_ok=True)

    txt_files = sorted(processed_dir.glob("*.txt"))
    total = len(txt_files)
    done = 0

    for txt_file in txt_files:
        try:
            wiki_ingest(json.dumps({
                "path": str(txt_file),
                "wiki_root": str(wiki_dir),
            }))
            done += 1
            if done % 10 == 0:
                print(f"    Wiki: {done}/{total} ({done*100//total}%)")
        except Exception as e:
            print(f"    ⚠️ {txt_file.name}: {e}")

    print(f"    Wiki 编译: {done}/{total} 完成")


if __name__ == "__main__":
    setup()
