"""프로젝트 복제 — 기존 export/import 를 쓰고, 그 둘이 안 옮기는 것만 덧붙인다.

`ProjectExportService.export_json()` + `ProjectImportService.import_json()` 이
이미 있는 복제 경로다(모든 레코드에 새 UUID 발급 + FK 재매핑). 다만 두 가지를
안 옮긴다.

  ① `step_run` 행       — 없으면 파이프라인이 "아직 안 돈 것"으로 본다
  ② 체크포인트 파일     — 없으면 상류 산출물이 없어 특정 스텝만 재실행할 수 없다

체크포인트는 `C01`·`L07` 같은 short_id 로 되어 있어 UUID 재매핑 뒤에도 유효하다.

★쓰기 규칙 — 이 스크립트는 **추가만** 한다.
  - 원본 project_id / 원본 디렉토리에는 어떤 경우에도 쓰지 않는다
  - DB 는 INSERT 만. 원본 행을 UPDATE·DELETE 하지 않는다
  - 대상 디렉토리가 이미 있으면 중단한다(덮어쓰기 금지)
  - `--apply` 없이는 아무것도 쓰지 않는다

이미지(수 GB)는 기본 제외 — scene_director 재실행에는 필요 없다. `--images` 로 포함.
"""
from __future__ import annotations

import argparse
import shutil
import sys
import uuid
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
from app.services.project_export_service import (  # noqa: E402
    ProjectExportService,
    ProjectImportService,
)

SKIP_DIRS = {"images"}  # --images 로 포함


def _one(db, sql: str, **p):
    return db.execute(sql_text(sql), p).fetchone()


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("project_id")
    ap.add_argument("--name", default=None, help="복제본 이름")
    ap.add_argument("--images", action="store_true", help="images/ 도 복사 (수 GB)")
    ap.add_argument("--apply", action="store_true")
    a = ap.parse_args()
    dry = not a.apply

    db = SessionLocal()
    src_dir = Path(settings.projects_dir) / a.project_id
    row = _one(db, "select name from project_registry where id=:i", i=a.project_id)
    if not row:
        sys.exit(f"원본 프로젝트 없음: {a.project_id}")
    src_eps = db.execute(sql_text(
        "select id, title from episode where project_id=:i"), {"i": a.project_id}
    ).fetchall()
    n_step = _one(db, "select count(*) from step_run where project_id=:i",
                  i=a.project_id)[0]
    cp_dir = src_dir / "checkpoints"

    print(f"원본 : {row[0]}  ({a.project_id})")
    print(f"  에피소드 {len(src_eps)} · step_run {n_step}행 · 체크포인트 "
          f"{'있음' if cp_dir.exists() else '없음'}")
    print(f"  디렉토리 {src_dir}")
    if dry:
        print("\n[미리보기] --apply 를 붙이면 실제로 복제합니다.")
        print("  · export_json → import_json (새 UUID 전면 발급)")
        print("  · 체크포인트 디렉토리 복사 (에피소드 id 재매핑)")
        print(f"  · step_run {n_step}행 INSERT")
        print(f"  · images/ {'포함' if a.images else '제외'}")
        return

    # ── ① 기존 복제 경로 ────────────────────────────────────────────────
    actor = _one(db, "select id from user_account order by created_at limit 1")
    if not actor:
        sys.exit("actor 로 쓸 user_account 가 없다")
    data = ProjectExportService(db=db, project_id=a.project_id).export_json()
    new_pid = ProjectImportService(db=db, actor_id=actor[0]).import_json(
        data=data, new_name=a.name or f"{row[0]} (복제)")
    db.commit()
    if new_pid == a.project_id:
        sys.exit("복제본 id 가 원본과 같다 — 중단")
    print(f"\n복제본 : {new_pid}")

    # 에피소드 old→new 매핑 (번호로 짝짓는다)
    new_eps = db.execute(sql_text(
        "select id, episode_number from episode where project_id=:i"),
        {"i": new_pid}).fetchall()
    old_eps = db.execute(sql_text(
        "select id, episode_number from episode where project_id=:i"),
        {"i": a.project_id}).fetchall()
    ep_map = {o[0]: n[0] for o in old_eps for n in new_eps if o[1] == n[1]}
    print(f"  에피소드 매핑 {len(ep_map)}건")

    dst_dir = Path(settings.projects_dir) / new_pid

    # ── ② 체크포인트 복사 (에피소드 id 재매핑) ──────────────────────────
    if cp_dir.exists():
        for sub in cp_dir.iterdir():
            if sub.name != "episodes":
                dst = dst_dir / "checkpoints" / sub.name
                if dst.exists():
                    sys.exit(f"대상이 이미 있다 — 중단: {dst}")
                shutil.copytree(sub, dst) if sub.is_dir() else shutil.copy2(sub, dst)
                continue
            for old_ep in sub.iterdir():
                new_ep = ep_map.get(old_ep.name)
                if not new_ep:
                    print(f"  ! 매핑 없는 에피소드 건너뜀: {old_ep.name}")
                    continue
                dst = dst_dir / "checkpoints" / "episodes" / new_ep
                if dst.exists():
                    sys.exit(f"대상이 이미 있다 — 중단: {dst}")
                dst.parent.mkdir(parents=True, exist_ok=True)
                shutil.copytree(old_ep, dst)
                print(f"  체크포인트 복사 {old_ep.name} → {new_ep}")

    # ── ③ step_run 복사 ────────────────────────────────────────────────
    cols = [r[0] for r in db.execute(sql_text(
        "select column_name from information_schema.columns "
        "where table_name='step_run' order by ordinal_position")).fetchall()]
    rows = db.execute(sql_text(
        f"select {','.join(cols)} from step_run where project_id=:i"),
        {"i": a.project_id}).fetchall()
    n = 0
    for r in rows:
        d = dict(zip(cols, r))
        d["project_id"] = new_pid
        if d.get("episode_id"):
            d["episode_id"] = ep_map.get(d["episode_id"], d["episode_id"])
        # step_run.id 는 TEXT NOT NULL 이고 서버 기본값이 없다 — 새로 발급한다.
        d["id"] = str(uuid.uuid4())
        keys = [k for k in d]
        db.execute(sql_text(
            f"insert into step_run ({','.join(keys)}) "
            f"values ({','.join(':' + k for k in keys)})"), d)
        n += 1
    db.commit()
    print(f"  step_run {n}행 복사")

    # ── ④ 나머지 디렉토리 ──────────────────────────────────────────────
    for sub in src_dir.iterdir():
        if sub.name == "checkpoints":
            continue
        if sub.name in SKIP_DIRS and not a.images:
            print(f"  images/ 제외 (필요하면 --images)")
            continue
        dst = dst_dir / sub.name
        if dst.exists() and any(dst.iterdir()):
            print(f"  이미 있어 건너뜀: {sub.name}")
            continue
        if dst.exists():
            dst.rmdir()
        shutil.copytree(sub, dst) if sub.is_dir() else shutil.copy2(sub, dst)
        print(f"  복사 {sub.name}")

    print(f"\n완료 — 복제본 project_id = {new_pid}")
    for o, nw in ep_map.items():
        print(f"        episode {o} → {nw}")


if __name__ == "__main__":
    main()
