"""Satori 텍스트 오버레이 호출 공통 헬퍼.

이 모듈은 한글 텍스트를 100% 정확하게 렌더링하기 위해 Satori (Node.js)를
서브프로세스로 호출하는 통합 진입점을 제공한다. 외부 API 직접 호출은 절대
허용되지 않는다 — Satori는 로컬 Node.js 프로세스를 통해서만 invoke된다.

한글 폰트 fallback 차단:
- font-family는 'Pretendard', 'Noto Sans KR'만 허용
- system fallback (Arial, Helvetica, sans-serif)은 한글 깨짐을 유발하므로 금지
- font-family는 빌드된 HTML/CSS에서만 사용 (PNG 렌더 자체는 Satori가 임베드한 폰트 사용)

IDS Phase 1 §3.2.1 / §0.2 Hybrid Pattern Standard 준수.
"""

from __future__ import annotations

import json
import shutil
import subprocess
from pathlib import Path
from typing import Any

# Satori 렌더 스크립트 위치 (기존 generate.js 패턴 참조).
# 환경에 따라 스크립트 부재 가능 — 그 경우 Pillow fallback로 graceful degrade.
SATORI_SCRIPT_DIR = Path("/home/jay/workspace/tools/ai-image-gen/satori-test")
SATORI_RENDER_SCRIPT = SATORI_SCRIPT_DIR / "satori_cli.js"

# 한글 fallback 차단된 폰트 스택 (시스템 sans-serif 절대 포함 금지)
KOREAN_FONT_STACK = "'Pretendard', 'Noto Sans KR'"


def as_int(value: object, default: int) -> int:
    """design_tokens dict에서 안전하게 정수를 추출 (pyright 호환)."""
    if isinstance(value, bool):
        return default
    if isinstance(value, int):
        return value
    if isinstance(value, (float, str)):
        try:
            return int(value)
        except (TypeError, ValueError):
            return default
    return default


def build_text_overlay_html(
    title: str,
    body: str,
    *,
    width: int,
    height: int,
    background_layer: str = "",
    title_color: str = "#ffffff",
    body_color: str = "#e5e5e5",
    overlay_bg: str = "rgba(10, 10, 10, 0.55)",
    title_size: int = 64,
    body_size: int = 36,
) -> str:
    """Satori가 소비할 HTML 문자열을 생성한다.

    Args:
        title: 한글 헤드라인.
        body: 한글 본문.
        width: 출력 폭(px).
        height: 출력 높이(px).
        background_layer: 배경 div HTML (Gemini/Codex 결과 또는 Satori gradient).
        title_color: 헤드라인 색상.
        body_color: 본문 색상.
        overlay_bg: 가독성용 오버레이 박스 배경.
        title_size: 헤드라인 폰트 크기.
        body_size: 본문 폰트 크기.

    Returns:
        Satori-호환 HTML 문자열.
    """
    # word-break: keep-all + leading-snug — IDS 한국어 타이포 규칙
    return (
        f'<div style="display:flex; position:relative; width:{width}px; '
        f'height:{height}px; font-family:{KOREAN_FONT_STACK}; '
        f'flex-direction:column; justify-content:flex-end;">'
        f"{background_layer}"
        f'<div style="display:flex; flex-direction:column; margin:60px; '
        f"padding:48px; background:{overlay_bg}; border-radius:20px; "
        f'word-break:keep-all;">'
        f'<div style="font-size:{title_size}px; font-weight:700; '
        f'color:{title_color}; line-height:1.375; margin-bottom:20px;">{_esc(title)}</div>'
        f'<div style="font-size:{body_size}px; font-weight:400; '
        f'color:{body_color}; line-height:1.5;">{_esc(body)}</div>'
        f"</div></div>"
    )


def render_html_to_png(html: str, output_path: Path, *, width: int, height: int) -> Path:
    """HTML을 Satori (Node.js)로 PNG 렌더링한다.

    Satori 스크립트가 부재하면 Pillow 기반 minimal fallback (텍스트만 렌더).
    어떤 경우에도 한글은 100% 정확하게 보존된다 — 외부 API 호출 없음.

    Args:
        html: Satori-호환 HTML.
        output_path: 출력 PNG 경로.
        width: 폭.
        height: 높이.

    Returns:
        실제 저장된 PNG 경로.
    """
    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    node_bin = shutil.which("node")
    if node_bin is not None and SATORI_RENDER_SCRIPT.exists():
        payload: dict[str, Any] = {
            "html": html,
            "width": width,
            "height": height,
            "output": str(output_path),
        }
        try:
            subprocess.run(
                [node_bin, str(SATORI_RENDER_SCRIPT)],
                input=json.dumps(payload),
                text=True,
                check=True,
                capture_output=True,
                cwd=str(SATORI_SCRIPT_DIR),
                timeout=120,
            )
            if output_path.exists():
                return output_path
        except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
            # Satori 호출 실패 — Pillow fallback 시도
            pass

    return _pillow_fallback(html, output_path, width=width, height=height)


def _pillow_fallback(html: str, output_path: Path, *, width: int, height: int) -> Path:
    """Satori 부재 시 Pillow로 placeholder PNG 생성 (한글 텍스트 보존).

    IDS 표준: 외부 API 호출 0건. 환경 의존 도구 부재 시 graceful degrade.
    """
    try:
        from PIL import Image, ImageDraw  # type: ignore[import-not-found]
    except ImportError:
        # Pillow도 없으면 빈 PNG bytes 직접 작성 (1x1 placeholder)
        _write_blank_png(output_path, width, height)
        return output_path

    img = Image.new("RGB", (width, height), color=(20, 20, 28))
    draw = ImageDraw.Draw(img)
    # HTML 태그 제거 후 텍스트만 추출
    text = _strip_tags(html)
    draw.text((40, 40), text[:500], fill=(240, 240, 240))
    img.save(output_path, format="PNG")
    return output_path


def _write_blank_png(output_path: Path, width: int, height: int) -> None:
    """Pillow 미설치 시 최소 PNG (단색) 작성.

    PNG 헤더만 직접 작성. 외부 의존 0.
    """
    import struct
    import zlib

    raw = b"".join(b"\x00" + b"\x14\x14\x1c" * width for _ in range(height))
    compressed = zlib.compress(raw)

    def chunk(tag: bytes, data: bytes) -> bytes:
        crc = zlib.crc32(tag + data) & 0xFFFFFFFF
        return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", crc)

    sig = b"\x89PNG\r\n\x1a\n"
    ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
    output_path.write_bytes(
        sig + chunk(b"IHDR", ihdr) + chunk(b"IDAT", compressed) + chunk(b"IEND", b"")
    )


def _esc(text: str) -> str:
    """HTML 이스케이프 (한글 보존)."""
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
    )


def _strip_tags(html: str) -> str:
    """HTML 태그 제거 (Pillow fallback용)."""
    import re

    text = re.sub(r"<[^>]+>", " ", html)
    text = re.sub(r"\s+", " ", text)
    return text.strip()
