"""Permission-aware font subsetting with content-hash caching.

Borrowed from svg2ooxml's FontEmbeddingEngine pattern:
- Reads OS/2 fsType to check embedding permissions
- Subsets fonts to only needed glyphs via fontTools
- Caches results by SHA-1(font path + glyph set)
- Falls back to full-font when fontTools unavailable
"""

from __future__ import annotations

import hashlib
import logging
from dataclasses import dataclass
from enum import Enum
from pathlib import Path

from tokenmoulds.caching import LRUCache

logger = logging.getLogger(__name__)

# Default character set for template embedding:
# Latin letters, digits, common punctuation, currency symbols
_TEMPLATE_CHARS = (
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    "0123456789"
    " .,;:!?-\u2013\u2014'\"\u201c\u201d()[]{}/@#$%&*+=<>"
    "\u00a9\u00ae\u2122\u20ac\u00a3\u00a5"
    "\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9"
    "\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf\u00d0\u00d1\u00d2\u00d3"
    "\u00d4\u00d5\u00d6\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de"
    "\u00df\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00e8"
    "\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2"
    "\u00f3\u00f4\u00f5\u00f6\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd"
    "\u00fe\u00ff"
)


class EmbeddingPermission(Enum):
    """Font embedding permission from OS/2 fsType field."""

    INSTALLABLE = "installable"
    EDITABLE = "editable"
    PREVIEW_PRINT = "preview_print"
    NO_SUBSETTING = "no_subsetting"
    RESTRICTED = "restricted"


@dataclass(frozen=True, slots=True)
class SubsetResult:
    """Result of a font subsetting operation."""

    data: bytes
    original_size: int
    subset_size: int
    permission: EmbeddingPermission
    was_subset: bool


def _read_fs_type(font_path: Path) -> int:
    """Read the OS/2 fsType value from a font file.

    Returns 0 (installable) if fontTools is unavailable or the font
    has no OS/2 table.
    """
    try:
        from fontTools.ttLib import TTFont

        font = TTFont(str(font_path))
        try:
            os2 = font.get("OS/2")
            return getattr(os2, "fsType", 0) if os2 is not None else 0
        finally:
            font.close()
    except (ImportError, Exception):
        return 0


def _permission_from_fs_type(fs_type: int) -> EmbeddingPermission:
    """Derive embedding permission from OS/2 fsType bit flags."""
    if fs_type & 0x0002:
        return EmbeddingPermission.RESTRICTED
    if fs_type & 0x0100:
        return EmbeddingPermission.NO_SUBSETTING
    if fs_type & 0x0004:
        return EmbeddingPermission.PREVIEW_PRINT
    if fs_type & 0x0008:
        return EmbeddingPermission.EDITABLE
    return EmbeddingPermission.INSTALLABLE


class FontSubsetter:
    """Permission-aware font subsetting with caching.

    Usage:
        subsetter = FontSubsetter()
        result = subsetter.subset(Path("font.ttf"), characters="Hello World")
        if result:
            print(f"Reduced {result.original_size} -> {result.subset_size}")
    """

    def __init__(self) -> None:
        self._cache: LRUCache[SubsetResult | None] = LRUCache(
            max_items=256,
            max_bytes=50_000_000,
            sizeof=lambda r: len(r.data) if r else 0,
        )

    def subset(
        self,
        font_path: Path,
        *,
        characters: str | None = None,
    ) -> SubsetResult | None:
        """Subset a font to only the needed glyphs.

        Args:
            font_path: Path to .ttf or .otf font file
            characters: Characters to keep. Defaults to _TEMPLATE_CHARS.

        Returns:
            SubsetResult with subset data, or None if embedding not allowed.
        """
        chars = characters or _TEMPLATE_CHARS
        cache_key = self._cache_key(font_path, chars)

        if cache_key in self._cache:
            return self._cache.get(cache_key)

        result = self._do_subset(font_path, chars)
        self._cache.put(cache_key, result)
        return result

    def can_embed(self, font_path: Path) -> bool:
        """Check if a font's embedding permissions allow embedding."""
        permission = self._read_permission(font_path)
        return permission != EmbeddingPermission.RESTRICTED

    def clear_cache(self) -> None:
        """Clear the subset cache."""
        self._cache.clear()

    @property
    def stats(self) -> dict[str, int]:
        """Cache statistics."""
        return self._cache.stats

    def _do_subset(self, font_path: Path, characters: str) -> SubsetResult | None:
        """Perform the actual subsetting."""
        permission = self._read_permission(font_path)

        if permission == EmbeddingPermission.RESTRICTED:
            logger.warning("Font %s has restricted embedding", font_path.name)
            return None

        original_data = font_path.read_bytes()
        original_size = len(original_data)

        if permission == EmbeddingPermission.NO_SUBSETTING:
            logger.info("Font %s disallows subsetting, embedding full", font_path.name)
            return SubsetResult(
                data=original_data,
                original_size=original_size,
                subset_size=original_size,
                permission=permission,
                was_subset=False,
            )

        # Try fontTools subsetting
        try:
            subset_data = self._subset_with_fonttools(font_path, characters)
            logger.info(
                "Subset %s: %d -> %d bytes (%.0f%% reduction)",
                font_path.name,
                original_size,
                len(subset_data),
                (1 - len(subset_data) / original_size) * 100,
            )
            return SubsetResult(
                data=subset_data,
                original_size=original_size,
                subset_size=len(subset_data),
                permission=permission,
                was_subset=True,
            )
        except ImportError:
            logger.info(
                "fontTools not available, embedding full font %s", font_path.name
            )
        except Exception as e:
            logger.warning(
                "Subsetting failed for %s: %s, embedding full", font_path.name, e
            )

        return SubsetResult(
            data=original_data,
            original_size=original_size,
            subset_size=original_size,
            permission=permission,
            was_subset=False,
        )

    def _subset_with_fonttools(self, font_path: Path, characters: str) -> bytes:
        """Subset using fontTools.subset."""
        from fontTools.subset import Subsetter
        from fontTools.ttLib import TTFont
        from io import BytesIO

        font = TTFont(str(font_path))
        subsetter = Subsetter()

        # Build unicode set from characters
        unicodes = [ord(c) for c in set(characters)]
        subsetter.populate(unicodes=unicodes)
        subsetter.subset(font)

        buf = BytesIO()
        font.save(buf)
        font.close()
        return buf.getvalue()

    def _read_permission(self, font_path: Path) -> EmbeddingPermission:
        """Read embedding permission from OS/2 fsType field."""
        return _permission_from_fs_type(_read_fs_type(font_path))

    def _cache_key(self, font_path: Path, characters: str) -> str:
        """Build a cache key from font path and character set."""
        chars_hash = hashlib.sha1(
            characters.encode("utf-8"), usedforsecurity=False
        ).hexdigest()[:12]
        return f"{font_path}:{chars_hash}"
