from __future__ import annotations

# pyright: reportMissingImports=false

"""
kakao_knowledge CLI

사용법:
  python3 -m kakao_knowledge parse   /path/to/kakao.txt   --output parsed.json
  python3 -m kakao_knowledge extract parsed.json           --output wiki_entries.json
  python3 -m kakao_knowledge extract parsed.json           --output wiki_entries.json --use-llm
  python3 -m kakao_knowledge pipeline /path/to/kakao.txt  --output wiki_entries.json
  python3 -m kakao_knowledge import wiki_entries.json
  python3 -m kakao_knowledge stats
  python3 -m kakao_knowledge search "광응고술"
  python3 -m kakao_knowledge update /path/to/new_chat.txt [--db-path PATH] [--insights-dir PATH]
  python3 -m kakao_knowledge similar "광응고술 보상" [--top-k 5] [--db-path PATH]
  python3 -m kakao_knowledge graph insight-001 [--depth 1] [--insights-dir PATH]
"""

import argparse
import json
import logging
import sys
from pathlib import Path

from .kakao_parser import parse_kakao_chat
from .knowledge_extractor import extract_knowledge
from .knowledge_extractor_v2 import extract_knowledge_v2
from .models import ChatMessage
from .wiki_store import WikiStore

# Phase 3 임포트
_DEFAULT_DB_PATH = "/home/jay/projects/insuwiki/data/chroma_db/"
_DEFAULT_INSIGHTS_DIR = "/home/jay/projects/insuwiki/data/insights/"
_DEFAULT_GRAPH_PATH = "/home/jay/projects/insuwiki/data/graph.json"
_DEFAULT_INDEX_PATH = "/home/jay/projects/insuwiki/data/insights_index.json"

# ---------------------------------------------------------------------------
# 로깅 설정
# ---------------------------------------------------------------------------

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%H:%M:%S",
)
logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# JSON 직렬화 헬퍼
# ---------------------------------------------------------------------------


def _serialize_parse_result(result: dict) -> dict:  # type: ignore[type-arg]
    """parse_kakao_chat 결과의 ChatMessage 리스트를 dict 리스트로 변환한다."""
    data = result.copy()
    data["messages"] = [m.model_dump() for m in data["messages"]]
    return data


def _deserialize_messages(messages_raw: list) -> list[ChatMessage]:  # type: ignore[type-arg]
    """JSON에서 로드한 dict 리스트를 ChatMessage 리스트로 복원한다."""
    return [ChatMessage(**m) for m in messages_raw]


# ---------------------------------------------------------------------------
# 출력 헬퍼
# ---------------------------------------------------------------------------


def _write_output(data: object, output_path: str | None) -> None:
    """데이터를 JSON으로 파일 또는 stdout에 출력한다."""
    text = json.dumps(data, ensure_ascii=False, indent=2)
    if output_path:
        Path(output_path).write_text(text, encoding="utf-8")
        print(f"저장 완료: {output_path}")
    else:
        print(text)


# ---------------------------------------------------------------------------
# 서브커맨드 핸들러
# ---------------------------------------------------------------------------


def cmd_parse(args: argparse.Namespace) -> int:
    """parse 서브커맨드: 카카오톡 txt 파일 파싱 → JSON 저장."""
    print(f"파싱 중: {args.input}")
    result = parse_kakao_chat(args.input)
    serialized = _serialize_parse_result(result)
    _write_output(serialized, args.output)
    stats = result.get("stats", {})
    print(
        f"파싱 완료 — 메시지: {stats.get('total_messages', 0)}건, "
        f"고유 사용자: {stats.get('unique_users', 0)}명"
    )
    return 0


def cmd_extract(args: argparse.Namespace) -> int:
    """extract 서브커맨드: 파싱된 JSON → 지식 추출 → JSON 저장."""
    print(f"로딩 중: {args.input}")
    raw = json.loads(Path(args.input).read_text(encoding="utf-8"))

    messages = _deserialize_messages(raw.get("messages", []))
    source_chat: str = raw.get("header", {}).get("chat_name", "")
    if args.chat_name:
        source_chat = args.chat_name

    print(
        f"지식 추출 중 (메시지 {len(messages)}건, "
        f"LLM={'활성화' if args.use_llm else '비활성화'}) ..."
    )
    entries = extract_knowledge(
        messages,
        use_llm=args.use_llm,
        api_key=getattr(args, "api_key", None),
        source_chat=source_chat,
    )

    _write_output(entries, args.output)
    print(f"추출 완료 — wiki_entry: {len(entries)}건")
    return 0


def cmd_import(args: argparse.Namespace) -> int:
    """import 서브커맨드: JSON 파일의 항목을 WikiStore에 일괄 저장."""
    print(f"로딩 중: {args.input}")
    raw = json.loads(Path(args.input).read_text(encoding="utf-8"))
    if not isinstance(raw, list):
        print("오류: JSON 파일의 최상위가 list여야 합니다.", file=sys.stderr)
        return 1
    entries: list[dict] = raw  # type: ignore[type-arg]
    db_path: str | None = getattr(args, "db_path", None) or None
    store = WikiStore(db_path)
    try:
        count = store.bulk_import(entries)
    finally:
        store.close()
    print(f"import 완료 — {count}/{len(entries)}건 저장")
    return 0


def cmd_stats(args: argparse.Namespace) -> int:
    """stats 서브커맨드: WikiStore 통계 출력."""
    db_path: str | None = getattr(args, "db_path", None) or None
    store = WikiStore(db_path)
    try:
        stats = store.get_stats()
    finally:
        store.close()
    print(json.dumps(stats, ensure_ascii=False, indent=2))
    return 0


def cmd_search(args: argparse.Namespace) -> int:
    """search 서브커맨드: WikiStore FTS5 전문 검색."""
    db_path: str | None = getattr(args, "db_path", None) or None
    store = WikiStore(db_path)
    try:
        results = store.search_entries(args.query, limit=args.limit)
    finally:
        store.close()
    print(json.dumps(results, ensure_ascii=False, indent=2))
    print(f"검색 결과: {len(results)}건", file=sys.stderr)
    return 0


def cmd_pipeline(args: argparse.Namespace) -> int:
    """pipeline 서브커맨드: parse + extract 연속 실행."""
    print(f"[파이프라인] 파싱 시작: {args.input}")
    result = parse_kakao_chat(args.input)

    source_chat: str = result.get("header", {}).get("chat_name", "")
    if args.chat_name:
        source_chat = args.chat_name

    messages: list[ChatMessage] = result.get("messages", [])
    stats = result.get("stats", {})
    print(
        f"[파이프라인] 파싱 완료 — 메시지: {stats.get('total_messages', 0)}건, "
        f"고유 사용자: {stats.get('unique_users', 0)}명"
    )

    print(
        f"[파이프라인] 지식 추출 중 (LLM={'활성화' if args.use_llm else '비활성화'}) ..."
    )
    entries = extract_knowledge(
        messages,
        use_llm=args.use_llm,
        api_key=getattr(args, "api_key", None),
        source_chat=source_chat,
    )

    _write_output(entries, args.output)
    print(f"[파이프라인] 완료 — wiki_entry: {len(entries)}건")
    return 0


def cmd_extract_v2(args: argparse.Namespace) -> int:
    """extract-v2 서브커맨드: 파싱된 JSON → v2 지식 추출 → JSON 저장."""
    print(f"로딩 중: {args.input}")
    raw = json.loads(Path(args.input).read_text(encoding="utf-8"))

    messages = _deserialize_messages(raw.get("messages", []))
    source_chat: str = raw.get("header", {}).get("chat_name", "")
    if args.chat_name:
        source_chat = args.chat_name

    print(
        f"지식 추출 중 v2 (메시지 {len(messages)}건, "
        f"LLM={'활성화' if args.use_llm else '비활성화'}) ..."
    )
    entries = extract_knowledge_v2(
        messages,
        use_llm=args.use_llm,
        source_chat=source_chat,
        output_dir=getattr(args, "output_dir", None),
        batch_size=args.batch_size,
        progress_file=getattr(args, "progress_file", None),
        month=getattr(args, "month", ""),
    )

    _write_output(entries, args.output)
    print(f"추출 완료 — insight_v2: {len(entries)}건")
    return 0


def cmd_update(args: argparse.Namespace) -> int:
    """update 서브커맨드: 새 카톡 파일로 증분 업데이트."""
    from .incremental_updater import IncrementalUpdater
    from .knowledge_graph import KnowledgeGraph
    from .vector_store import VectorStore

    db_path = getattr(args, "db_path", None) or _DEFAULT_DB_PATH
    insights_dir = getattr(args, "insights_dir", None) or _DEFAULT_INSIGHTS_DIR
    graph_path = _DEFAULT_GRAPH_PATH
    index_path = _DEFAULT_INDEX_PATH

    print(f"[update] 파싱 중: {args.input}")
    result = parse_kakao_chat(args.input)
    source_chat: str = result.get("header", {}).get("chat_name", "")
    messages: list[ChatMessage] = result.get("messages", [])
    stats_parse = result.get("stats", {})
    print(f"[update] 파싱 완료 — 메시지: {stats_parse.get('total_messages', 0)}건")

    print("[update] 인사이트 추출 중 (규칙 기반) ...")
    new_insights = extract_knowledge_v2(
        messages, use_llm=False, source_chat=source_chat
    )
    print(f"[update] 추출 완료 — {len(new_insights)}건")

    print("[update] VectorStore + KnowledgeGraph 초기화 중 ...")
    store = VectorStore(db_path=db_path)
    graph = KnowledgeGraph(
        insights_dir=insights_dir,
        graph_path=graph_path,
        index_path=index_path,
    )

    updater = IncrementalUpdater(vector_store=store, graph=graph)
    update_stats = updater.update(new_insights)

    print(json.dumps(update_stats, ensure_ascii=False, indent=2))
    print(
        f"[update] 완료 — 신규: {update_stats['new']}건, "
        f"보강: {update_stats['augmented']}건"
    )
    return 0


def cmd_similar(args: argparse.Namespace) -> int:
    """similar 서브커맨드: 유사 인사이트 검색."""
    from .vector_store import VectorStore

    db_path = getattr(args, "db_path", None) or _DEFAULT_DB_PATH
    top_k = getattr(args, "top_k", 5)

    store = VectorStore(db_path=db_path)
    results = store.search_similar(args.query, top_k=top_k)
    print(json.dumps(results, ensure_ascii=False, indent=2))
    print(f"검색 결과: {len(results)}건", file=sys.stderr)
    return 0


def cmd_graph(args: argparse.Namespace) -> int:
    """graph 서브커맨드: 특정 인사이트의 연결 조회."""
    from .knowledge_graph import KnowledgeGraph

    insights_dir = getattr(args, "insights_dir", None) or _DEFAULT_INSIGHTS_DIR
    depth = getattr(args, "depth", 1)

    graph = KnowledgeGraph(
        insights_dir=insights_dir,
        graph_path=_DEFAULT_GRAPH_PATH,
        index_path=_DEFAULT_INDEX_PATH,
    )

    # graph.json 로드
    graph_json_path = Path(_DEFAULT_GRAPH_PATH)
    if graph_json_path.exists():
        import json as _json

        graph._graph = _json.loads(graph_json_path.read_text(encoding="utf-8"))

    related = graph.get_related(args.insight_id, max_depth=depth)
    print(json.dumps(related, ensure_ascii=False, indent=2))
    print(f"연결된 인사이트: {len(related)}건", file=sys.stderr)
    return 0


def cmd_pipeline_v2(args: argparse.Namespace) -> int:
    """pipeline-v2 서브커맨드: parse + extract_v2 연속 실행."""
    print(f"[파이프라인 v2] 파싱 시작: {args.input}")
    result = parse_kakao_chat(args.input)

    source_chat: str = result.get("header", {}).get("chat_name", "")
    if args.chat_name:
        source_chat = args.chat_name

    messages: list[ChatMessage] = result.get("messages", [])
    stats = result.get("stats", {})
    print(
        f"[파이프라인 v2] 파싱 완료 — 메시지: {stats.get('total_messages', 0)}건, "
        f"고유 사용자: {stats.get('unique_users', 0)}명"
    )

    print(
        f"[파이프라인 v2] 지식 추출 중 (LLM={'활성화' if args.use_llm else '비활성화'}) ..."
    )
    entries = extract_knowledge_v2(
        messages,
        use_llm=args.use_llm,
        source_chat=source_chat,
        output_dir=getattr(args, "output_dir", None),
        batch_size=args.batch_size,
        progress_file=getattr(args, "progress_file", None),
        month=getattr(args, "month", ""),
        skip_threads=getattr(args, "skip_threads", 0),
    )

    _write_output(entries, args.output)
    print(f"[파이프라인 v2] 완료 — insight_v2: {len(entries)}건")
    return 0


# ---------------------------------------------------------------------------
# argparse 구성
# ---------------------------------------------------------------------------


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="kakao_knowledge",
        description="카카오톡 대화에서 보험 실무 지식을 추출합니다.",
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    # ── parse ──────────────────────────────────────────────────────────────
    parse_parser = subparsers.add_parser(
        "parse",
        help="카카오톡 txt 파일을 파싱하여 JSON으로 저장합니다.",
    )
    parse_parser.add_argument("input", help="카카오톡 txt 파일 경로")
    parse_parser.add_argument(
        "--output",
        default=None,
        help="출력 파일 경로 (기본값: stdout)",
    )
    parse_parser.add_argument(
        "--chat-name",
        default=None,
        dest="chat_name",
        help="채팅방 이름 (source_chat 값 override)",
    )

    # ── extract ────────────────────────────────────────────────────────────
    extract_parser = subparsers.add_parser(
        "extract",
        help="파싱된 JSON에서 보험 실무 지식을 추출합니다.",
    )
    extract_parser.add_argument("input", help="파싱된 JSON 파일 경로")
    extract_parser.add_argument(
        "--output",
        default=None,
        help="출력 파일 경로 (기본값: stdout)",
    )
    extract_parser.add_argument(
        "--use-llm",
        action="store_true",
        default=False,
        dest="use_llm",
        help="Gemini LLM 분석 활성화",
    )
    extract_parser.add_argument(
        "--chat-name",
        default=None,
        dest="chat_name",
        help="source_chat 값 override",
    )
    extract_parser.add_argument(
        "--api-key",
        default=None,
        dest="api_key",
        help="Gemini API 키 (환경변수 GLM_API_KEY / GEMINI_API_KEY 대체)",
    )

    # ── pipeline ───────────────────────────────────────────────────────────
    pipeline_parser = subparsers.add_parser(
        "pipeline",
        help="parse + extract 연속 실행 (전체 파이프라인).",
    )
    pipeline_parser.add_argument("input", help="카카오톡 txt 파일 경로")
    pipeline_parser.add_argument(
        "--output",
        default=None,
        help="출력 파일 경로 (기본값: stdout)",
    )
    pipeline_parser.add_argument(
        "--use-llm",
        action="store_true",
        default=False,
        dest="use_llm",
        help="Gemini LLM 분석 활성화",
    )
    pipeline_parser.add_argument(
        "--chat-name",
        default=None,
        dest="chat_name",
        help="source_chat 값 override",
    )
    pipeline_parser.add_argument(
        "--api-key",
        default=None,
        dest="api_key",
        help="Gemini API 키 (환경변수 GLM_API_KEY / GEMINI_API_KEY 대체)",
    )

    # ── extract-v2 ─────────────────────────────────────────────────────────
    extract_v2_parser = subparsers.add_parser(
        "extract-v2",
        help="파싱된 JSON에서 v2 파이프라인으로 보험 실무 지식을 추출합니다.",
    )
    extract_v2_parser.add_argument("input", help="파싱된 JSON 파일 경로")
    extract_v2_parser.add_argument(
        "--output",
        default=None,
        help="출력 파일 경로 (기본값: stdout)",
    )
    extract_v2_parser.add_argument(
        "--use-llm",
        action="store_true",
        default=False,
        dest="use_llm",
        help="Claude CLI LLM 분석 활성화",
    )
    extract_v2_parser.add_argument(
        "--chat-name",
        default=None,
        dest="chat_name",
        help="source_chat 값 override",
    )
    extract_v2_parser.add_argument(
        "--batch-size",
        type=int,
        default=50,
        dest="batch_size",
        help="배치 크기 (기본값: 50)",
    )
    extract_v2_parser.add_argument(
        "--output-dir",
        default=None,
        dest="output_dir",
        help="중간 결과 저장 디렉토리 경로",
    )
    extract_v2_parser.add_argument(
        "--progress-file",
        default=None,
        dest="progress_file",
        help="진행 상태 JSON 파일 경로 (배치마다 업데이트)",
    )
    extract_v2_parser.add_argument(
        "--month",
        type=str,
        default="",
        help="처리할 월 (YYYY-MM, YYYY-MM-H1, YYYY-MM-H2 형식). H1=상반기(1~15일), H2=하반기(16~말일)",
    )

    # ── pipeline-v2 ────────────────────────────────────────────────────────
    pipeline_v2_parser = subparsers.add_parser(
        "pipeline-v2",
        help="parse + extract-v2 연속 실행 (v2 전체 파이프라인).",
    )
    pipeline_v2_parser.add_argument("input", help="카카오톡 txt 파일 경로")
    pipeline_v2_parser.add_argument(
        "--output",
        default=None,
        help="출력 파일 경로 (기본값: stdout)",
    )
    pipeline_v2_parser.add_argument(
        "--use-llm",
        action="store_true",
        default=False,
        dest="use_llm",
        help="Claude CLI LLM 분석 활성화",
    )
    pipeline_v2_parser.add_argument(
        "--chat-name",
        default=None,
        dest="chat_name",
        help="source_chat 값 override",
    )
    pipeline_v2_parser.add_argument(
        "--batch-size",
        type=int,
        default=50,
        dest="batch_size",
        help="배치 크기 (기본값: 50)",
    )
    pipeline_v2_parser.add_argument(
        "--output-dir",
        default=None,
        dest="output_dir",
        help="중간 결과 저장 디렉토리 경로",
    )
    pipeline_v2_parser.add_argument(
        "--progress-file",
        default=None,
        dest="progress_file",
        help="진행 상태 JSON 파일 경로 (배치마다 업데이트)",
    )
    pipeline_v2_parser.add_argument(
        "--month",
        type=str,
        default="",
        help="처리할 월 (YYYY-MM, YYYY-MM-H1, YYYY-MM-H2 형식). H1=상반기(1~15일), H2=하반기(16~말일)",
    )
    pipeline_v2_parser.add_argument(
        "--skip-threads",
        type=int,
        default=0,
        dest="skip_threads",
        help="처음 N개 스레드를 건너뜀 (이어서 정제용)",
    )

    # ── update ─────────────────────────────────────────────────────────────
    update_parser = subparsers.add_parser(
        "update",
        help="새 카톡 파일로 증분 업데이트합니다.",
    )
    update_parser.add_argument("input", help="카카오톡 txt 파일 경로")
    update_parser.add_argument(
        "--db-path",
        default=None,
        dest="db_path",
        help=f"chromadb 저장 경로 (기본값: {_DEFAULT_DB_PATH})",
    )
    update_parser.add_argument(
        "--insights-dir",
        default=None,
        dest="insights_dir",
        help=f"인사이트 마크다운 저장 경로 (기본값: {_DEFAULT_INSIGHTS_DIR})",
    )

    # ── similar ─────────────────────────────────────────────────────────────
    similar_parser = subparsers.add_parser(
        "similar",
        help="유사 인사이트를 의미 기반으로 검색합니다.",
    )
    similar_parser.add_argument("query", help="검색 쿼리")
    similar_parser.add_argument(
        "--top-k",
        type=int,
        default=5,
        dest="top_k",
        help="최대 결과 수 (기본값: 5)",
    )
    similar_parser.add_argument(
        "--db-path",
        default=None,
        dest="db_path",
        help=f"chromadb 저장 경로 (기본값: {_DEFAULT_DB_PATH})",
    )

    # ── graph ──────────────────────────────────────────────────────────────
    graph_parser = subparsers.add_parser(
        "graph",
        help="특정 인사이트의 연결된 인사이트를 조회합니다.",
    )
    graph_parser.add_argument("insight_id", help="인사이트 ID (예: insight-001)")
    graph_parser.add_argument(
        "--depth",
        type=int,
        default=1,
        help="탐색 깊이 (기본값: 1)",
    )
    graph_parser.add_argument(
        "--insights-dir",
        default=None,
        dest="insights_dir",
        help=f"인사이트 마크다운 저장 경로 (기본값: {_DEFAULT_INSIGHTS_DIR})",
    )

    # ── import ────────────────────────────────────────────────────────────
    import_parser = subparsers.add_parser(
        "import",
        help="JSON 파일의 위키 항목을 DB에 일괄 저장합니다.",
    )
    import_parser.add_argument("input", help="wiki_entries JSON 파일 경로")
    import_parser.add_argument(
        "--db-path",
        default=None,
        dest="db_path",
        help="DB 파일 경로 (기본값: /home/jay/projects/insuwiki/data/wiki.db)",
    )

    # ── stats ─────────────────────────────────────────────────────────────
    stats_parser = subparsers.add_parser(
        "stats",
        help="WikiStore 카테고리별/상태별 통계를 출력합니다.",
    )
    stats_parser.add_argument(
        "--db-path",
        default=None,
        dest="db_path",
        help="DB 파일 경로 (기본값: /home/jay/projects/insuwiki/data/wiki.db)",
    )

    # ── search ────────────────────────────────────────────────────────────
    search_parser = subparsers.add_parser(
        "search",
        help="WikiStore에서 키워드를 전문 검색합니다.",
    )
    search_parser.add_argument("query", help="검색어")
    search_parser.add_argument(
        "--limit",
        type=int,
        default=20,
        help="최대 결과 건수 (기본값: 20)",
    )
    search_parser.add_argument(
        "--db-path",
        default=None,
        dest="db_path",
        help="DB 파일 경로 (기본값: /home/jay/projects/insuwiki/data/wiki.db)",
    )

    return parser


# ---------------------------------------------------------------------------
# 진입점
# ---------------------------------------------------------------------------


def main() -> int:
    parser = _build_parser()
    args = parser.parse_args()

    handlers: dict[str, object] = {
        "parse": cmd_parse,
        "extract": cmd_extract,
        "extract-v2": cmd_extract_v2,
        "pipeline": cmd_pipeline,
        "pipeline-v2": cmd_pipeline_v2,
        "import": cmd_import,
        "stats": cmd_stats,
        "search": cmd_search,
        "update": cmd_update,
        "similar": cmd_similar,
        "graph": cmd_graph,
    }

    handler = handlers.get(args.command)
    if handler is None:
        parser.print_help()
        return 1

    return handler(args)  # type: ignore[operator]


if __name__ == "__main__":
    sys.exit(main())
