# pyright: reportMissingImports=false
"""IDS Phase 1 회귀 테스트 — 카드뉴스 고급화 (60 design-md × 12 satori + hybrid 5종).

검증 시나리오 (IDS plan §0.4):
1. 5 카테고리 × 1+ Stratified Sampling
2. Hybrid 패턴 H1~H5 각 1건
3. 한글 100% (구조 검증, OCR은 별도)
4. 5 사이즈 자동
5. 잘못된 design-md → fallback
6. 외부 API 직접 호출 시도 mock → 차단 확인 (IDS §0.5)

회귀 0: task-2381/2384/2387 보존 (외부 인프라 미수정).
"""

from __future__ import annotations

import json
import sys
from pathlib import Path

import pytest

WORKSPACE_ROOT = Path("/home/jay/workspace")
SATORI_SKILL = WORKSPACE_ROOT / "skills" / "satori-cardnews"
HYBRID_SKILL = WORKSPACE_ROOT / "skills" / "hybrid-image"
TEMPLATES_DIR = SATORI_SKILL / "templates"

sys.path.insert(0, str(SATORI_SKILL))
sys.path.insert(0, str(HYBRID_SKILL))


# ---------------------------------------------------------------------------
# 5 카테고리 × 1+ Stratified Sampling
# ---------------------------------------------------------------------------

CATEGORY_BRANDS = {
    "finance": ["stripe"],
    "saas": ["supabase", "linear.app", "mongodb"],
    "consumer": ["airbnb", "spotify", "intercom"],
    "luxury": ["ferrari", "lamborghini", "bmw"],
    "tech_minimal": ["apple", "raycast", "claude"],
}


@pytest.fixture(scope="module")
def loader():
    from design_md_loader import load_design_md

    return load_design_md


@pytest.mark.parametrize("category,brand_options", list(CATEGORY_BRANDS.items()))
def test_design_md_load_per_category(loader, category, brand_options):
    """각 카테고리당 최소 1개 브랜드의 디자인 토큰을 정상 추출한다."""
    available = [b for b in brand_options if (WORKSPACE_ROOT / "resources" / "design-md" / b / "DESIGN.md").exists()]
    assert available, f"category {category} has no available brand"
    brand = available[0]
    tokens = loader(brand)
    # 필수 키 검증
    for key in ("primary", "secondary", "accent", "background", "text_primary", "font_display", "font_body", "spacing", "border_radius"):
        assert key in tokens, f"{brand}: missing token {key}"
    # 색상 형식
    assert tokens["primary"].startswith("#"), f"{brand} primary not hex"
    # 폰트는 한글 fallback 차단 — 시스템 sans-serif 미포함 보장
    assert "sans-serif" not in tokens["font_display"].lower()
    assert "arial" not in tokens["font_display"].lower()


# ---------------------------------------------------------------------------
# 12 매거진 템플릿 검증
# ---------------------------------------------------------------------------

EXPECTED_TEMPLATES = {
    "magazine", "grid-4", "grid-6", "asymmetric", "typography-focus",
    "infographic", "storytelling", "minimal", "monochrome", "colorful",
    "corporate", "cover",
}


def test_all_12_templates_exist():
    found = {p.stem for p in TEMPLATES_DIR.glob("*.html")}
    missing = EXPECTED_TEMPLATES - found
    assert not missing, f"missing templates: {missing}"


@pytest.mark.parametrize("template_id", sorted(EXPECTED_TEMPLATES))
def test_template_has_3_files(template_id):
    """각 템플릿은 .html + .css + .json 3-파일 구성이어야 한다."""
    base = TEMPLATES_DIR / template_id
    assert (base.with_suffix(".html")).exists(), f"{template_id}.html missing"
    assert (base.with_suffix(".css")).exists(), f"{template_id}.css missing"
    assert (base.with_suffix(".json")).exists(), f"{template_id}.json missing"


@pytest.mark.parametrize("template_id", sorted(EXPECTED_TEMPLATES))
def test_template_html_satori_compatible(template_id):
    """Satori 호환: <div style= 시작, 종료 태그 포함, 한글 폰트 명시."""
    html = (TEMPLATES_DIR / f"{template_id}.html").read_text(encoding="utf-8")
    assert "<div" in html and "style=" in html, f"{template_id} not Satori-style"
    assert "</div>" in html, f"{template_id} no closing tag"
    # 한글 폰트 fallback 차단 — Pretendard 또는 Noto Sans KR 명시
    assert ("Pretendard" in html) or ("Noto Sans KR" in html), f"{template_id} korean font missing"


@pytest.mark.parametrize("template_id", sorted(EXPECTED_TEMPLATES))
def test_template_json_schema(template_id):
    schema = json.loads((TEMPLATES_DIR / f"{template_id}.json").read_text(encoding="utf-8"))
    for key in ("id", "name", "description", "required_vars", "default_size"):
        assert key in schema, f"{template_id}.json missing {key}"
    assert schema["id"] == template_id


# ---------------------------------------------------------------------------
# Hybrid 패턴 H1~H5
# ---------------------------------------------------------------------------


def test_hybrid_patterns_5_export():
    from patterns import PATTERNS

    assert set(PATTERNS.keys()) == {"h1", "h2", "h3", "h4", "h5"}


@pytest.mark.parametrize("pattern_id", ["h1", "h2", "h3", "h4", "h5"])
def test_hybrid_pattern_signature(pattern_id):
    """모든 패턴의 render() 시그니처가 일관되어야 한다."""
    import inspect

    from patterns import PATTERNS

    sig = inspect.signature(PATTERNS[pattern_id])
    params = list(sig.parameters.keys())
    for required in ("title", "body", "output_path", "size", "design_tokens", "background_path", "prompt_hint"):
        assert required in params, f"{pattern_id} missing param: {required}"


# ---------------------------------------------------------------------------
# 5 사이즈 자동
# ---------------------------------------------------------------------------


def test_5_sizes():
    from sizes import SIZES, get_size, list_platforms

    expected = {"instagram", "facebook", "twitter", "threads", "naver"}
    assert set(SIZES.keys()) == expected
    assert get_size("instagram") == (1080, 1080)
    assert get_size("facebook") == (1200, 630)
    assert get_size("twitter") == (1200, 675)
    assert get_size("threads") == (1080, 1350)
    assert get_size("naver") == (800, 800)
    # 미존재 플랫폼은 fallback
    assert get_size("nonexistent") == (1080, 1080)
    assert set(list_platforms()) == expected


# ---------------------------------------------------------------------------
# 잘못된 design-md → fallback
# ---------------------------------------------------------------------------


def test_invalid_brand_raises_filenotfound(loader):
    with pytest.raises(FileNotFoundError):
        loader("nonexistent-brand-xyz-12345")


def test_fallback_tokens_safe():
    from design_md_loader import fallback_tokens

    fb = fallback_tokens()
    for key in ("primary", "secondary", "accent", "background", "text_primary", "font_display", "font_body"):
        assert key in fb
    assert fb["primary"].startswith("#")
    assert "Pretendard" in fb["font_display"]


# ---------------------------------------------------------------------------
# 외부 API 직접 호출 mock 차단 (IDS §0.5)
# ---------------------------------------------------------------------------


def test_no_direct_api_imports():
    """skills/satori-cardnews 와 skills/hybrid-image/patterns 안의 모든 .py 파일에
    `import openai`, `import anthropic`, `from google.generativeai`,
    `api.openai.com` 직접 사용이 0건이어야 한다 (docstring/주석 제외).
    """
    forbidden_imports = ("import openai", "import anthropic", "import google.generativeai",
                          "from openai ", "from anthropic ", "from google.generativeai")
    forbidden_urls = ("api.openai.com", "api.anthropic.com", "generativelanguage.googleapis.com")

    targets = list(SATORI_SKILL.rglob("*.py")) + list((HYBRID_SKILL / "patterns").rglob("*.py"))
    for path in targets:
        text = path.read_text(encoding="utf-8")
        for pattern in forbidden_imports:
            for line in text.splitlines():
                stripped = line.strip()
                # 주석/docstring 제외 (단순 휴리스틱)
                if stripped.startswith("#") or stripped.startswith('"""') or stripped.startswith("'''"):
                    continue
                if "❌" in line or "금지" in line:
                    continue
                assert not stripped.startswith(pattern), f"{path}: forbidden import: {stripped!r}"
        for url in forbidden_urls:
            for line in text.splitlines():
                if "❌" in line or "금지" in line or line.strip().startswith("#"):
                    continue
                # 도메인이 코드 호출의 인자/리터럴로 들어가면 안 됨
                if url in line and ("http" in line.lower() or '"' + url in line or "'" + url in line):
                    pytest.fail(f"{path}: forbidden URL in code: {line.strip()!r}")


# ---------------------------------------------------------------------------
# list_brands — 60+ 보장 (60 목표, 실제 77)
# ---------------------------------------------------------------------------


def test_list_brands_meets_minimum():
    from design_md_loader import list_brands

    brands = list_brands()
    assert len(brands) >= 50, f"only {len(brands)} brands; target ~60+"
