"""Thumbnail generation for DOM.РФ photos.

Strategy: original PNG/JPG ~1-3 MB downloaded by scraper to
data/raw/domrf_photos/<obj_id>/<file_id>.{png,jpg}. We additionally write a
WebP cover-thumbnail at 320×240 (~10-30 KB) next to it, suffix `_thumb.webp`.

The frontend grid uses /api/v1/photos/<obj_id>/<file_id>?size=thumb. Lightbox
opens the original DOM.РФ URL (we don't need to mirror originals).
"""

from __future__ import annotations

import logging
import os
from pathlib import Path

from PIL import Image, ImageOps

logger = logging.getLogger(__name__)

THUMB_SIZE: tuple[int, int] = (320, 240)
THUMB_QUALITY: int = 70
THUMB_SUFFIX: str = "_thumb.webp"


def thumb_path_for(src: Path) -> Path:
    """Return canonical thumbnail path next to the source file."""
    return src.with_name(src.stem + THUMB_SUFFIX)


def make_thumbnail(
    src: Path,
    *,
    size: tuple[int, int] = THUMB_SIZE,
    quality: int = THUMB_QUALITY,
    overwrite: bool = False,
) -> Path | None:
    """Generate a WebP cover-thumbnail next to src. Returns thumb path on success.

    Skips if thumb already exists, is not stale (dst mtime >= src mtime) and
    overwrite=False. A stale thumb (src rewritten after thumb, e.g. original
    re-downloaded on size mismatch) is regenerated. Returns None on any error
    (logged) so callers can keep going with the next file.
    """
    if not src.exists():
        return None
    dst = thumb_path_for(src)
    if dst.exists() and not overwrite:
        # Existence alone is not freshness: if src was rewritten after dst was
        # generated, the thumb is stale and must be regenerated. Only skip when
        # the thumb is at least as new as the source.
        try:
            if dst.stat().st_mtime >= src.stat().st_mtime:
                return dst
        except OSError as e:
            # stat failed (race / removed) — fall through and try to regenerate.
            logger.warning("thumbnail freshness check %s failed: %s", src, e)
    try:
        with Image.open(src) as im:
            im = ImageOps.exif_transpose(im)
            im = im.convert("RGB")
            im = ImageOps.fit(im, size, method=Image.Resampling.LANCZOS)
            dst.parent.mkdir(parents=True, exist_ok=True)
            # Atomic write: encode to a sibling temp file, then os.replace() so a
            # crash/OOM mid-encode never leaves a truncated .webp at the canonical
            # path (which dst.exists() would later treat as a valid cached thumb).
            tmp = dst.with_name(f".{dst.name}.tmp")
            try:
                im.save(tmp, format="WEBP", quality=quality, method=4)
                os.replace(tmp, dst)
            except BaseException:
                tmp.unlink(missing_ok=True)
                raise
        return dst
    except Exception as e:
        logger.warning("thumbnail %s failed: %s", src, e)
        return None
