"""Phase T 전체 파이프라인 시뮬레이션 (Stage 0 ~ Stage 5).

API 호출을 mock으로 대체하여 코드 경로 전체를 검증.
실제 Kei 응답(기존 run) + mock Sonnet/Selenium으로 전 Stage 통과 여부 확인.
"""
import asyncio
import json
import sys
import logging
from pathlib import Path
from unittest.mock import patch, AsyncMock, MagicMock

sys.path.insert(0, ".")
logging.basicConfig(level=logging.WARNING)

# ── 실제 데이터 로드 ──
RUN_DIR = Path("data/runs/1774922951020")
STAGE_0_DIR = Path("data/runs/20260401_151426")

stage0_ctx = json.loads((STAGE_0_DIR / "stage_0_context.json").read_text(encoding="utf-8"))
raw_content = stage0_ctx["raw_content"]
analysis_1a = json.loads((RUN_DIR / "step1_analysis.json").read_text(encoding="utf-8"))
concepts_1b = json.loads((RUN_DIR / "step1b_concepts.json").read_text(encoding="utf-8"))


# ── Mock 응답 정의 ──

async def mock_classify_content(content):
    """Stage 1A mock: 실제 Kei 응답 반환"""
    return analysis_1a


async def mock_refine_concepts(content, analysis):
    """Stage 1B mock: 실제 Kei 1B 응답을 analysis에 병합하여 반환"""
    result = dict(analysis)
    concepts = concepts_1b.get("concepts", [])
    for t in result.get("topics", []):
        match = next((c for c in concepts if c.get("id") == t.get("id")), None)
        if match:
            t["relation_type"] = match.get("relation_type", "")
            t["expression_hint"] = match.get("expression_hint", "")
            t["source_data"] = match.get("source_data", "")
    return result


# Stage 2 mock: generate_with_retry → mock HTML 반환
MOCK_BODY_HTML = """<div style="overflow:hidden; font-size:12px;">
<div class="bg" style="padding:10px;">
  <h3 style="font-size:11px;">용어 혼용</h3>
  <p style="font-size:11px;">DX와 BIM이 혼용되어 사용되고 있다</p>
</div>
<div style="height:12px;"></div>
<div class="core" style="padding:10px;">
  <h3 style="font-size:12px;">DX와 핵심기술의 올바른 관계</h3>
  <p style="font-size:12px;">DX는 BIM, GIS, 디지털트윈을 포함하는 상위개념이다</p>
  <div class="key-msg" style="font-size:14px; font-weight:bold;">BIM ≠ DX</div>
</div>
</div>"""

MOCK_SIDEBAR_HTML = """<div style="overflow:hidden; font-size:10px; padding-left:14px; text-indent:-14px;">
<h3 style="font-size:10px;">용어 정의</h3>
<div style="padding-left:14px; text-indent:-14px;">
  <p>건설산업: 종합산업</p>
  <p>BIM: 정보관리도구</p>
  <p>DX: 디지털 전환</p>
</div>
</div>"""

MOCK_FOOTER_HTML = """<div style="background:linear-gradient(135deg,#1e40af,#3b82f6); padding:14px 30px; text-align:center; border-radius:6px;">
<span style="font-size:14px; font-weight:bold; color:white;">BIM은 DX의 기초가 되는 일부분이다</span>
</div>"""

MOCK_GENERATED = {
    "body_html": MOCK_BODY_HTML,
    "sidebar_html": MOCK_SIDEBAR_HTML,
    "footer_html": MOCK_FOOTER_HTML,
    "reasoning": "mock",
}

MOCK_VERIFICATION = {}  # verify_all_areas 결과


async def mock_generate_with_retry(content, analysis, container_specs, preset, images=None):
    """Stage 2 mock"""
    from src.content_verifier import VerificationResult
    verification = {
        "body_bg": VerificationResult(passed=True, area_name="body_bg", score=1.0),
        "body_core": VerificationResult(passed=True, area_name="body_core", score=1.0),
        "sidebar": VerificationResult(passed=True, area_name="sidebar", score=1.0),
        "footer": VerificationResult(passed=True, area_name="footer", score=1.0),
    }
    return MOCK_GENERATED, verification


def mock_render_slide_from_html(generated, analysis, preset):
    """Stage 3 mock"""
    return f"""<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8">
<style>.slide {{width:1280px;height:720px;padding:40px;}}</style>
</head><body><div class="slide">
<div class="area-header"><h1>{analysis.get('title','')}</h1></div>
<div class="area-body">{generated.get('body_html','')}</div>
<div class="area-sidebar">{generated.get('sidebar_html','')}</div>
<div class="area-footer">{generated.get('footer_html','')}</div>
</div></body></html>"""


def mock_measure_rendered_heights(html):
    """Stage 4 L4 mock: overflow 없음"""
    return {
        "zones": {
            "body": {"scrollHeight": 400, "clientHeight": 490, "overflowed": False},
            "sidebar": {"scrollHeight": 300, "clientHeight": 490, "overflowed": False},
            "footer": {"scrollHeight": 55, "clientHeight": 60, "overflowed": False},
        }
    }


def mock_capture_slide_screenshot(html):
    """Stage 4 L5 mock: 빈 스크린샷"""
    return ""  # 빈 문자열 → 비전 품질 게이트 스킵


async def run_full_simulation():
    """전체 파이프라인 시뮬레이션"""
    passed = 0
    failed = 0

    def check(name, condition, detail=""):
        nonlocal passed, failed
        if condition:
            print(f"  ✅ {name}")
            passed += 1
        else:
            print(f"  ❌ {name}")
            if detail:
                print(f"     → {detail}")
            failed += 1

    # Mock 패치 적용
    # _retry_kei는 async fn을 await하는 래퍼이므로, mock도 await 해야 함
    async def mock_retry_kei(fn, *a, **kw):
        return await fn(*a, **kw)

    with patch("src.pipeline._retry_kei", side_effect=mock_retry_kei), \
         patch("src.kei_client.classify_content", side_effect=mock_classify_content), \
         patch("src.kei_client.refine_concepts", side_effect=mock_refine_concepts), \
         patch("src.content_verifier.generate_with_retry", side_effect=mock_generate_with_retry), \
         patch("src.renderer.render_slide_from_html", side_effect=mock_render_slide_from_html), \
         patch("src.slide_measurer.measure_rendered_heights", side_effect=mock_measure_rendered_heights), \
         patch("src.slide_measurer.capture_slide_screenshot", side_effect=mock_capture_slide_screenshot), \
         patch("src.image_utils.get_image_sizes", return_value={}), \
         patch("src.image_utils.embed_images", side_effect=lambda html, bp: html):

        from src.pipeline import generate_slide

        events = []
        print("── 전체 파이프라인 실행 ──")
        try:
            async for event in generate_slide(raw_content):
                events.append(event)
                evt_type = event.get("event", "")
                evt_data = event.get("data", "")
                if evt_type == "progress":
                    print(f"  📌 {evt_data}")
                elif evt_type == "error":
                    print(f"  ❌ ERROR: {evt_data}")
                elif evt_type == "result":
                    print(f"  📄 result: {len(evt_data)}자 HTML")
        except Exception as e:
            print(f"  💥 EXCEPTION: {type(e).__name__}: {e}")
            import traceback
            traceback.print_exc()
            check("파이프라인 예외 없음", False, str(e))
            print(f"\n{'═' * 55}")
            print(f"  전체 시뮬레이션: {passed} passed, {failed} failed")
            print(f"{'═' * 55}")
            return False

        print()

        # 이벤트 검증
        event_types = [e["event"] for e in events]
        check("progress 이벤트 존재", "progress" in event_types)
        check("error 이벤트 없음", "error" not in event_types,
              f"errors: {[e['data'] for e in events if e['event']=='error']}")
        check("result 이벤트 존재", "result" in event_types)

        # result HTML 검증
        result_events = [e for e in events if e["event"] == "result"]
        if result_events:
            html = result_events[0]["data"]
            check("HTML 비어있지 않음", len(html) > 100, f"길이: {len(html)}")
            check("HTML에 slide 클래스", "slide" in html)
            check("HTML에 body 영역", "area-body" in html or "body_html" in html or "bg" in html)
            check("HTML에 sidebar 영역", "area-sidebar" in html or "sidebar" in html)
            check("HTML에 footer 영역", "area-footer" in html or "footer" in html)
        else:
            check("result HTML", False, "result 이벤트 없음")

        # 스냅샷 파일 확인
        import glob
        latest_runs = sorted(glob.glob("data/runs/2026*"), reverse=True)
        if latest_runs:
            run_dir = latest_runs[0]
            files = [Path(f).name for f in glob.glob(f"{run_dir}/*.json")]
            print(f"\n  스냅샷 폴더: {Path(run_dir).name}")
            print(f"  저장된 파일: {files}")
            check("stage_0 스냅샷", "stage_0_context.json" in files)
            check("stage_1a 스냅샷", "stage_1a_context.json" in files)
            check("stage_1b 스냅샷", "stage_1b_context.json" in files)
            check("stage_1_5a 스냅샷", "stage_1_5a_context.json" in files)
            check("stage_1_7 스냅샷", "stage_1_7_context.json" in files)
            check("stage_1_5b 스냅샷", "stage_1_5b_context.json" in files)
            check("stage_2 스냅샷", "stage_2_context.json" in files)
            check("stage_3 스냅샷", "stage_3_context.json" in files)
            check("stage_4 스냅샷", "stage_4_context.json" in files)
            check("final 스냅샷", "final_context.json" in files)
            check("final.html 저장", "final.html" in [Path(f).name for f in glob.glob(f"{run_dir}/*")])
        else:
            check("스냅샷 폴더", False, "run 폴더 없음")

    print(f"\n{'═' * 55}")
    print(f"  전체 파이프라인 시뮬레이션: {passed} passed, {failed} failed")
    if failed == 0:
        print("  전체 통과 ✅")
    else:
        print(f"  ❌ {failed}개 실패")
    print(f"{'═' * 55}")
    return failed == 0


if __name__ == "__main__":
    success = asyncio.run(run_full_simulation())
    sys.exit(0 if success else 1)
