"""복제본에서 scene_director 만 다시 돌려 결함이 재현되는지 본다.

8/4 실행은 샷 기술이 지명한 인물의 52% 를 씬 배정에 넣지 못했다(7/28 실행은
1%). 같은 팩(`9.202605181550`)·같은 모델 별칭이었으므로, 남은 가설은 둘이다.

  ① 구조적 결함  — 지금 돌려도 같은 수준으로 재현된다
  ② 나쁜 뽑기    — 다시 돌리면 7/28 수준으로 돌아온다

★이 스크립트는 **복제본에만** 쓴다. 원본 project_id 를 받으면 중단한다.
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from sqlalchemy import text as sql_text  # noqa: E402

from app.core.config import settings  # noqa: E402
from app.core.database import SessionLocal  # noqa: E402

SOURCE_PROJECTS = {  # 원본 — 절대 대상이 될 수 없다
    "e716bafb-24bb-42b7-aea0-fdb383844ee8",
    "475e5694-7c25-4f3f-8dec-910412a1761d",
}


def base(n: str) -> str:
    return re.sub(r"\([^)]*\)", "", n or "").strip()


def measure(db, project_id: str, episode_id: str) -> tuple[int, int]:
    """샷 기술이 지명한 인물 중 씬 배정에 없는 비율."""
    rows = db.execute(sql_text(
        "select short_id, name from entity_canon "
        "where project_id=:p and entity_type='character'"), {"p": project_id}).fetchall()
    sid2n = {r[0]: base(r[1]) for r in rows if r[0]}
    names = sorted({v for v in sid2n.values() if v}, key=len, reverse=True)
    shots = db.execute(sql_text(
        "select scene_index, string_agg(shot_description,' ') from scene_still "
        "where episode_id=:e and is_selected=true group by scene_index"),
        {"e": episode_id}).fetchall()
    txt = {r[0]: (r[1] or "") for r in shots}
    cp = json.loads((Path(settings.projects_dir) / project_id / "checkpoints"
                     / "episodes" / episode_id / "scene_director"
                     / "manifest.json").read_text("utf-8"))
    tot = miss = 0
    for s in (cp.get("data") or {}).get("scenes") or []:
        t = txt.get(s.get("scene_index"))
        if not t:
            continue
        named = {n for n in names if n and n in t}
        if not named:
            continue
        assigned = {sid2n.get(i) for i in (s.get("present_entity_ids") or [])} - {None}
        tot += 1
        if named - assigned:
            miss += 1
    return miss, tot


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("project_id")
    ap.add_argument("episode_id")
    a = ap.parse_args()
    if a.project_id in SOURCE_PROJECTS:
        sys.exit("원본 프로젝트다 — 중단")

    db = SessionLocal()
    name = db.execute(sql_text("select name from project_registry where id=:i"),
                      {"i": a.project_id}).fetchone()
    print(f"대상 : {name[0] if name else '?'}  ({a.project_id})")

    before = measure(db, a.project_id, a.episode_id)
    print(f"재실행 전 : 인물 지명 씬 {before[1]} 중 누락 {before[0]} "
          f"({100*before[0]/max(before[1],1):.0f}%)")

    from app.core.steps.analysis_steps_legacy import SceneDirectorStep
    from app.services.step_readmodel_service import _load_project_config

    cfg = _load_project_config(db, a.project_id)
    runner = SceneDirectorStep(
        step_id="scene_director", project_id=a.project_id,
        episode_id=a.episode_id, db=db, project_config=cfg)
    print("실행 중 … (단일 호출, 씬 전체)")
    data = runner._execute(mode="rerun")
    runner.save_checkpoint(data)
    db.commit()

    after = measure(db, a.project_id, a.episode_id)
    print(f"재실행 후 : 인물 지명 씬 {after[1]} 중 누락 {after[0]} "
          f"({100*after[0]/max(after[1],1):.0f}%)")
    print(f"\n원본 8/4 실행 = 74% · 7/28 실행 = 76% (씬 원문 기준)")
    print(f"샷 기술 기준 원본 8/4 = 73% · 7/28 = 4%")


if __name__ == "__main__":
    main()
