"""Image reader — uses ``Pillow`` (lazy import) to extract metadata.

Images are represented as a textual description of their EXIF/metadata,
plus an optional prompt to an LLM for visual description.  Pixel data is
not stored in the Document; use :class:`~ractogateway.prompts.engine.RactoFile`
for multimodal vision calls.

Install with:  pip install ractogateway[rag-image]
"""

from __future__ import annotations

import io
from pathlib import Path
from typing import Any


def _require_pillow() -> Any:
    try:
        from PIL import Image
    except ImportError as exc:
        raise ImportError(
            "Reading image files requires the 'Pillow' package. "
            "Install it with:  pip install ractogateway[rag-image]"
        ) from exc
    return Image


from ractogateway.rag._models.document import Document
from ractogateway.rag.readers.base import BaseReader

_EXIF_TAGS: dict[int, str] = {}


def _load_exif_tags() -> dict[int, str]:
    global _EXIF_TAGS  # noqa: PLW0603
    if not _EXIF_TAGS:
        try:
            from PIL.ExifTags import TAGS

            _EXIF_TAGS = dict(TAGS.items())
        except Exception:
            return _EXIF_TAGS
    return _EXIF_TAGS


class ImageReader(BaseReader):
    """Extract metadata from image files and represent them as text Documents.

    The resulting ``Document.content`` is a human-readable summary of image
    properties (size, mode, format, EXIF tags).  Pass the image to a vision
    LLM separately using :class:`~ractogateway.prompts.engine.RactoFile` for
    actual visual understanding.

    Accepts a file path (``str`` / ``Path``), raw ``bytes``, or any binary
    file-like object with a ``.read()`` method.

    Parameters
    ----------
    include_exif:
        Whether to extract and include EXIF metadata in the content.
    """

    def __init__(self, include_exif: bool = True) -> None:
        self._include_exif = include_exif

    @property
    def supported_extensions(self) -> frozenset[str]:
        return frozenset(
            {
                ".png",
                ".jpg",
                ".jpeg",
                ".webp",
                ".gif",
                ".bmp",
                ".tiff",
                ".tif",
            }
        )

    def _read_path(self, path: Path) -> Document:
        image_module = _require_pillow()
        with image_module.open(str(path)) as img:
            content, meta = self._extract_image_info(img, name=path.name)

        return Document(
            content=content,
            source=str(path.resolve()),
            metadata={
                "extension": path.suffix.lower(),
                "filename": path.name,
                "size_bytes": path.stat().st_size,
                **meta,
            },
        )

    def _read_bytes(self, data: bytes, *, source_label: str = "<bytes>") -> Document:
        image_module = _require_pillow()
        with image_module.open(io.BytesIO(data)) as img:
            content, meta = self._extract_image_info(img, name=source_label)

        return Document(
            content=content,
            source=source_label,
            metadata={"size_bytes": len(data), **meta},
        )

    def _extract_image_info(self, img: Any, *, name: str) -> tuple[str, dict[str, Any]]:
        width, height = img.size
        mode = img.mode
        fmt = img.format or "UNKNOWN"

        lines: list[str] = [
            f"Image: {name}",
            f"Format: {fmt}",
            f"Size: {width}x{height} pixels",
            f"Color mode: {mode}",
        ]

        if self._include_exif:
            exif_data = self._extract_exif(img)
            if exif_data:
                lines.append("EXIF metadata:")
                for key, val in exif_data.items():
                    lines.append(f"  {key}: {val}")

        meta: dict[str, Any] = {
            "width": width,
            "height": height,
            "format": fmt,
            "mode": mode,
        }
        return "\n".join(lines), meta

    def _extract_exif(self, img: Any) -> dict[str, str]:
        tags = _load_exif_tags()
        result: dict[str, str] = {}
        try:
            raw_exif = img._getexif()
            if raw_exif:
                for tag_id, value in raw_exif.items():
                    tag_name = tags.get(tag_id, str(tag_id))
                    if isinstance(value, bytes):
                        continue  # skip binary blobs
                    result[tag_name] = str(value)[:200]
        except Exception:
            return result
        return result
