# 제관 부품별 공정 부여 게이트 (2026-07-19)
# 1) fab.html 로드 → 콘솔에러 0 확인
# 2) ROCK_SAW_1980 블럭 로드 → FRAME_GP_130 부품 선택 → 공정 5단계 표시 확인
# 3) 단계 추가 → 저장(/save-process) → process.json 갱신 확인 → 원복
import json, os, sys, io, shutil
from playwright.sync_api import sync_playwright

URL = "http://localhost:8090/fab.html"
D = r"E:\도진팩토리\3D스캔및티칭시스템"
PJSON = os.path.join(D, "fab_models", "parts", "ROCK_SAW_1980", "process.json")

# 원본 백업(테스트로 갱신되므로 끝나면 원복 — 예시 데이터 보존)
backup = None
if os.path.exists(PJSON):
    with open(PJSON, "r", encoding="utf-8") as f:
        backup = f.read()

result = {"js_exceptions": [], "ext_service_noise": [], "steps_shown": None, "saved_ok": None, "count_after_save": None}

# 외부 서비스(SHIN 엔진 8093 / 폰서버 8444) 미기동으로 인한 리소스 로드 실패는
# 이 기능(공정 부여)과 무관 — 별도 집계. JS 예외(pageerror)만 하드 실패로 본다.
def _con(m):
    if m.type == "error":
        t = m.text
        if "ERR_CONNECTION_REFUSED" in t or "Failed to load resource" in t:
            result["ext_service_noise"].append(t)
        else:
            result["js_exceptions"].append("CONSOLE:" + t)

with sync_playwright() as p:
    b = p.chromium.launch()
    pg = b.new_page(viewport={"width": 1400, "height": 900})
    pg.on("pageerror", lambda e: result["js_exceptions"].append(str(e)))
    pg.on("console", _con)
    pg.goto(URL)
    pg.wait_for_function("window.FAB && window.FAB.loadBlock", timeout=30000)
    pg.wait_for_timeout(1500)

    # 1) 블럭 로드 (index.json 의 ROCK_SAW 항목 parts 로 loadBlock 호출)
    pg.evaluate("""async () => {
        const r = await fetch('fab_models/parts/index.json',{cache:'no-store'});
        const d = await r.json();
        const blk = d.blocks.find(x=>x.model==='ROCK_SAW_1980');
        await window.FAB.loadBlock(blk.model, blk.parts);
    }""")
    pg.wait_for_timeout(2500)

    # 2) 부품 선택 시뮬레이션: fab_scene 은 클릭→#fab-selname 갱신.
    #    헤드리스에서 3D 픽킹은 불안정하므로, 선택의 계약(#fab-selname 텍스트 변경)을 직접 발생시켜
    #    fab_process 의 MutationObserver 경로를 검증한다.
    pg.evaluate("""() => {
        const s = document.getElementById('fab-selname');
        s.textContent = 'FRAME_GP_130';
    }""")
    pg.wait_for_timeout(800)

    # 패널에 표시된 단계 수 세기 (순번 원 = 24px 카드)
    steps = pg.evaluate("""() => {
        const box = document.querySelectorAll('#fab-canvas div');
        // 단계 입력창(공정명 placeholder) 개수로 카운트
        const names = document.querySelectorAll('input[placeholder^="공정명"]');
        const panelVisible = !!document.querySelector('#fab-canvas') &&
            Array.from(document.querySelectorAll('#fab-canvas > div')).some(d=>d.style.display==='flex');
        return { count: names.length, panelVisible };
    }""")
    result["steps_shown"] = steps

    pg.screenshot(path=os.path.join(D, "shots", "fab_process.png"))

    # 3) 단계 추가 → 저장
    pg.evaluate("""() => {
        // '＋ 단계 추가' 버튼 클릭
        const btns = Array.from(document.querySelectorAll('#fab-canvas button'));
        const add = btns.find(x=>x.textContent.indexOf('단계 추가')>=0);
        add.click();
    }""")
    pg.wait_for_timeout(400)
    # 저장 버튼 클릭
    save_resp = pg.evaluate("""async () => {
        const btns = Array.from(document.querySelectorAll('#fab-canvas button'));
        const save = btns.find(x=>x.textContent.indexOf('저장')>=0);
        save.click();
        return true;
    }""")
    pg.wait_for_timeout(1500)
    b.close()

# 저장된 파일 확인
if os.path.exists(PJSON):
    with open(PJSON, "r", encoding="utf-8") as f:
        data = json.load(f)
    result["count_after_save"] = len(data.get("parts", {}).get("FRAME_GP_130", []))
    result["saved_ok"] = True

# 판정: 콘솔에러 0, 단계 5개 표시, 저장 후 6개(5+추가1)
sc = result["steps_shown"] or {}
ok = (len(result["js_exceptions"]) == 0
      and sc.get("count") == 5
      and result["saved_ok"]
      and result["count_after_save"] == 6)

print(json.dumps(result, ensure_ascii=False, indent=1))
print("RESULT:", "PASS" if ok else "FAIL")

# 원복 (예시 데이터 보존)
if backup is not None:
    with open(PJSON, "w", encoding="utf-8") as f:
        f.write(backup)
    print("CLEANUP: process.json 원복(예시 데이터 보존)")

sys.exit(0 if ok else 1)
