"""블록 라이브러리 FAISS 인덱스 빌드 스크립트.

catalog.yaml의 46개 블록을 임베딩하여 FAISS 인덱스를 생성한다.
블록 추가/수정 시 이 스크립트를 다시 실행하면 인덱스가 갱신된다.

사용법:
    python scripts/build_block_index.py

산출물:
    data/block_index.faiss      — FAISS 벡터 인덱스
    data/block_metadata.json    — 인덱스 순서 → 블록 매핑
"""
from __future__ import annotations

import json
import logging
import sys
from pathlib import Path

import faiss
import numpy as np
import yaml
from sentence_transformers import SentenceTransformer

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)

# Kei persona와 동일 모델 (1024차원, 한국어 최적화)
EMBEDDING_MODEL = "BAAI/bge-m3"

PROJECT_ROOT = Path(__file__).parent.parent
CATALOG_PATH = PROJECT_ROOT / "templates" / "catalog.yaml"
INDEX_PATH = PROJECT_ROOT / "data" / "block_index.faiss"
META_PATH = PROJECT_ROOT / "data" / "block_metadata.json"


def load_catalog() -> list[dict]:
    """catalog.yaml에서 블록 목록을 로드한다."""
    if not CATALOG_PATH.exists():
        logger.error(f"catalog.yaml 없음: {CATALOG_PATH}")
        sys.exit(1)

    with open(CATALOG_PATH, encoding="utf-8") as f:
        data = yaml.safe_load(f)

    blocks = data.get("blocks", [])
    if not blocks:
        logger.error("catalog.yaml에 블록이 없습니다.")
        sys.exit(1)

    logger.info(f"catalog 로드: {len(blocks)}개 블록")
    return blocks


def build_search_texts(blocks: list[dict]) -> list[str]:
    """각 블록의 검색용 텍스트를 생성한다.

    name + visual + when을 조합하여 검색 쿼리와 매칭되도록 한다.
    not_for는 네거티브이므로 검색 텍스트에 포함하지 않는다.
    """
    texts = []
    for block in blocks:
        parts = [
            block.get("name", ""),
            block.get("visual", ""),
            block.get("when", ""),
        ]
        text = ". ".join(p.strip() for p in parts if p.strip())
        texts.append(text)
    return texts


def build_index(texts: list[str]) -> tuple[faiss.IndexFlatIP, np.ndarray]:
    """텍스트를 임베딩하고 FAISS 인덱스를 생성한다."""
    logger.info(f"임베딩 모델 로딩: {EMBEDDING_MODEL}")
    model = SentenceTransformer(EMBEDDING_MODEL)

    logger.info(f"{len(texts)}개 텍스트 임베딩 중...")
    embeddings = model.encode(
        texts,
        normalize_embeddings=True,  # 코사인 유사도를 위해 정규화
        show_progress_bar=True,
    )
    embeddings = np.array(embeddings, dtype=np.float32)

    dim = embeddings.shape[1]
    logger.info(f"임베딩 완료: {embeddings.shape[0]}개 × {dim}차원")

    # Inner Product = 정규화된 벡터에서 코사인 유사도
    index = faiss.IndexFlatIP(dim)
    index.add(embeddings)

    logger.info(f"FAISS 인덱스 생성: {index.ntotal}개 벡터, {dim}차원")
    return index, embeddings


def save_metadata(blocks: list[dict]) -> None:
    """블록 메타데이터를 인덱스 순서대로 저장한다."""
    metadata = []
    for block in blocks:
        metadata.append({
            "id": block["id"],
            "name": block.get("name", ""),
            "template": block.get("template", ""),
            "category": block.get("template", "").split("/")[1] if "/" in block.get("template", "") else "",
            "height_cost": block.get("height_cost", "medium"),
            "visual": block.get("visual", ""),
            "when": block.get("when", ""),
            "not_for": block.get("not_for", ""),
        })

    META_PATH.parent.mkdir(parents=True, exist_ok=True)
    with open(META_PATH, "w", encoding="utf-8") as f:
        json.dump(metadata, f, ensure_ascii=False, indent=2)

    logger.info(f"메타데이터 저장: {META_PATH} ({len(metadata)}개)")


def main():
    # 1. catalog 로드
    blocks = load_catalog()

    # 2. 검색용 텍스트 생성
    texts = build_search_texts(blocks)

    # 3. 임베딩 + FAISS 인덱스
    index, embeddings = build_index(texts)

    # 4. 저장
    INDEX_PATH.parent.mkdir(parents=True, exist_ok=True)
    faiss.write_index(index, str(INDEX_PATH))
    logger.info(f"FAISS 인덱스 저장: {INDEX_PATH}")

    save_metadata(blocks)

    # 5. 검증
    logger.info("--- 검증 ---")
    test_index = faiss.read_index(str(INDEX_PATH))
    with open(META_PATH, encoding="utf-8") as f:
        test_meta = json.load(f)

    assert test_index.ntotal == len(blocks), f"벡터 수 불일치: {test_index.ntotal} vs {len(blocks)}"
    assert len(test_meta) == len(blocks), f"메타데이터 수 불일치: {len(test_meta)} vs {len(blocks)}"

    logger.info(f"✅ 검증 통과: {test_index.ntotal}개 벡터, {len(test_meta)}개 메타데이터")

    # 6. 테스트 검색
    model = SentenceTransformer(EMBEDDING_MODEL)
    test_queries = [
        "A vs B 두 개념 비교",
        "연도별 정책 로드맵",
        "핵심 수치 KPI 통계",
    ]
    for query in test_queries:
        q_emb = model.encode([query], normalize_embeddings=True)
        scores, indices = test_index.search(np.array(q_emb, dtype=np.float32), 3)
        results = [test_meta[i]["id"] for i in indices[0]]
        logger.info(f"  검색 '{query}' → {results}")


if __name__ == "__main__":
    main()
