"""
Template management system for pyPhotoAlbum
"""

import json
import os
from pathlib import Path
from typing import List, Dict, Any, Tuple, Optional
from pyPhotoAlbum.models import BaseLayoutElement, ImageData, PlaceholderData, TextBoxData
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.project import Page


class Template:
    """Class representing a page layout template"""

    def __init__(
        self, name: str = "Untitled Template", description: str = "", page_size_mm: Tuple[float, float] = (210, 297)
    ):
        self.name = name
        self.description = description
        self.page_size_mm = page_size_mm
        self.elements: List[BaseLayoutElement] = []

    def add_element(self, element: BaseLayoutElement):
        """Add an element to the template"""
        self.elements.append(element)

    def to_dict(self) -> Dict[str, Any]:
        """Serialize template to dictionary"""
        return {
            "name": self.name,
            "description": self.description,
            "page_size_mm": self.page_size_mm,
            "elements": [elem.serialize() for elem in self.elements],
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Template":
        """Deserialize template from dictionary"""
        template = cls(
            name=data.get("name", "Untitled Template"),
            description=data.get("description", ""),
            page_size_mm=tuple(data.get("page_size_mm", (210, 297))),
        )

        # Deserialize elements
        for elem_data in data.get("elements", []):
            elem_type = elem_data.get("type")
            elem: BaseLayoutElement
            if elem_type == "placeholder":
                elem = PlaceholderData()
            elif elem_type == "textbox":
                elem = TextBoxData()
            else:
                continue  # Skip image elements in templates

            elem.deserialize(elem_data)
            template.add_element(elem)

        return template

    def save_to_file(self, file_path: str):
        """Save template to JSON file"""
        with open(file_path, "w") as f:
            json.dump(self.to_dict(), f, indent=2)

    @classmethod
    def load_from_file(cls, file_path: str) -> "Template":
        """Load template from JSON file"""
        with open(file_path, "r") as f:
            data = json.load(f)
        return cls.from_dict(data)


class TemplateManager:
    """Manager for template operations"""

    def __init__(self, project=None):
        self.templates_dir = self._get_templates_directory()
        self._ensure_templates_directory()
        self.project = project  # Optional project for embedded templates

    def _get_templates_directory(self) -> Path:
        """Get the templates directory path"""
        # User templates directory
        home = Path.home()
        templates_dir = home / ".pyphotoalbum" / "templates"
        return templates_dir

    def _get_builtin_templates_directory(self) -> Path:
        """Get the built-in templates directory path"""
        # Built-in templates in the application directory
        app_dir = Path(__file__).parent
        return app_dir / "templates"

    def _ensure_templates_directory(self):
        """Create templates directory if it doesn't exist"""
        self.templates_dir.mkdir(parents=True, exist_ok=True)

        # Also ensure built-in templates directory exists
        builtin_dir = self._get_builtin_templates_directory()
        builtin_dir.mkdir(parents=True, exist_ok=True)

    def list_templates(self) -> List[str]:
        """List all available template names (embedded + user + built-in)"""
        templates = []

        # List embedded templates (priority)
        if self.project and self.project.embedded_templates:
            for template_name in self.project.embedded_templates.keys():
                templates.append(f"[Embedded] {template_name}")

        # List user templates
        if self.templates_dir.exists():
            for file in self.templates_dir.glob("*.json"):
                templates.append(file.stem)

        # List built-in templates
        builtin_dir = self._get_builtin_templates_directory()
        if builtin_dir.exists():
            for file in builtin_dir.glob("*.json"):
                template_name = f"[Built-in] {file.stem}"
                templates.append(template_name)

        return sorted(templates)

    def load_template(self, name: str) -> Template:
        """
        Load a template by name with priority: embedded > user > built-in.

        Args:
            name: Template name (may include prefix like '[Embedded]' or '[Built-in]')

        Returns:
            Template instance
        """
        # Check if it's an embedded template (priority)
        if name.startswith("[Embedded] "):
            actual_name = name.replace("[Embedded] ", "")
            if self.project and actual_name in self.project.embedded_templates:
                template_data = self.project.embedded_templates[actual_name]
                return Template.from_dict(template_data)
            raise FileNotFoundError(f"Embedded template '{actual_name}' not found")

        # Check embedded templates even without prefix (for backward compatibility)
        if self.project and name in self.project.embedded_templates:
            template_data = self.project.embedded_templates[name]
            return Template.from_dict(template_data)

        # Check if it's a built-in template
        if name.startswith("[Built-in] "):
            actual_name = name.replace("[Built-in] ", "")
            template_path = self._get_builtin_templates_directory() / f"{actual_name}.json"
        else:
            # User template
            template_path = self.templates_dir / f"{name}.json"

        if not template_path.exists():
            raise FileNotFoundError(f"Template '{name}' not found")

        return Template.load_from_file(str(template_path))

    def save_template(self, template: Template, embed_in_project: bool = False):
        """
        Save a template to filesystem or embed in project.

        Args:
            template: Template to save
            embed_in_project: If True, embed in project instead of saving to filesystem
        """
        if embed_in_project and self.project:
            # Embed in project
            self.project.embedded_templates[template.name] = template.to_dict()
            print(f"Embedded template '{template.name}' in project")
        else:
            # Save to filesystem
            template_path = self.templates_dir / f"{template.name}.json"
            template.save_to_file(str(template_path))

    def delete_template(self, name: str):
        """Delete a template (embedded or user templates only)"""
        if name.startswith("[Built-in] "):
            raise PermissionError("Cannot delete built-in templates")

        # Check if it's an embedded template
        if name.startswith("[Embedded] "):
            actual_name = name.replace("[Embedded] ", "")
            if self.project and actual_name in self.project.embedded_templates:
                del self.project.embedded_templates[actual_name]
                print(f"Removed embedded template '{actual_name}'")
                return
            raise FileNotFoundError(f"Embedded template '{actual_name}' not found")

        # User template from filesystem
        template_path = self.templates_dir / f"{name}.json"
        if template_path.exists():
            template_path.unlink()

    def embed_template(self, template: Template):
        """
        Embed a template in the project.

        Args:
            template: Template to embed
        """
        if not self.project:
            raise RuntimeError("No project associated with this TemplateManager")

        self.project.embedded_templates[template.name] = template.to_dict()
        print(f"Embedded template '{template.name}' in project")

    def create_template_from_page(self, page: Page, name: str, description: str = "") -> Template:
        """
        Create a template from an existing page.
        Converts all ImageData elements to PlaceholderData.
        """
        template = Template(name=name, description=description, page_size_mm=page.layout.size)

        # Convert elements
        for element in page.layout.elements:
            if isinstance(element, ImageData):
                # Convert image to placeholder
                placeholder = PlaceholderData(
                    placeholder_type="image",
                    x=element.position[0],
                    y=element.position[1],
                    width=element.size[0],
                    height=element.size[1],
                    rotation=element.rotation,
                    z_index=element.z_index,
                )
                template.add_element(placeholder)
            elif isinstance(element, TextBoxData):
                # Keep text boxes as-is
                text_box = TextBoxData(
                    text_content=element.text_content,
                    font_settings=element.font_settings,
                    alignment=element.alignment,
                    x=element.position[0],
                    y=element.position[1],
                    width=element.size[0],
                    height=element.size[1],
                    rotation=element.rotation,
                    z_index=element.z_index,
                )
                template.add_element(text_box)
            elif isinstance(element, PlaceholderData):
                # Keep placeholders as-is
                placeholder = PlaceholderData(
                    placeholder_type=element.placeholder_type,
                    default_content=element.default_content,
                    x=element.position[0],
                    y=element.position[1],
                    width=element.size[0],
                    height=element.size[1],
                    rotation=element.rotation,
                    z_index=element.z_index,
                )
                template.add_element(placeholder)

        return template

    def scale_template_elements(
        self,
        elements: List[BaseLayoutElement],
        from_size: Tuple[float, float],
        to_size: Tuple[float, float],
        scale_mode: str = "proportional",
        margin_percent: float = 0.0,
    ) -> List[BaseLayoutElement]:
        """
        Scale template elements to fit target page size with adjustable margins.

        Args:
            elements: List of elements to scale
            from_size: Original template size (width, height) in mm
            to_size: Target page size (width, height) in mm
            scale_mode: "proportional", "stretch", or "center"
            margin_percent: Percentage of page size to use for margins (0-10%)

        Returns:
            List of scaled elements
        """
        from_width, from_height = from_size
        to_width, to_height = to_size

        # Calculate target margins from percentage
        margin_x = to_width * (margin_percent / 100.0)
        margin_y = to_height * (margin_percent / 100.0)

        # Available content area after margins
        content_width = to_width - (2 * margin_x)
        content_height = to_height - (2 * margin_y)

        # Calculate scale factors based on mode
        if scale_mode == "stretch":
            # Stretch to fill content area independently in each dimension
            scale_x = content_width / from_width
            scale_y = content_height / from_height
            offset_x = margin_x
            offset_y = margin_y
        elif scale_mode == "proportional":
            # Maintain aspect ratio - scale uniformly to fit content area
            scale = min(content_width / from_width, content_height / from_height)
            scale_x = scale
            scale_y = scale
            # Center the scaled content within the page
            scaled_width = from_width * scale
            scaled_height = from_height * scale
            offset_x = (to_width - scaled_width) / 2
            offset_y = (to_height - scaled_height) / 2
        else:  # "center"
            # No scaling, just center on page
            scale_x = 1.0
            scale_y = 1.0
            offset_x = (to_width - from_width) / 2
            offset_y = (to_height - from_height) / 2

        scaled_elements: List[BaseLayoutElement] = []
        for element in elements:
            # Create a new element of the same type
            new_elem: BaseLayoutElement
            if isinstance(element, PlaceholderData):
                new_elem = PlaceholderData(
                    placeholder_type=element.placeholder_type, default_content=element.default_content
                )
            elif isinstance(element, TextBoxData):
                new_elem = TextBoxData(
                    text_content=element.text_content,
                    font_settings=element.font_settings.copy() if element.font_settings else None,
                    alignment=element.alignment,
                )
            else:
                continue  # Skip other types

            # Scale position and size (still in mm)
            old_x, old_y = element.position
            old_w, old_h = element.size

            new_elem.position = (old_x * scale_x + offset_x, old_y * scale_y + offset_y)
            new_elem.size = (old_w * scale_x, old_h * scale_y)
            new_elem.rotation = element.rotation
            new_elem.z_index = element.z_index

            scaled_elements.append(new_elem)

        # Convert all elements from mm to pixels (DPI conversion)
        # The rest of the application uses pixels, not mm
        dpi = 300  # Default DPI (should match project working_dpi if available)
        if self.project:
            dpi = self.project.working_dpi

        mm_to_px = dpi / 25.4

        for elem in scaled_elements:
            # Convert position from mm to pixels
            elem.position = (elem.position[0] * mm_to_px, elem.position[1] * mm_to_px)
            # Convert size from mm to pixels
            elem.size = (elem.size[0] * mm_to_px, elem.size[1] * mm_to_px)

        return scaled_elements

    def apply_template_to_page(
        self,
        template: Template,
        page: Page,
        mode: str = "replace",
        scale_mode: str = "proportional",
        margin_percent: float = 2.5,
        auto_embed: bool = True,
    ):
        """
        Apply template to an existing page with adjustable margins.

        Args:
            template: Template to apply
            page: Target page
            mode: "replace" to clear page and add placeholders,
                  "reflow" to keep existing content and reposition
            scale_mode: "proportional", "stretch", or "center"
            margin_percent: Percentage of page size to use for margins (0-10%)
            auto_embed: If True, automatically embed template in project
        """
        # Auto-embed template if requested and not already embedded
        if auto_embed and self.project:
            if template.name not in self.project.embedded_templates:
                self.embed_template(template)

        if mode == "replace":
            # Clear existing elements
            page.layout.elements.clear()

            # Scale template elements to fit page
            scaled_elements = self.scale_template_elements(
                template.elements, template.page_size_mm, page.layout.size, scale_mode, margin_percent
            )

            # Add scaled elements to page
            for element in scaled_elements:
                page.layout.add_element(element)

        elif mode == "reflow":
            # Keep existing content but reposition to template slots
            existing_images = [e for e in page.layout.elements if isinstance(e, ImageData)]
            existing_text = [e for e in page.layout.elements if isinstance(e, TextBoxData)]

            # Get template placeholders (scaled)
            scaled_elements = self.scale_template_elements(
                template.elements, template.page_size_mm, page.layout.size, scale_mode, margin_percent
            )

            template_placeholders = [e for e in scaled_elements if isinstance(e, PlaceholderData)]
            template_text = [e for e in scaled_elements if isinstance(e, TextBoxData)]

            # Clear page
            page.layout.elements.clear()

            # Reflow images into placeholder slots
            for i, placeholder in enumerate(template_placeholders):
                if i < len(existing_images):
                    # Use existing image, update position/size
                    img = existing_images[i]
                    img.position = placeholder.position
                    img.size = placeholder.size
                    img.z_index = placeholder.z_index
                    page.layout.add_element(img)
                else:
                    # Add placeholder if no more images
                    page.layout.add_element(placeholder)

            # Add remaining images (if any) at their original positions
            for img in existing_images[len(template_placeholders) :]:
                page.layout.add_element(img)

            # Add template text boxes
            for text_elem in template_text:
                page.layout.add_element(text_elem)

    def create_page_from_template(
        self,
        template: Template,
        page_number: int = 1,
        target_size_mm: Optional[Tuple[float, float]] = None,
        scale_mode: str = "proportional",
        margin_percent: float = 2.5,
        auto_embed: bool = True,
    ) -> Page:
        """
        Create a new page from a template.

        Args:
            template: Template to use
            page_number: Page number for the new page
            target_size_mm: Target page size (if different from template)
            scale_mode: Scaling mode if target_size_mm is provided
            margin_percent: Percentage of page size to use for margins (0-10%)
            auto_embed: If True, automatically embed template in project

        Returns:
            New Page instance with template layout
        """
        # Auto-embed template if requested and not already embedded
        if auto_embed and self.project:
            if template.name not in self.project.embedded_templates:
                self.embed_template(template)

        # Determine page size
        if target_size_mm is None:
            page_size = template.page_size_mm
            elements = [e for e in template.elements]  # Copy elements as-is
        else:
            page_size = target_size_mm
            # Scale template elements with margins
            elements = self.scale_template_elements(
                template.elements, template.page_size_mm, target_size_mm, scale_mode, margin_percent
            )

        # Create new page layout
        layout = PageLayout(width=page_size[0], height=page_size[1])

        # Add elements
        for element in elements:
            layout.add_element(element)

        # Create and return page
        page = Page(layout=layout, page_number=page_number)
        return page
