"""証拠番号の右上スタンプ焼き込み（要件定義書 4.7）。

準備の手引29頁「証拠番号は...証拠データの右上にも記載」に基づき、証拠番号
（例：「甲001」「甲001-1」）をPDFの各ページ又は画像の右上に焼き込む。

文字サイズ・余白は一次資料に数値定めがないため config.yaml（evidence_stamp）で
管理する（第7章「公式規定のない事項の扱い」）。

この処理はファイルの内容・体裁を変更するため、CLAUDE.md「絶対に譲れない設計
原則」3に従い、呼び出し側（GUI）で必ず実行前にユーザーの確認を得ること。
本モジュール自体は確認済みの実行を担うのみで、確認ダイアログを持たない。
"""

from __future__ import annotations

import io
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont
from pypdf import PdfReader, PdfWriter
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.pdfgen import canvas

from mints_prep.config import EvidenceStampConfig

# Adobe-Japan1の標準CIDフォント。フォントファイルの同梱・外部取得を要さず、
# reportlabに組み込み済みのメトリクスのみでPDFに直接埋め込める
# （CLAUDE.md「完全ローカル動作」原則に適合）。
_PDF_STAMP_FONT = "HeiseiKakuGo-W5"
_pdf_font_registered = False

# 画像スタンプ用のTTFフォント（Pillowはreportlabと異なりCID組み込み
# フォントを持たないため、実ファイルが必要）。Windows標準搭載フォントを
# 優先順に探索する（実行環境はWindows想定。CLAUDE.md非機能要件参照）。
_IMAGE_FONT_CANDIDATES = (
    r"C:\Windows\Fonts\meiryo.ttc",
    r"C:\Windows\Fonts\YuGothM.ttc",
    r"C:\Windows\Fonts\msgothic.ttc",
)


def _ensure_pdf_font_registered() -> None:
    global _pdf_font_registered
    if not _pdf_font_registered:
        pdfmetrics.registerFont(UnicodeCIDFont(_PDF_STAMP_FONT))
        _pdf_font_registered = True


# スタンプの見た目（evidence_stamp.style）に対応する描画色。
# 根拠：一次資料に色・枠の規定はなし（config.yaml参照）。旧・紙媒体実務の
# 赤枠・赤字慣行に合わせたい場合の選択肢として用意する。
_PDF_STYLE_COLORS = {"black": (0, 0, 0), "red": (1, 0, 0), "red_boxed": (1, 0, 0)}
_IMAGE_STYLE_COLORS = {"black": "black", "red": "red", "red_boxed": "red"}


def stamp_pdf_page(page, label: str, config: EvidenceStampConfig) -> None:
    """pypdfのページオブジェクトに、右上スタンプのオーバーレイを合成する（in-place）。"""
    _ensure_pdf_font_registered()
    width = float(page.mediabox.width)
    height = float(page.mediabox.height)
    color = _PDF_STYLE_COLORS[config.style]

    buf = io.BytesIO()
    c = canvas.Canvas(buf, pagesize=(width, height))
    c.setFont(_PDF_STAMP_FONT, config.font_size_pt)
    c.setFillColorRGB(*color)
    x = width - config.margin_right_mm * mm
    y = height - config.margin_top_mm * mm - config.font_size_pt

    if config.style == "red_boxed":
        text_width = c.stringWidth(label, _PDF_STAMP_FONT, config.font_size_pt)
        pad = config.box_padding_mm * mm
        c.setStrokeColorRGB(*color)
        c.setLineWidth(config.box_line_width_pt)
        c.rect(
            x - text_width - pad,
            y - pad,
            text_width + 2 * pad,
            config.font_size_pt + 2 * pad,
            fill=0,
            stroke=1,
        )

    c.drawRightString(x, y, label)
    c.save()
    buf.seek(0)

    overlay = PdfReader(buf).pages[0]
    page.merge_page(overlay)


def stamp_pdf(input_path: Path, output_path: Path, label: str, config: EvidenceStampConfig) -> None:
    """PDFの全ページ右上に証拠番号ラベルを焼き込み、output_pathへ書き出す。"""
    writer = PdfWriter(clone_from=str(input_path))
    for page in writer.pages:
        stamp_pdf_page(page, label, config)
    with open(output_path, "wb") as f:
        writer.write(f)


def _load_image_font(size_px: int) -> ImageFont.FreeTypeFont:
    for path in _IMAGE_FONT_CANDIDATES:
        if Path(path).exists():
            return ImageFont.truetype(path, size_px, index=0)
    raise FileNotFoundError(
        "証拠番号スタンプ用の日本語フォントが見つかりません"
        f"（候補：{', '.join(_IMAGE_FONT_CANDIDATES)}）。"
        "Windows標準の日本語フォントが必要です。"
    )


def stamp_image(input_path: Path, output_path: Path, label: str, config: EvidenceStampConfig) -> None:
    """画像（JPEG/PNG）の右上に証拠番号ラベルを焼き込み、output_pathへ書き出す。

    ポイント換算のPDFと異なり画像はピクセル単位のため、文字サイズはmm指定の
    font_size_ptをそのままピクセル数として用いる（画像解像度に応じた換算表は
    一次資料に定めがなく、簡明さを優先する）。
    """
    image = Image.open(input_path)
    original_format = image.format
    size_px = max(1, round(config.font_size_pt))
    font = _load_image_font(size_px)
    draw = ImageDraw.Draw(image)

    # 小さい画像（サムネイル等）ではmm指定の余白がキャンバス外にはみ出し、
    # スタンプが全く描画されなくなる場合があるため、画像寸法に収まるように
    # クランプする。
    margin_right_px = min(round(config.margin_right_mm * 96 / 25.4), max(0, image.width - 1))
    margin_top_px = min(
        round(config.margin_top_mm * 96 / 25.4), max(0, image.height - size_px)
    )
    x = image.width - margin_right_px
    y = margin_top_px

    text_color = _IMAGE_STYLE_COLORS[config.style]

    # 縁取り（白）を先に描き、本文を重ねることで、背景の濃淡に関わらず
    # 判読性を確保する。
    for dx, dy in ((-1, -1), (-1, 1), (1, -1), (1, 1)):
        draw.text((x + dx, y + dy), label, font=font, fill="white", anchor="ra")
    draw.text((x, y), label, font=font, fill=text_color, anchor="ra")

    if config.style == "red_boxed":
        bbox = draw.textbbox((x, y), label, font=font, anchor="ra")
        pad_px = round(config.box_padding_mm * 96 / 25.4)
        box = (bbox[0] - pad_px, bbox[1] - pad_px, bbox[2] + pad_px, bbox[3] + pad_px)
        line_width_px = max(1, round(config.box_line_width_pt * 96 / 72))
        draw.rectangle(box, outline=text_color, width=line_width_px)

    image.save(output_path, format=original_format)


_IMAGE_FORMATS = {"JPEG", "PNG"}


def stamp_file(
    input_path: Path,
    output_path: Path,
    label: str,
    config: EvidenceStampConfig,
    detected_format: str,
) -> None:
    """検出形式に応じてPDF/画像いずれかのスタンプ処理を実行する。"""
    if detected_format == "PDF":
        stamp_pdf(input_path, output_path, label, config)
    elif detected_format in _IMAGE_FORMATS:
        stamp_image(input_path, output_path, label, config)
    else:
        raise ValueError(f"証拠番号スタンプに対応していない形式です：{detected_format}")
