"""Skill harness — LLM 에이전트가 skills/ 가이드를 로드해 export 코드를 생성·실행한다.

LLM 이 생성한 완성 HTML(을 렌더한 PNG)을 레이아웃 레퍼런스로, 구조화 데이터(view)를 컨텐츠로
주고, 포맷별 SKILL.md 가이드를 컨텍스트에 넣어 빌더 코드를 생성한 뒤 실행한다. 실패 시 1회 자가수정.

- pptx: Node + pptxgenjs (run_pptx_builder.mjs)
- docx: Node + docx-js   (run_docx_builder.mjs)
- xlsx: Python + openpyxl (run_xlsx_builder.py)
"""
from __future__ import annotations

import base64
import json
import re
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any

from app import prompts as _p
from app.config import BACKEND_ROOT, settings
from app.services import llm as llm_service

NODE_DIR = BACKEND_ROOT / "export_node"

# 포맷별 설정: (스킬 디렉터리, 가이드 파일들, 빌더 확장자, 러너, 코드언어)
_FMT = {
    "pptx": {
        "skill_files": ("SKILL.md", "pptxgenjs.md"),
        "builder_ext": "cjs",
        "runner": ["node", str(NODE_DIR / "run_pptx_builder.mjs")],
        "lang": "javascript",
    },
    "docx": {
        "skill_files": ("SKILL.md",),
        "builder_ext": "cjs",
        "runner": ["node", str(NODE_DIR / "run_docx_builder.mjs")],
        "lang": "javascript",
    },
    "xlsx": {
        "skill_files": ("SKILL.md",),
        "builder_ext": "py",
        "runner": [sys.executable, str(NODE_DIR / "run_xlsx_builder.py")],
        "lang": "python",
    },
}


def _read_skill(fmt: str, name: str) -> str:
    try:
        return settings.skills_path.joinpath(fmt, name).read_text(encoding="utf-8")
    except OSError:
        return ""


def _skill_context(fmt: str) -> str:
    parts = []
    for fname in _FMT[fmt]["skill_files"]:
        parts.append(f"=== skills/{fmt}/{fname} ===\n{_read_skill(fmt, fname)}")
    return "\n\n".join(parts)


def _strip_code(text: str) -> str:
    t = text.strip()
    t = re.sub(r"^```(?:javascript|js|python|py)?", "", t).strip()
    t = re.sub(r"```$", "", t).strip()
    return t


def _generate_builder_code(
    fmt: str,
    sample_view: dict[str, Any],
    ref_png: bytes | None = None,
    prev_code: str = "",
    error: str = "",
) -> str:
    """포맷별 빌더 코드 생성. ref_png(완성 HTML 렌더 이미지)를 레이아웃 레퍼런스로 멀티모달 주입."""
    from langchain_core.messages import HumanMessage, SystemMessage

    system = _p.get(fmt, "system")
    repair = _p.get(fmt, "repair_suffix")
    user_text = (
        "[SKILL GUIDES]\n" + _skill_context(fmt)[:16000]
        + "\n\n[PAGE VIEW (structured data — use values verbatim)]\n"
        + json.dumps(sample_view, ensure_ascii=False)
        + "\n\nThe attached image is the RENDERED REFERENCE of the finished report page. "
        "Reproduce its layout/grouping/emphasis; use the structured data for content."
    )
    if error:
        user_text += repair.format(prev=prev_code[:6000], error=error[:1500])

    content: Any = user_text
    if ref_png is not None:
        b64 = base64.b64encode(ref_png).decode("ascii")
        content = [
            {"type": "text", "text": user_text},
            {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
        ]
    llm = llm_service.get_llm()
    resp = llm.invoke([SystemMessage(content=system), HumanMessage(content=content)])
    raw = resp.content if isinstance(resp.content, str) else str(resp.content)
    return _strip_code(raw)


def _run_builder(fmt: str, builder_code: str, payload: Any, out_path: Path) -> tuple[bool, str]:
    cfg = _FMT[fmt]
    with tempfile.TemporaryDirectory() as tmp:
        builder_path = Path(tmp) / f"builder.{cfg['builder_ext']}"
        builder_path.write_text(builder_code, encoding="utf-8")
        data_path = Path(tmp) / "data.json"
        data_path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
        proc = subprocess.run(
            [*cfg["runner"], str(builder_path), str(data_path), str(out_path)],
            capture_output=True, text=True, cwd=str(NODE_DIR), timeout=180,
        )
    ok = proc.returncode == 0 and out_path.exists()
    return ok, (proc.stderr or proc.stdout)


def build(
    fmt: str,
    pages: list[dict[str, Any]],
    out_path: Path,
    ref_png: bytes | None = None,
    cached_code: str | None = None,
) -> tuple[Path, str]:
    """포맷별 한 파일을 만든다(페이지당 1슬라이드/섹션/시트). 실패 시 1회 자가수정.

    pages = [view, view, ...]. ref_png = 완성 HTML 1페이지 렌더 이미지(레이아웃 레퍼런스).
    (out_path, 최종 builder_code) 반환.
    """
    if fmt not in _FMT:
        raise ValueError(f"하네스 미지원 포맷: {fmt}")
    sample = pages[0] if pages else {}
    payload = {"pages": pages}
    code = cached_code or _generate_builder_code(fmt, sample, ref_png)
    ok, err = _run_builder(fmt, code, payload, out_path)
    if not ok:
        code = _generate_builder_code(fmt, sample, ref_png, prev_code=code, error=err)
        ok, err = _run_builder(fmt, code, payload, out_path)
        if not ok:
            raise RuntimeError(f"{fmt.upper()} 생성 실패(자가수정 후): {err}")
    return out_path, code


# 하위호환: 기존 호출부 유지
def build_pptx(
    pages: list[dict[str, Any]], out_path: Path, cached_code: str | None = None
) -> tuple[Path, str]:
    return build("pptx", pages, out_path, ref_png=None, cached_code=cached_code)


def generate_pptx_builder_code(vm: dict[str, Any], prev_code: str = "", error: str = "") -> str:
    return _generate_builder_code("pptx", vm, ref_png=None, prev_code=prev_code, error=error)
