"""THMX Theme Package Emitter.

Generates standalone Office Theme (.thmx) files that can be applied to
PowerPoint, Word, and Excel documents.

A .thmx file is an OPC (Open Packaging Conventions) archive containing:
- [Content_Types].xml - Content type definitions
- _rels/.rels - Root relationships
- theme/theme1.xml - The theme definition
- theme/_rels/theme1.xml.rels - Theme relationships (optional, for embedded fonts)
- theme/fonts/ - Embedded font files (optional)

XML templates are the Single Source of Truth (SSOT) for document structure.
This emitter loads templates and modifies attribute values via template
substitution - it never constructs new XML elements programmatically.
"""

from __future__ import annotations

import io
import zipfile
from dataclasses import dataclass, field
from pathlib import Path
from string import Template
from typing import TYPE_CHECKING, Dict, List, Optional

from tokenmoulds.fonts.otf2ttf import convert_font_bytes_for_embedding
from tokenmoulds.emitters import TemplateResolver

# XML Namespaces (exported for test compatibility)
NS_CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types"
NS_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships"
NS_DRAWINGML = "http://schemas.openxmlformats.org/drawingml/2006/main"

if TYPE_CHECKING:
    from tokenmoulds.ir import ColorTheme, TypographyScheme


@dataclass
class ThemeMetadata:
    """Metadata for theme package."""

    name: str = "TokenMoulds Theme"
    color_scheme_name: str = "TokenMoulds Colors"
    font_scheme_name: str = "TokenMoulds Fonts"
    format_scheme_name: str = "TokenMoulds Effects"

    def with_prefix(self, prefix: str) -> ThemeMetadata:
        """Create a copy with prefixed names."""
        return ThemeMetadata(
            name=f"{prefix} {self.name}" if prefix else self.name,
            color_scheme_name=(
                f"{prefix} {self.color_scheme_name}"
                if prefix
                else self.color_scheme_name
            ),
            font_scheme_name=(
                f"{prefix} {self.font_scheme_name}" if prefix else self.font_scheme_name
            ),
            format_scheme_name=(
                f"{prefix} {self.format_scheme_name}"
                if prefix
                else self.format_scheme_name
            ),
        )


@dataclass
class EmbeddedFont:
    """Embedded font reference for theme."""

    filename: str
    relationship_id: str
    data: bytes


@dataclass
class ThemeDefinition:
    """Complete theme definition for .thmx generation."""

    # Colors (12 OOXML theme colors)
    dk1: str = "000000"
    lt1: str = "FFFFFF"
    dk2: str = "44546A"
    lt2: str = "E7E6E6"
    accent1: str = "4472C4"
    accent2: str = "ED7D31"
    accent3: str = "A5A5A5"
    accent4: str = "FFC000"
    accent5: str = "5B9BD5"
    accent6: str = "70AD47"
    hlink: str = "0563C1"
    folHlink: str = "954F72"

    # Fonts
    major_font: str = "Inter"  # Heading font
    minor_font: str = "Inter"  # Body font

    # Metadata
    metadata: ThemeMetadata = field(default_factory=ThemeMetadata)

    # Embedded fonts (optional)
    embedded_fonts: List[EmbeddedFont] = field(default_factory=list)

    @classmethod
    def from_color_theme(
        cls,
        color_theme: ColorTheme,
        typography: Optional[TypographyScheme] = None,
        metadata: Optional[ThemeMetadata] = None,
    ) -> ThemeDefinition:
        """Create ThemeDefinition from IR models."""

        def strip_hash(color: str) -> str:
            return color.lstrip("#").upper()

        return cls(
            dk1=strip_hash(color_theme.dk1),
            lt1=strip_hash(color_theme.lt1),
            dk2=strip_hash(color_theme.dk2),
            lt2=strip_hash(color_theme.lt2),
            accent1=strip_hash(color_theme.accent1),
            accent2=strip_hash(color_theme.accent2),
            accent3=strip_hash(color_theme.accent3),
            accent4=strip_hash(color_theme.accent4),
            accent5=strip_hash(color_theme.accent5),
            accent6=strip_hash(color_theme.accent6),
            hlink=strip_hash(color_theme.hyperlink),
            folHlink=strip_hash(color_theme.hyperlink_followed),
            major_font=typography.heading_font if typography else "Inter",
            minor_font=typography.body_font if typography else "Inter",
            metadata=metadata or ThemeMetadata(),
        )


class ThmxEmitter:
    """Emitter for standalone Office Theme (.thmx) files.

    Uses SSOT templates from xml-structures/ecma-376/thmx/templates/.
    XML structure comes from templates; Python only substitutes values.

    Example:
        # Create from theme definition
        theme_def = ThemeDefinition(
            accent1="4472C4",
            accent2="ED7D31",
            major_font="Calibri",
            minor_font="Calibri",
        )
        emitter = ThmxEmitter(theme_def)
        emitter.save("my_theme.thmx")

        # Create from IR models
        theme_def = ThemeDefinition.from_color_theme(
            color_theme=document.color_theme,
            typography=document.typography,
        )
        emitter = ThmxEmitter(theme_def)
        data = emitter.emit()  # Returns bytes
    """

    # Package structure: archive path -> template name
    PACKAGE_STRUCTURE: Dict[str, str] = {
        "[Content_Types].xml": "content_types.xml",
        "_rels/.rels": "package_rels.xml",
        "theme/theme1.xml": "theme.xml",
    }

    def __init__(
        self,
        theme: ThemeDefinition,
        template_root: str | Path | None = None,
    ) -> None:
        """Initialize emitter with theme definition.

        Args:
            theme: Theme definition containing colors, fonts, and metadata
            template_root: Optional custom template root directory
        """
        self.theme = theme
        self.resolver = TemplateResolver("thmx", template_root)

    def _read_template(self, template_name: str) -> str:
        """Read template content from resolver."""
        return self.resolver.read_text(template_name)

    def _build_substitutions(self) -> Dict[str, str]:
        """Build substitution dictionary for templates."""
        # Build font content types (if we have embedded fonts)
        font_content_types = ""
        if self.theme.embedded_fonts:
            font_content_types = self._read_template("font_content_type.xml")

        # Build font relationships
        font_relationships = ""
        if self.theme.embedded_fonts:
            rel_template = self._read_template("font_relationship.xml")
            rels = []
            for font in self.theme.embedded_fonts:
                rel = Template(rel_template).safe_substitute(
                    REL_ID=font.relationship_id,
                    FILENAME=font.filename,
                )
                rels.append(rel)
            font_relationships = "".join(rels)

        # Build font embed elements (currently empty, fonts embedded via relationships)
        major_font_embed = ""
        minor_font_embed = ""

        return {
            # Metadata
            "THEME_NAME": self.theme.metadata.name,
            "COLOR_SCHEME_NAME": self.theme.metadata.color_scheme_name,
            "FONT_SCHEME_NAME": self.theme.metadata.font_scheme_name,
            "FORMAT_SCHEME_NAME": self.theme.metadata.format_scheme_name,
            # Colors
            "DK1": self.theme.dk1,
            "LT1": self.theme.lt1,
            "DK2": self.theme.dk2,
            "LT2": self.theme.lt2,
            "ACCENT1": self.theme.accent1,
            "ACCENT2": self.theme.accent2,
            "ACCENT3": self.theme.accent3,
            "ACCENT4": self.theme.accent4,
            "ACCENT5": self.theme.accent5,
            "ACCENT6": self.theme.accent6,
            "HLINK": self.theme.hlink,
            "FOLHLINK": self.theme.folHlink,
            # Fonts
            "MAJOR_FONT": self.theme.major_font,
            "MINOR_FONT": self.theme.minor_font,
            "MAJOR_FONT_EMBED": major_font_embed,
            "MINOR_FONT_EMBED": minor_font_embed,
            # Dynamic content
            "FONT_CONTENT_TYPES": font_content_types,
            "FONT_RELATIONSHIPS": font_relationships,
        }

    def emit(self) -> bytes:
        """Generate .thmx file as bytes.

        Returns:
            Bytes content of the .thmx archive
        """
        buffer = io.BytesIO()
        substitutions = self._build_substitutions()

        with zipfile.ZipFile(
            buffer, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
        ) as zf:
            # Write package structure from templates
            for archive_path, template_name in self.PACKAGE_STRUCTURE.items():
                template_content = self._read_template(template_name)
                content = Template(template_content).safe_substitute(substitutions)
                zf.writestr(archive_path, content.encode("utf-8"))

            # Write theme relationships if we have embedded fonts
            if self.theme.embedded_fonts:
                theme_rels_template = self._read_template("theme_rels.xml")
                theme_rels_content = Template(theme_rels_template).safe_substitute(
                    substitutions
                )
                zf.writestr(
                    "theme/_rels/theme1.xml.rels", theme_rels_content.encode("utf-8")
                )

                # Write embedded font files
                for font in self.theme.embedded_fonts:
                    font_data = font.data
                    try:
                        font_data = convert_font_bytes_for_embedding(
                            font_data,
                            strip_opentype_features=True,
                        )
                    except Exception:
                        font_data = font.data
                    zf.writestr(f"theme/fonts/{font.filename}", font_data)

        buffer.seek(0)
        return buffer.getvalue()

    def save(self, path: str | Path) -> None:
        """Save .thmx file to disk.

        Args:
            path: Output file path (should end with .thmx)
        """
        path = Path(path)
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_bytes(self.emit())


def create_thmx(
    colors: ColorTheme,
    typography: Optional[TypographyScheme] = None,
    metadata: Optional[ThemeMetadata] = None,
    output_path: Optional[str | Path] = None,
) -> bytes:
    """Convenience function to create a .thmx theme file.

    Args:
        colors: Color theme from IR model
        typography: Optional typography scheme
        metadata: Optional theme metadata
        output_path: Optional path to save the .thmx file

    Returns:
        Bytes content of the .thmx archive
    """
    theme_def = ThemeDefinition.from_color_theme(colors, typography, metadata)
    emitter = ThmxEmitter(theme_def)

    data = emitter.emit()

    if output_path:
        emitter.save(output_path)

    return data


__all__ = [
    "ThmxEmitter",
    "ThemeDefinition",
    "ThemeMetadata",
    "EmbeddedFont",
    "create_thmx",
    "NS_CONTENT_TYPES",
    "NS_RELATIONSHIPS",
    "NS_DRAWINGML",
]
