"""Phase T 전수 검사.

1. 모든 파일 syntax
2. 모든 import chain
3. pipeline.py 내 이름 참조
4. lazy import 유효성
5. catalog.yaml
6. Pydantic 모델
7. 실제 데이터 Stage 0~1.5b
8. Stage 3 render 호출
9. Stage 2 supplement 생성
"""
import ast, re, json, sys
from pathlib import Path
sys.path.insert(0, ".")

errors = []

def check(name, condition, detail=""):
    if condition:
        print(f"  OK  {name}")
    else:
        print(f"  FAIL {name} -- {detail}")
        errors.append(f"{name}: {detail}")


print("-- 1. Syntax --")
for f in Path("src").glob("*.py"):
    try:
        ast.parse(f.read_text(encoding="utf-8"))
        print(f"  OK  {f.name}")
    except SyntaxError as e:
        print(f"  FAIL {f.name}: {e}")
        errors.append(f"syntax: {f.name}")

print("\n-- 2. Import --")
for mod in ["src.pipeline_context", "src.mdx_normalizer", "src.validators",
            "src.block_reference", "src.space_allocator", "src.html_generator",
            "src.content_verifier", "src.renderer", "src.kei_client",
            "src.image_utils", "src.slide_measurer", "src.config",
            "src.main", "src.pipeline"]:
    try:
        __import__(mod)
        print(f"  OK  {mod}")
    except Exception as e:
        print(f"  FAIL {mod}: {e}")
        errors.append(f"import: {mod}")

print("\n-- 3. pipeline.py import 참조 --")
psrc = Path("src/pipeline.py").read_text(encoding="utf-8")
needed = ["PipelineContext", "Topic", "NormalizedContent", "Analysis",
          "PageStructure", "ContainerInfo", "TextBudget", "DesignBudget",
          "FontHierarchy", "BlockReference", "StageFailure",
          "build_retry_feedback", "create_context"]
import_block = re.search(r"from src\.pipeline_context import \((.*?)\)", psrc, re.DOTALL)
imported = set()
if import_block:
    imported = {n.strip() for n in import_block.group(1).split(",") if n.strip()}
for name in needed:
    if name in psrc and name not in imported:
        # 메서드인지 확인
        is_method = all(("." + name) in line or name not in line
                       for line in psrc.split("\n")
                       if "from src.pipeline_context" not in line)
        if not is_method:
            check(f"import {name}", False, "사용되지만 import 안 됨")
        else:
            check(f"import {name}", True)
    else:
        check(f"import {name}", name in imported or name not in psrc)

print("\n-- 4. lazy import --")
for mod_name, func_name in re.findall(r"from (src\.\w+) import (\w+)", psrc):
    if "pipeline_context" in mod_name:
        continue
    try:
        mod = __import__(mod_name, fromlist=[func_name])
        check(f"{mod_name}.{func_name}", hasattr(mod, func_name))
    except Exception as e:
        check(f"{mod_name}.{func_name}", False, str(e))

print("\n-- 5. catalog.yaml --")
import yaml
data = yaml.safe_load(Path("templates/catalog.yaml").read_text(encoding="utf-8"))
blocks = data.get("blocks", [])
check("blocks count", len(blocks) == 38, f"got {len(blocks)}")
check("schema 38/38", sum(1 for b in blocks if b.get("schema")) == 38)
check("visual_diff 20", sum(1 for b in blocks if b.get("visual_diff")) == 20)

print("\n-- 6. Pydantic --")
from src.pipeline_context import *
check("create_context", create_context("test") is not None)
check("FontHierarchy OK", FontHierarchy(key_msg=14, core=12, bg=11, sidebar=10) is not None)
try:
    FontHierarchy(key_msg=10, core=12, bg=14, sidebar=9)
    check("FontHierarchy violation", False, "not caught")
except:
    check("FontHierarchy violation", True)
check("Topic no weight", "weight" not in Topic.model_fields)
check("DesignBudget", DesignBudget(available_height_px=100) is not None)

print("\n-- 7. 실제 데이터 Stage 0~1.5b --")
s0 = json.loads(Path("data/runs/20260401_151426/stage_0_context.json").read_text(encoding="utf-8"))
a1 = json.loads(Path("data/runs/1774922951020/step1_analysis.json").read_text(encoding="utf-8"))
c1b = json.loads(Path("data/runs/1774922951020/step1b_concepts.json").read_text(encoding="utf-8"))

# 1A
topics = [Topic(**{k: v for k, v in t.items() if k in Topic.model_fields}) for t in a1["topics"]]
check("1A Topic 변환", len(topics) == 5)

# 1B
concepts = c1b.get("concepts", [])
updated = []
for t in topics:
    m = next((c for c in concepts if c.get("id") == t.id), None)
    if m:
        updated.append(t.model_copy(update={
            "relation_type": m.get("relation_type", ""),
            "expression_hint": m.get("expression_hint", ""),
            "source_data": m.get("source_data", ""),
        }))
    else:
        updated.append(t)
check("1B 병합", len(updated) == 5)

# 검증
from src.validators import validate_stage_1a, validate_stage_1b
e1a = validate_stage_1a(a1, s0["normalized"]["clean_text"])
check("1A 검증", not e1a, str(e1a)[:100] if e1a else "")
e1b = validate_stage_1b([t.model_dump() for t in updated], s0["normalized"]["clean_text"], raw_content=s0["raw_content"])
check("1B 검증", not e1b, str(e1b)[:100] if e1b else "")

# 1.5a
from src.space_allocator import calculate_font_hierarchy, calculate_dynamic_ratio, calculate_container_specs, calculate_design_budget
from src.design_director import LAYOUT_PRESETS, select_preset
from src.block_reference import select_and_generate_references

ctx = create_context(s0["raw_content"])
ctx = ctx.model_copy(update={
    "normalized": NormalizedContent(**s0["normalized"]),
    "topics": updated,
    "page_structure": PageStructure(roles=a1.get("page_structure", {})),
    "analysis": Analysis(core_message=a1.get("core_message", ""), title=a1.get("title", "")),
})
rtl = {role: len(ctx.get_role_content(role)) for role in ["배경", "본심", "첨부", "결론"]}
fh_dict = calculate_font_hierarchy(rtl)
fh = FontHierarchy(key_msg=fh_dict["핵심"], core=fh_dict["본심"], bg=fh_dict["배경"], sidebar=fh_dict["첨부"])
check("1.5a 폰트위계", fh.key_msg > fh.core >= fh.bg > fh.sidebar)

ratio = calculate_dynamic_ratio(rtl, fh_dict)
check("1.5a 비율", ratio[0] + ratio[1] == 100)

preset_name = select_preset(a1)
preset = LAYOUT_PRESETS.get(preset_name, {})
specs = calculate_container_specs(a1.get("page_structure", {}), [t.model_dump() for t in updated], preset)
check("1.5a 컨테이너", len(specs) >= 3)

# 1.7
refs = select_and_generate_references([t.model_dump() for t in updated], specs, a1.get("page_structure", {}))
check("1.7 참고블록", len(refs) >= 3)

# 1.5b
for role, spec in specs.items():
    ref = refs.get(role, {})
    schema = ref.get("schema_info", {})
    font_map = {"본심": fh.core, "배경": fh.bg, "첨부": fh.sidebar, "결론": fh.core}
    budget = calculate_design_budget(spec.height_px, spec.width_px, schema, font_map.get(role, 12))
    db = DesignBudget(**budget)
    check(f"1.5b {role}", True)

print("\n-- 8. Stage 3 render --")
from src.renderer import render_slide_from_html
mock_gen = {
    "body_html": '<div style="overflow:hidden"><div class="key-msg">test</div></div>',
    "sidebar_html": '<div style="overflow:hidden; padding-left:14px; text-indent:-14px;">side</div>',
    "footer_html": "<div>foot</div>",
}
analysis_dict = {
    "topics": [t.model_dump() for t in updated],
    "page_structure": a1.get("page_structure", {}),
    "core_message": a1.get("core_message", ""),
    "title": a1.get("title", ""),
}
html = render_slide_from_html(mock_gen, analysis_dict, preset)
check("Stage 3 render", len(html) > 100, f"len={len(html)}")

print("\n-- 9. Stage 2 supplement --")
from src.html_generator import _build_phase_t_supplement
phase_t_ctx = {
    "font_hierarchy": fh.model_dump(),
    "container_ratio": ratio,
    "references": {r: v for r, v in refs.items()},
    "design_budgets": {},
}
for role in ["배경", "본심", "첨부", "결론"]:
    supp = _build_phase_t_supplement(role, {"phase_t": phase_t_ctx})
    check(f"supplement {role}", len(supp) > 50, f"len={len(supp)}")

# 결과
print()
if errors:
    print(f"=== FAIL: {len(errors)}건 ===")
    for e in errors:
        print(f"  - {e}")
else:
    print("=== 전수 검사 통과: 오류 0건 ===")

sys.exit(1 if errors else 0)
