"""Template loader for resolving and loading templates.

The loader searches for templates in this order:
1. Memory cache
2. Built-in templates
3. User templates (local store)
4. Cached remote templates
5. Remote gallery (with auto-caching)
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import TYPE_CHECKING

from gaol.templates.builtin import (
    BUILTIN_TEMPLATES,
    get_builtin_categories,
    list_builtin_templates,
)
from gaol.templates.exceptions import (
    TemplateNotFoundError,
    TemplateParseError,
)
from gaol.templates.models import (
    DEFAULT_CATEGORIES,
    CategoryInfo,
    Template,
    TemplateEntry,
    TemplateMetadata,
)
from gaol.templates.store import TemplateStore, get_template_store

if TYPE_CHECKING:
    from gaol.templates.gallery import GalleryManager

logger = logging.getLogger(__name__)


class TemplateLoader:
    """Load and manage templates from multiple sources.

    Resolution order:
    1. Memory cache
    2. Built-in templates
    3. User templates (local store)
    4. Cached remote templates
    5. Remote gallery (if enabled)
    """

    def __init__(
        self,
        store: TemplateStore | None = None,
        enable_remote: bool = True,
    ) -> None:
        """Initialize the template loader.

        Args:
            store: Template store for user templates
            enable_remote: Whether to enable remote gallery fetching
        """
        self._store = store or get_template_store()
        self._enable_remote = enable_remote
        self._cache: dict[str, Template] = {}
        self._gallery: GalleryManager | None = None

    @property
    def gallery(self) -> GalleryManager:
        """Get the gallery manager (lazy initialization)."""
        if self._gallery is None:
            from gaol.templates.gallery import GalleryManager
            self._gallery = GalleryManager()
        return self._gallery

    def get(self, template_id: str, use_cache: bool = True) -> Template:
        """Get a template by ID.

        Args:
            template_id: Template ID (e.g., 'web-dev/django')
            use_cache: Whether to use the memory cache

        Returns:
            Template object

        Raises:
            TemplateNotFoundError: If template not found
        """
        searched = []

        # 1. Memory cache
        if use_cache and template_id in self._cache:
            return self._cache[template_id]

        # 2. Built-in templates
        searched.append("builtin")
        if template_id in BUILTIN_TEMPLATES:
            template = self._load_builtin(template_id)
            self._cache[template_id] = template
            return template

        # 3. User templates
        searched.append("user")
        user_template = self._store.get(template_id)
        if user_template:
            self._cache[template_id] = user_template
            return user_template

        # 4. Remote gallery (if enabled)
        if self._enable_remote:
            searched.append("gallery")
            try:
                remote_template = self.gallery.fetch_template(template_id)
                if remote_template:
                    self._cache[template_id] = remote_template
                    return remote_template
            except Exception as e:
                logger.debug(f"Failed to fetch from gallery: {e}")

        raise TemplateNotFoundError(template_id, searched)

    def _load_builtin(self, template_id: str) -> Template:
        """Load a built-in template.

        Args:
            template_id: Template ID

        Returns:
            Template object

        Raises:
            TemplateParseError: If template data is invalid
        """
        data = BUILTIN_TEMPLATES.get(template_id)
        if not data:
            raise TemplateNotFoundError(template_id, ["builtin"])

        try:
            return Template.from_dict(data)
        except Exception as e:
            raise TemplateParseError(template_id, str(e), e) from e

    def list_local(self, category: str | None = None) -> list[Template]:
        """List all locally available templates.

        Includes built-in and user templates.

        Args:
            category: Filter by category

        Returns:
            List of templates
        """
        templates = []

        # Built-in templates
        for template_id in list_builtin_templates():
            if category:
                template_category = template_id.split("/")[0]
                if template_category != category:
                    continue
            try:
                template = self._load_builtin(template_id)
                templates.append(template)
            except Exception as e:
                logger.warning(f"Failed to load builtin template {template_id}: {e}")

        # User templates
        user_templates = self._store.list(category)
        templates.extend(user_templates)

        return templates

    def list_remote(self, refresh: bool = False) -> list[TemplateEntry]:
        """List templates from the remote gallery.

        Args:
            refresh: Force refresh of gallery index

        Returns:
            List of template entries
        """
        if not self._enable_remote:
            return []

        try:
            index = self.gallery.get_index(refresh=refresh)
            return list(index.templates.values())
        except Exception as e:
            logger.warning(f"Failed to list remote templates: {e}")
            return []

    def search(
        self,
        query: str,
        category: str | None = None,
        include_remote: bool = True,
    ) -> list[TemplateEntry]:
        """Search templates by query.

        Args:
            query: Search query (matches name, description, tags)
            category: Filter by category
            include_remote: Whether to include remote templates

        Returns:
            List of matching template entries
        """
        results: list[TemplateEntry] = []
        seen_ids: set[str] = set()
        query_lower = query.lower()

        # Search local templates
        for template in self.list_local(category):
            if self._matches_query(template, query_lower):
                entry = TemplateEntry.from_template(template, "local")
                if entry.id not in seen_ids:
                    results.append(entry)
                    seen_ids.add(entry.id)

        # Search remote templates
        if include_remote and self._enable_remote:
            try:
                index = self.gallery.get_index()
                remote_results = index.search(query, category)
                for entry in remote_results:
                    if entry.id not in seen_ids:
                        results.append(entry)
                        seen_ids.add(entry.id)
            except Exception as e:
                logger.debug(f"Remote search failed: {e}")

        return results

    def _matches_query(self, template: Template, query: str) -> bool:
        """Check if a template matches a search query.

        Args:
            template: Template to check
            query: Lowercase search query

        Returns:
            True if template matches
        """
        searchable = " ".join([
            template.metadata.display_name.lower(),
            template.metadata.description.lower(),
            " ".join(t.lower() for t in template.metadata.tags),
            template.metadata.id.lower(),
        ])
        return query in searchable

    def get_categories(self, include_remote: bool = True) -> list[CategoryInfo]:
        """Get all available categories.

        Args:
            include_remote: Whether to include remote categories

        Returns:
            List of category info
        """
        categories: dict[str, CategoryInfo] = {}

        # Start with defaults
        categories.update(DEFAULT_CATEGORIES)

        # Add builtin categories
        for cat in get_builtin_categories():
            if cat not in categories:
                categories[cat] = CategoryInfo(name=cat, display_name=cat.title())

        # Add user categories
        for cat in self._store.get_categories():
            if cat not in categories:
                categories[cat] = CategoryInfo(name=cat, display_name=cat.title())

        # Add remote categories
        if include_remote and self._enable_remote:
            try:
                index = self.gallery.get_index()
                for cat_name, cat_info in index.categories.items():
                    if cat_name not in categories:
                        categories[cat_name] = cat_info
            except Exception:
                pass

        return sorted(categories.values(), key=lambda c: c.name)

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

    def is_builtin(self, template_id: str) -> bool:
        """Check if a template is built-in.

        Args:
            template_id: Template ID

        Returns:
            True if template is built-in
        """
        return template_id in BUILTIN_TEMPLATES

    def is_local(self, template_id: str) -> bool:
        """Check if a template is available locally.

        Args:
            template_id: Template ID

        Returns:
            True if template is available locally
        """
        return (
            template_id in BUILTIN_TEMPLATES
            or self._store.exists(template_id)
        )


# Module-level loader instance
_template_loader: TemplateLoader | None = None


def get_template_loader() -> TemplateLoader:
    """Get the global template loader instance.

    Returns:
        TemplateLoader instance
    """
    global _template_loader
    if _template_loader is None:
        _template_loader = TemplateLoader()
    return _template_loader
