"""PDF 렌더러 — 웹북 에피소드를 장페이지 PDF로 렌더링."""

import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from fpdf import FPDF

logger = logging.getLogger(__name__)

# Page layout constants (from prototype)
WEBBOOK_PAGE_WIDTH_PT = 399.685
WEBBOOK_TOP_MARGIN_PT = 26
WEBBOOK_SIDE_MARGIN_PT = 36
WEBBOOK_IMAGE_WIDTH_PT = 328
SCENE_TARGET_W = 1600
SCENE_TARGET_H = 900
WEBBOOK_IMAGE_HEIGHT_PT = WEBBOOK_IMAGE_WIDTH_PT * (SCENE_TARGET_H / SCENE_TARGET_W)
WEBBOOK_MIN_PAGE_HEIGHT_PT = 5200
WEBBOOK_MAX_PAGE_HEIGHT_PT = 18000

# Font sizes
FONT_SIZE_HEADER = 8.5
FONT_SIZE_EPISODE_LABEL = 10
FONT_SIZE_TITLE = 22
FONT_SIZE_SUBTITLE = 11
FONT_SIZE_SECTION_TITLE = 12
FONT_SIZE_BODY = 10.8
FONT_SIZE_CAPTION = 9

# Line heights
LINE_HEIGHT_HEADER = 12
LINE_HEIGHT_EPISODE_LABEL = 14
LINE_HEIGHT_TITLE = 28
LINE_HEIGHT_SUBTITLE = 16
LINE_HEIGHT_SECTION_TITLE = 18
LINE_HEIGHT_BODY = 17
LINE_HEIGHT_CAPTION = 13

# Spacing
SPACING_AFTER_HEADER = 8
SPACING_AFTER_EPISODE_LABEL = 6
SPACING_AFTER_TITLE = 2
SPACING_AFTER_SUBTITLE = 16
SPACING_AFTER_PARAGRAPH = 8
SPACING_AFTER_IMAGE = 14
SPACING_AFTER_SECTION = 8
SPACING_BEFORE_SECTION_TITLE = 16
SPACING_AFTER_SECTION_TITLE = 10
SPACING_AFTER_CAPTION = 8
SPACING_PAGE_BOTTOM = 72

# Korean font candidates (tried in order)
_KOREAN_FONT_CANDIDATES: list[Tuple[str, str]] = [
    ("AppleSDGothic", "/System/Library/Fonts/AppleSDGothicNeo.ttc"),          # macOS
    ("NanumGothic", "/usr/share/fonts/truetype/nanum/NanumGothic.ttf"),       # Linux (fonts-nanum)
    ("NotoSansCJK", "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"),# Linux (fonts-noto-cjk)
    ("NotoSansCJK", "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc"),    # Alt Linux path
    ("MalgunGothic", "C:/Windows/Fonts/malgun.ttf"),                          # Windows
]

# Resolved font name (cached after first call)
_resolved_font: Optional[Tuple[str, Optional[str]]] = None  # (font_name, font_path|None)


def _resolve_korean_font() -> Tuple[str, Optional[str]]:
    """Find the first available Korean font. Returns (font_name, font_path).

    If no Korean font is found, returns ("Helvetica", None) as a fallback.
    """
    global _resolved_font
    if _resolved_font is not None:
        return _resolved_font

    for name, path in _KOREAN_FONT_CANDIDATES:
        if Path(path).exists():
            logger.info("PDF renderer: using Korean font %s (%s)", name, path)
            _resolved_font = (name, path)
            return _resolved_font

    logger.warning(
        "PDF renderer: no Korean font found, falling back to Helvetica. "
        "Korean text will not render correctly. "
        "Install fonts-nanum or fonts-noto-cjk on Linux."
    )
    _resolved_font = ("Helvetica", None)
    return _resolved_font


# Colors
COLOR_HEADER = (120, 120, 120)
COLOR_EPISODE_LABEL = (130, 130, 130)
COLOR_TITLE = (18, 18, 18)
COLOR_SUBTITLE = (90, 90, 90)
COLOR_SECTION_TITLE = (30, 30, 30)
COLOR_BODY = (35, 35, 35)
COLOR_CAPTION = (100, 100, 100)
COLOR_DIVIDER = (200, 200, 200)


def _new_webbook_pdf(page_height: float) -> FPDF:
    pdf = FPDF(unit="pt", format=(WEBBOOK_PAGE_WIDTH_PT, page_height))
    pdf.set_auto_page_break(auto=False)
    font_name, font_path = _resolve_korean_font()
    if font_path:
        pdf.add_font(font_name, "", font_path)
    pdf.set_font(font_name, size=FONT_SIZE_BODY)
    pdf.set_margins(
        WEBBOOK_SIDE_MARGIN_PT, WEBBOOK_TOP_MARGIN_PT, WEBBOOK_SIDE_MARGIN_PT
    )
    pdf.add_page()
    return pdf


def _set_font(pdf: FPDF, size: float) -> None:
    """Set font using the resolved Korean font (or Helvetica fallback)."""
    font_name, _ = _resolve_korean_font()
    pdf.set_font(font_name, size=size)


def _draw_section_divider(pdf: FPDF, content_w: float) -> None:
    """Draw a subtle divider line between sections."""
    y = pdf.get_y()
    center_x = WEBBOOK_SIDE_MARGIN_PT + content_w / 2
    line_half = 40
    pdf.set_draw_color(*COLOR_DIVIDER)
    pdf.line(center_x - line_half, y, center_x + line_half, y)
    pdf.ln(SPACING_BEFORE_SECTION_TITLE)


def _draw_section_title(
    pdf: FPDF, content_w: float, section_title: str
) -> None:
    """Draw section title centered with accent styling."""
    _set_font(pdf, FONT_SIZE_SECTION_TITLE)
    pdf.set_text_color(*COLOR_SECTION_TITLE)
    pdf.multi_cell(content_w, LINE_HEIGHT_SECTION_TITLE, section_title, align="C")
    pdf.ln(SPACING_AFTER_SECTION_TITLE)


def _draw_image_caption(
    pdf: FPDF, content_w: float, caption: str
) -> None:
    """Draw image caption in smaller italic-style text."""
    _set_font(pdf, FONT_SIZE_CAPTION)
    pdf.set_text_color(*COLOR_CAPTION)
    pdf.multi_cell(content_w, LINE_HEIGHT_CAPTION, caption, align="C")
    pdf.ln(SPACING_AFTER_CAPTION)


def _draw_paragraphs(
    pdf: FPDF, content_w: float, paragraphs: list
) -> None:
    """Draw body paragraphs."""
    _set_font(pdf, FONT_SIZE_BODY)
    pdf.set_text_color(*COLOR_BODY)
    for paragraph in paragraphs:
        pdf.multi_cell(content_w, LINE_HEIGHT_BODY, paragraph, align="J")
        pdf.ln(SPACING_AFTER_PARAGRAPH)


def _draw_webbook_episode(
    pdf: FPDF,
    *,
    package_title: str,
    episode: Dict[str, Any],
    section_images: Dict[str, str],
) -> None:
    content_w = WEBBOOK_PAGE_WIDTH_PT - (WEBBOOK_SIDE_MARGIN_PT * 2)
    image_x = (WEBBOOK_PAGE_WIDTH_PT - WEBBOOK_IMAGE_WIDTH_PT) / 2

    # Header: series title
    pdf.set_xy(WEBBOOK_SIDE_MARGIN_PT, WEBBOOK_TOP_MARGIN_PT)
    _set_font(pdf, FONT_SIZE_HEADER)
    pdf.set_text_color(*COLOR_HEADER)
    pdf.multi_cell(content_w, LINE_HEIGHT_HEADER, f"{package_title} WEBBOOK", align="L")
    pdf.ln(SPACING_AFTER_HEADER)

    # Episode label
    _set_font(pdf, FONT_SIZE_EPISODE_LABEL)
    pdf.set_text_color(*COLOR_EPISODE_LABEL)
    pdf.multi_cell(
        content_w,
        LINE_HEIGHT_EPISODE_LABEL,
        f"EPISODE {episode['episode_number']}",
        align="C",
    )
    pdf.ln(SPACING_AFTER_EPISODE_LABEL)

    # Episode title
    pdf.set_text_color(*COLOR_TITLE)
    _set_font(pdf, FONT_SIZE_TITLE)
    pdf.multi_cell(content_w, LINE_HEIGHT_TITLE, episode["title"], align="C")
    pdf.ln(SPACING_AFTER_TITLE)

    # Subtitle
    _set_font(pdf, FONT_SIZE_SUBTITLE)
    pdf.set_text_color(*COLOR_SUBTITLE)
    pdf.multi_cell(content_w, LINE_HEIGHT_SUBTITLE, episode["subtitle"], align="C")
    pdf.ln(SPACING_AFTER_SUBTITLE)

    # Opener paragraphs
    _draw_paragraphs(pdf, content_w, episode.get("opener_paragraphs", []))

    # Sections
    for section_idx, section in enumerate(episode.get("sections", [])):
        still_id = section["still_id"]
        image_path = section_images.get(still_id)
        image_after = int(section.get("image_after_paragraph", 1))
        section_title = section.get("section_title", "")
        image_caption = section.get("image_caption", "")

        # Section divider (except before the first section)
        if section_idx > 0:
            _draw_section_divider(pdf, content_w)

        # Section title
        if section_title:
            _draw_section_title(pdf, content_w, section_title)

        # Opening paragraphs (new v1.1 field)
        opening = section.get("opening_paragraphs", [])
        if opening:
            _draw_paragraphs(pdf, content_w, opening)

        # Main paragraphs with image interleaved
        _set_font(pdf, FONT_SIZE_BODY)
        pdf.set_text_color(*COLOR_BODY)
        for paragraph_index, paragraph in enumerate(
            section.get("paragraphs", []), start=1
        ):
            pdf.multi_cell(content_w, LINE_HEIGHT_BODY, paragraph, align="J")
            pdf.ln(SPACING_AFTER_PARAGRAPH)
            if (
                paragraph_index == image_after
                and image_path
                and Path(image_path).exists()
            ):
                pdf.image(
                    str(image_path),
                    x=image_x,
                    w=WEBBOOK_IMAGE_WIDTH_PT,
                    h=WEBBOOK_IMAGE_HEIGHT_PT,
                )
                pdf.ln(WEBBOOK_IMAGE_HEIGHT_PT + SPACING_AFTER_IMAGE)
                # Caption under image
                if image_caption:
                    _draw_image_caption(pdf, content_w, image_caption)

        # Closing paragraphs (new v1.1 field)
        closing = section.get("closing_paragraphs", [])
        if closing:
            _draw_paragraphs(pdf, content_w, closing)

        pdf.ln(SPACING_AFTER_SECTION)

    # Episode closer paragraphs
    _draw_paragraphs(pdf, content_w, episode.get("closer_paragraphs", []))


def _estimate_episode_page_height(
    *,
    package_title: str,
    episode: Dict[str, Any],
    section_images: Dict[str, str],
) -> float:
    pdf = _new_webbook_pdf(WEBBOOK_MAX_PAGE_HEIGHT_PT)
    with pdf.offset_rendering() as dummy:
        _draw_webbook_episode(
            dummy,
            package_title=package_title,
            episode=episode,
            section_images=section_images,
        )
        estimated = dummy.get_y() + SPACING_PAGE_BOTTOM
    return max(
        WEBBOOK_MIN_PAGE_HEIGHT_PT,
        min(WEBBOOK_MAX_PAGE_HEIGHT_PT, estimated),
    )


def _episode_file_stub(episode_number: int, episode_title: str) -> str:
    cleaned = "".join(
        char if char.isalnum() else "_" for char in episode_title
    ).strip("_").lower()
    if not cleaned:
        cleaned = f"episode_{episode_number:02d}"
    return f"web_episode_{episode_number:02d}_{cleaned}"


class PDFRenderer:
    """웹북 에피소드 PDF 렌더러."""

    def render_episode(
        self,
        episode_data: Dict[str, Any],
        images: Dict[str, str],
        output_path: Path,
        package_title: str = "",
    ) -> Path:
        """Render one webbook episode as a long-page PDF.

        Args:
            episode_data: Single episode dict from webbook package.
            images: Map of still_id -> file path string.
            output_path: Where to save the PDF.
            package_title: Series title for the header.

        Returns:
            The output path.
        """
        page_height = _estimate_episode_page_height(
            package_title=package_title,
            episode=episode_data,
            section_images=images,
        )
        pdf = _new_webbook_pdf(page_height)
        _draw_webbook_episode(
            pdf,
            package_title=package_title,
            episode=episode_data,
            section_images=images,
        )
        output_path.parent.mkdir(parents=True, exist_ok=True)
        pdf.output(str(output_path))
        return output_path

    def render_all(
        self,
        package: Dict[str, Any],
        images: Dict[str, str],
        output_dir: Path,
    ) -> List[Path]:
        """Render all webbook episodes as PDFs.

        Args:
            package: Full webbook package dict.
            images: Map of still_id -> file path string.
            output_dir: Directory for PDF output.

        Returns:
            List of output paths.
        """
        package_title = package.get("series_title", "")
        output_dir.mkdir(parents=True, exist_ok=True)
        paths: List[Path] = []

        for episode in package.get("episodes", []):
            stub = _episode_file_stub(
                episode["episode_number"], episode["title"]
            )
            pdf_path = output_dir / f"{stub}.pdf"
            self.render_episode(
                episode_data=episode,
                images=images,
                output_path=pdf_path,
                package_title=package_title,
            )
            paths.append(pdf_path)

        return paths
