"""Font embedding service for OOXML packages.

Encapsulates font resolution, variant iteration, ODTTF obfuscation,
and EOT conversion. Returns PackagedEntry objects ready for ZIP assembly.
"""

from __future__ import annotations

import logging
import uuid
from pathlib import Path
from typing import Literal

from tokenmoulds.fonts.types import EmbeddedFont, obfuscate_font_odttf
from tokenmoulds.fonts import FontQuery, build_font_service
from tokenmoulds.packaging.types import PackagedEntry

logger = logging.getLogger(__name__)

# Default font mapping for embedding (mirrors OOXMLBaseEmitter.FONT_MAPPING)
DEFAULT_FONT_MAPPING: dict[str, dict[str, str]] = {
    "Inter": {
        "regular": "inter/Inter-Regular.ttf",
        "bold": "inter/Inter-Bold.ttf",
    },
    "Roboto Slab": {
        "regular": "roboto-slab/RobotoSlab-Regular.ttf",
        "bold": "roboto-slab/RobotoSlab-Bold.ttf",
    },
    "Merriweather": {
        "regular": "merriweather/fonts/ttf/Merriweather-Regular.ttf",
        "bold": "merriweather/fonts/ttf/Merriweather-Bold.ttf",
        "italic": "merriweather/fonts/ttf/Merriweather-Italic.ttf",
        "bold_italic": "merriweather/fonts/ttf/Merriweather-BoldItalic.ttf",
    },
}

_VARIANTS = (
    ("regular", False, False),
    ("bold", True, False),
    ("italic", False, True),
    ("bold_italic", True, True),
)


class FontEmbeddingService:
    """Resolves, converts, and packages fonts for OOXML embedding.

    Usage:
        service = FontEmbeddingService()
        embedded_fonts, entries = service.prepare(
            document_ir=doc,
            fonts_dir=Path("fonts/"),
            font_format="odttf",
            font_mapping=DEFAULT_FONT_MAPPING,
            fonts_path="word/fonts/",
            initial_rel_id=1,
        )
    """

    def __init__(self) -> None:
        self._font_service = None

    def prepare(
        self,
        document_ir: object,
        fonts_dir: Path | None,
        font_format: Literal["odttf", "eot", "none"],
        font_mapping: dict[str, dict[str, str]],
        fonts_path: str,
        initial_rel_id: int = 1,
    ) -> tuple[list[EmbeddedFont], list[PackagedEntry]]:
        """Prepare fonts for embedding.

        Args:
            document_ir: DocumentIR with typography info.
            fonts_dir: Directory containing vendored fonts.
            font_format: Target format ('odttf' for Word, 'eot' for PowerPoint).
            font_mapping: Family-name to variant-path mapping.
            fonts_path: Archive path prefix (e.g. 'word/fonts/').
            initial_rel_id: Starting relationship ID number.

        Returns:
            Tuple of (embedded_fonts list, packaged_entries list).
        """
        if font_format == "none":
            return [], []

        embedded_fonts: list[EmbeddedFont] = []
        rel_id_counter = initial_rel_id

        # Collect font families from typography
        typo = document_ir.typography  # type: ignore[attr-defined]
        fonts_to_embed: set[str] = {
            typo.body_font,
            typo.heading_font,
            typo.monospace_font,
        }
        seen_variants: set[tuple[str, bool, bool]] = set()

        for requested_font in fonts_to_embed:
            for variant_name, is_bold, is_italic in _VARIANTS:
                resolved_font, font_path = self._resolve_font_variant(
                    requested_font,
                    variant_name,
                    is_bold=is_bold,
                    is_italic=is_italic,
                    fonts_dir=fonts_dir,
                    font_mapping=font_mapping,
                )
                if font_path is None:
                    continue
                variant_key = (resolved_font, is_bold, is_italic)
                if variant_key in seen_variants:
                    continue
                seen_variants.add(variant_key)
                embedded_fonts.append(
                    self._create_font_entry(
                        resolved_font,
                        font_path,
                        rel_id_counter,
                        is_bold=is_bold,
                        is_italic=is_italic,
                        font_format=font_format,
                    )
                )
                rel_id_counter += 1

        # Convert EmbeddedFont objects into PackagedEntry objects
        entries = self._build_packaged_entries(embedded_fonts, font_format, fonts_path)
        return embedded_fonts, entries

    def _resolve_font_variant(
        self,
        family_name: str,
        variant_name: str,
        *,
        is_bold: bool,
        is_italic: bool,
        fonts_dir: Path | None,
        font_mapping: dict[str, dict[str, str]],
    ) -> tuple[str, Path | None]:
        """Resolve a font variant to a file path."""
        font_files = font_mapping.get(family_name, {})
        variant_path = font_files.get(variant_name)
        if variant_path and fonts_dir:
            font_path = fonts_dir / variant_path
            if font_path.exists():
                return family_name, font_path

        match = self._get_font_service(fonts_dir).find_font(
            FontQuery(
                family=family_name,
                weight=700 if is_bold else 400,
                style="italic" if is_italic else "normal",
            )
        )
        if match is None:
            logger.warning("No embeddable font source for: %s", family_name)
            return family_name, None
        if match.family != family_name:
            logger.info(
                "Resolved '%s' to embeddable font '%s'",
                family_name,
                match.family,
            )
        return match.family, match.path

    def _get_font_service(self, fonts_dir: Path | None):
        """Lazy-build a FontService."""
        if self._font_service is None:
            self._font_service = build_font_service(
                fonts_dir,
                enable_downloads=True,
                include_system=False,
            )
        return self._font_service

    def _create_font_entry(
        self,
        family_name: str,
        font_path: Path,
        rel_id: int,
        *,
        is_bold: bool,
        is_italic: bool,
        font_format: str,
    ) -> EmbeddedFont:
        """Create an EmbeddedFont entry with format-specific fields."""
        font = EmbeddedFont(
            family_name=family_name,
            font_path=font_path,
            relationship_id=f"rId{rel_id}",
            is_bold=is_bold,
            is_italic=is_italic,
        )

        if font_format == "odttf":
            font_guid = uuid.uuid4()
            font.font_key = str(font_guid).upper()
            font.odttf_filename = f"font{rel_id}.odttf"
        elif font_format == "eot":
            font.fntdata_filename = f"font{rel_id}.fntdata"

        return font

    def _build_packaged_entries(
        self,
        embedded_fonts: list[EmbeddedFont],
        font_format: str,
        fonts_path: str,
    ) -> list[PackagedEntry]:
        """Convert EmbeddedFont objects into PackagedEntry objects for ZIP assembly."""
        entries: list[PackagedEntry] = []

        for font in embedded_fonts:
            if not font.font_path.exists():
                logger.warning("Font file not found: %s", font.font_path)
                continue

            font_data = font.font_path.read_bytes()

            if font_format == "odttf":
                font_guid = uuid.UUID(font.font_key)
                font_data = obfuscate_font_odttf(font_data, font_guid)
                filename = font.odttf_filename
            elif font_format == "eot":
                font_data = _convert_to_eot(font_data, font.family_name)
                filename = font.fntdata_filename
            else:
                continue

            entries.append(
                PackagedEntry(
                    archive_path=f"{fonts_path}{filename}",
                    data=font_data,
                )
            )
            logger.info("Embedded font: %s -> %s", font.family_name, filename)

        return entries


def _convert_to_eot(font_data: bytes, family_name: str) -> bytes:
    """Convert font data to EOT format for PowerPoint."""
    try:
        from tokenmoulds.fonts.eot import build_eot
        from tokenmoulds.fonts.otf2ttf import convert_font_bytes_for_embedding

        font_data = convert_font_bytes_for_embedding(
            font_data,
            strip_opentype_features=True,
        )
        eot_result = build_eot(font_data)
        logger.info("Converted %s to EOT format", family_name)
        return eot_result.data

    except ImportError:
        logger.warning("EOT module not available, using raw TTF")
        return font_data
    except Exception as e:
        logger.warning(
            "EOT conversion failed for %s: %s, using raw TTF", family_name, e
        )
        return font_data
