#!/usr/bin/env python3
"""
Day 13 mini project: 멀티툴 리서치 어시스턴트 핸즈온
- mode=selfcheck: 로컬 도구 단위 점검
- mode=single: 단일 질의 실행
- mode=eval: 샘플 태스크 배치 평가

온라인 모드(기본): smolagents + LiteLLMModel 사용
오프라인 모드(--offline): 규칙 기반 fallback (API 키 없이 재현 가능)
"""

from __future__ import annotations

import argparse
import json
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List

DOCS = [
    {
        "id": "agents-course",
        "text": "Hugging Face Agents Course는 에이전트 기본 개념, 툴 호출, 평가, 멀티에이전트 패턴을 다룬다.",
        "url": "https://huggingface.co/learn/agents-course",
    },
    {
        "id": "smolagents-docs",
        "text": "smolagents는 CodeAgent, ToolCallingAgent, @tool 데코레이터를 제공하며 경량 에이전트 구현에 적합하다.",
        "url": "https://huggingface.co/docs/smolagents",
    },
    {
        "id": "course-repo",
        "text": "agents-course GitHub 저장소에는 챕터별 예제와 실습 자료가 포함되어 있다.",
        "url": "https://github.com/huggingface/agents-course",
    },
]


@dataclass
class Task:
    query: str
    required_keyword: str


def search_course_docs(query: str) -> str:
    q = query.lower()
    hits = []
    for d in DOCS:
        t = d["text"].lower()
        if any(tok in t for tok in q.split() if tok.strip()):
            hits.append(d)
    if not hits:
        hits = DOCS[:2]
    return "\n".join([f"- [{h['id']}] {h['text']} ({h['url']})" for h in hits])


def make_checklist(text: str) -> str:
    bullets = []
    for k in ["tool", "평가", "멀티에이전트", "CodeAgent", "ToolCallingAgent"]:
        if k.lower() in text.lower():
            bullets.append(f"- 확인 필요: {k}")
    if not bullets:
        bullets = ["- 확인 필요: 핵심 개념 3개 추출", "- 확인 필요: 실습 재현 단계 작성"]
    return "\n".join(bullets)


def parse_final_block(raw: str) -> Dict[str, str]:
    # 기대 포맷: FINAL: summary=... | checklist=... | verdict=...
    m = re.search(r"FINAL:\s*(.*)", raw, flags=re.DOTALL)
    if not m:
        return {"summary": "", "checklist": "", "verdict": "PARSE_ERROR", "raw": raw}
    body = m.group(1).strip()
    parts = [p.strip() for p in body.split("|")]
    out = {"summary": "", "checklist": "", "verdict": "UNKNOWN", "raw": raw}
    for p in parts:
        if "=" in p:
            k, v = p.split("=", 1)
            out[k.strip()] = v.strip()
    return out


def offline_agent(query: str) -> str:
    docs = search_course_docs(query)
    checklist = make_checklist(docs)
    summary = "HF Agents Course 관련 핵심 포인트를 로컬 문서셋에서 요약함"
    verdict = "READY"
    return f"FINAL: summary={summary} | checklist={checklist.replace(chr(10), '; ')} | verdict={verdict}"


def online_agent(query: str, model_id: str) -> str:
    try:
        from smolagents import CodeAgent, LiteLLMModel, tool
    except Exception as e:  # pragma: no cover
        raise RuntimeError(f"smolagents import 실패: {e}")

    @tool
    def search_docs(query: str) -> str:
        """Search local HF agents course snippets by query."""
        return search_course_docs(query)

    @tool
    def build_checklist(text: str) -> str:
        """Build practical checklist bullets from evidence text."""
        return make_checklist(text)

    model = LiteLLMModel(model_id=model_id)
    agent = CodeAgent(tools=[search_docs, build_checklist], model=model)

    prompt = f"""
너는 HF Agents Course 실습 어시스턴트다.
질의: {query}
반드시 아래 형식으로만 답해라.
FINAL: summary=<한줄요약> | checklist=<세미콜론 구분 체크리스트 2개 이상> | verdict=<READY 또는 NEEDS_REVIEW>
""".strip()

    result = agent.run(prompt)
    return str(result)


def run_single(query: str, offline: bool, model_id: str) -> Dict[str, str]:
    raw = offline_agent(query) if offline else online_agent(query, model_id)
    parsed = parse_final_block(raw)
    return parsed


def run_selfcheck() -> Dict[str, object]:
    q = "smolagents tool calling 평가"
    docs = search_course_docs(q)
    checklist = make_checklist(docs)
    ok = "smolagents" in docs.lower() and "확인 필요" in checklist
    return {
        "selfcheck_ok": ok,
        "docs_preview": docs.splitlines()[:2],
        "checklist_preview": checklist.splitlines()[:2],
    }


def run_eval(tasks: List[Task], offline: bool, model_id: str) -> Dict[str, object]:
    rows = []
    passed = 0
    for t in tasks:
        r = run_single(t.query, offline=offline, model_id=model_id)
        text = json.dumps(r, ensure_ascii=False)
        ok = t.required_keyword.lower() in text.lower() and r.get("verdict") in {"READY", "NEEDS_REVIEW"}
        rows.append({"query": t.query, "required_keyword": t.required_keyword, "ok": ok, "result": r})
        passed += int(ok)
    score = passed / len(tasks) if tasks else 0.0
    return {"pass": score >= 0.66, "score": round(score, 2), "total": len(tasks), "passed": passed, "rows": rows}


def main() -> None:
    p = argparse.ArgumentParser()
    p.add_argument("--mode", choices=["selfcheck", "single", "eval"], required=True)
    p.add_argument("--query", default="HF Agents Course에서 tool calling 학습 포인트 정리")
    p.add_argument("--input", help="eval 모드용 JSON 파일 경로")
    p.add_argument("--model", default="openai/gpt-4o-mini")
    p.add_argument("--offline", action="store_true")
    args = p.parse_args()

    if args.mode == "selfcheck":
        print(json.dumps(run_selfcheck(), ensure_ascii=False, indent=2))
        return

    if args.mode == "single":
        print(json.dumps(run_single(args.query, offline=args.offline, model_id=args.model), ensure_ascii=False, indent=2))
        return

    if args.mode == "eval":
        if not args.input:
            raise SystemExit("--input 이 필요합니다")
        data = json.loads(Path(args.input).read_text(encoding="utf-8"))
        if not isinstance(data, list):
            raise SystemExit("input JSON root must be list")
        tasks = [Task(**x) for x in data]
        print(json.dumps(run_eval(tasks, offline=args.offline, model_id=args.model), ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
