#!/usr/bin/env python3
"""W22 야외 직행 fresh E2E — 64스텝(이미지 포함) 순차 실행.

e2e_v16corr_runner.py 복제 + 이미지 스텝 타임아웃 확장 + 실패 시 resume 재시도
(E2E 승인=예산 무제한 완주 규칙 — cap 소진 시 resume 반복).
usage: backend/.venv/bin/python e2e_w22_runner.py
"""
import json
import sys
import time
from pathlib import Path

import requests

BASE = "http://localhost:8000"
ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")
STATE = json.loads((ROOT / "scratchpad" / "e2e_w22b_state.json").read_text())
PID, EID = STATE["project_id"], STATE["episode_id"]
_steps_doc = json.loads((ROOT / "scratchpad" / "e2e_w22b_steps.json").read_text())
STEPS = _steps_doc["runnable"]
CATEGORY = _steps_doc.get("category", {})

TERMINAL_OK = {"completed", "skipped", "not_applicable"}
TERMINAL_BAD = {"failed"}

# W22 flag ON 설계: background_render 는 야외 bg 를 skipped_outdoor_direct 로
# 남기므로 step 상태가 항상 partial — failed=0 이면 정상 통과로 취급.
PARTIAL_OK_STEPS = {"background_render"}


def _is_terminal_ok(sid, st):
    cur = st.get("status")
    if cur in TERMINAL_OK:
        return True
    return (
        sid in PARTIAL_OK_STEPS
        and cur == "partial"
        and not st.get("failed_count")
    )
MAX_RESUME_RETRY = 5  # 실패 시 resume 재시도 횟수 (cap 소진 대비)

s = requests.Session()


def login():
    r = s.post(f"{BASE}/api/v1/auth/login",
               json={"username": "admin", "password": "admin123"}, timeout=30)
    r.raise_for_status()


def status_map():
    r = s.get(f"{BASE}/api/v1/projects/{PID}/episodes/{EID}/steps", timeout=60)
    if r.status_code == 401:
        login()
        r = s.get(f"{BASE}/api/v1/projects/{PID}/episodes/{EID}/steps",
                  timeout=60)
    r.raise_for_status()
    return {x["step_id"]: x for x in r.json()["steps"]}


def step_timeout(sid):
    # 이미지 스텝은 대량 생성 가능 — 넉넉히
    return 6 * 3600 if CATEGORY.get(sid) == "image" else 5400


def run_once(sid, attempt):
    t0 = time.time()
    st = status_map().get(sid) or {}
    if _is_terminal_ok(sid, st):
        print(f"[{sid}] already {st.get('status')}", flush=True)
        return "ok"
    r = s.post(f"{BASE}/api/v1/projects/{PID}/episodes/{EID}/steps/{sid}",
               params={"mode": "resume"}, timeout=120)
    if r.status_code == 401:
        login()
        r = s.post(f"{BASE}/api/v1/projects/{PID}/episodes/{EID}/steps/{sid}",
                   params={"mode": "resume"}, timeout=120)
    if r.status_code != 200:
        print(f"[{sid}] start FAILED http={r.status_code} {r.text[:300]}",
              flush=True)
        return "start_failed"
    print(f"[{sid}] started (attempt {attempt})", flush=True)
    limit = step_timeout(sid)
    while True:
        time.sleep(30)
        try:
            st = status_map().get(sid) or {}
        except Exception as exc:
            print(f"[{sid}] poll error {exc}", flush=True)
            continue
        cur = st.get("status")
        if _is_terminal_ok(sid, st):
            print(f"[{sid}] {cur} ({int(time.time()-t0)}s) "
                  f"done={st.get('completed_count')}/{st.get('applicable_count')}"
                  f" failed={st.get('failed_count')}", flush=True)
            return "ok"
        if cur in TERMINAL_BAD:
            print(f"[{sid}] FAILED ({int(time.time()-t0)}s) — "
                  f"{json.dumps(st, ensure_ascii=False)[:500]}", flush=True)
            return "failed"
        if time.time() - t0 > limit:
            print(f"[{sid}] TIMEOUT {limit}s (status={cur})", flush=True)
            return "timeout"


def run_step(sid):
    for attempt in range(1, MAX_RESUME_RETRY + 1):
        res = run_once(sid, attempt)
        if res == "ok":
            return True
        if res in ("failed", "timeout", "start_failed"):
            if attempt < MAX_RESUME_RETRY:
                print(f"[{sid}] retrying resume "
                      f"({attempt}/{MAX_RESUME_RETRY})…", flush=True)
                time.sleep(20)
                continue
    return False


def main():
    login()
    print(f"target {len(STEPS)} steps | pid={PID} eid={EID}", flush=True)
    for sid in STEPS:
        if not run_step(sid):
            print(f"STOP at {sid}", flush=True)
            sys.exit(1)
    print("ALL STEPS DONE", flush=True)


if __name__ == "__main__":
    main()
