"""HTTP fetcher for remote templates.

Uses subprocess curl for consistency with the rest of the codebase,
with optional httpx support for better performance.
"""

from __future__ import annotations

import hashlib
import json
import logging
import subprocess
from pathlib import Path
from typing import Any

import yaml

from gaol.config import get_data_dir
from gaol.templates.exceptions import GalleryFetchError

logger = logging.getLogger(__name__)


class TemplateFetcher:
    """Fetch templates from remote URLs.

    Uses curl subprocess by default, with optional httpx support
    for better performance when available.
    """

    def __init__(
        self,
        cache_dir: Path | None = None,
        timeout: int = 30,
    ) -> None:
        """Initialize the fetcher.

        Args:
            cache_dir: Directory for caching fetched content
            timeout: Request timeout in seconds
        """
        if cache_dir is None:
            cache_dir = get_data_dir() / "templates" / "cache"
        self._cache_dir = cache_dir
        self._cache_dir.mkdir(parents=True, exist_ok=True)
        self._timeout = timeout
        self._httpx_available: bool | None = None

    def _check_httpx(self) -> bool:
        """Check if httpx is available."""
        if self._httpx_available is None:
            try:
                import httpx  # noqa: F401
                self._httpx_available = True
            except ImportError:
                self._httpx_available = False
        return self._httpx_available

    def fetch(self, url: str) -> str:
        """Fetch content from a URL.

        Args:
            url: URL to fetch

        Returns:
            Response content as string

        Raises:
            GalleryFetchError: If fetch fails
        """
        if self._check_httpx():
            return self._fetch_httpx(url)
        return self._fetch_curl(url)

    def _fetch_curl(self, url: str) -> str:
        """Fetch content using curl subprocess.

        Args:
            url: URL to fetch

        Returns:
            Response content

        Raises:
            GalleryFetchError: If curl fails
        """
        try:
            result = subprocess.run(
                [
                    "curl",
                    "-fsSL",  # fail, silent, show errors, follow redirects
                    "--max-time", str(self._timeout),
                    url,
                ],
                capture_output=True,
                text=True,
                timeout=self._timeout + 5,  # Extra time for subprocess
            )

            if result.returncode != 0:
                raise GalleryFetchError(
                    url,
                    f"curl failed with exit code {result.returncode}: {result.stderr}",
                )

            return result.stdout

        except subprocess.TimeoutExpired as e:
            raise GalleryFetchError(url, f"Request timed out after {self._timeout}s", e)
        except FileNotFoundError as e:
            raise GalleryFetchError(url, "curl not found in PATH", e)
        except Exception as e:
            raise GalleryFetchError(url, str(e), e)

    def _fetch_httpx(self, url: str) -> str:
        """Fetch content using httpx.

        Args:
            url: URL to fetch

        Returns:
            Response content

        Raises:
            GalleryFetchError: If request fails
        """
        try:
            import httpx

            with httpx.Client(timeout=self._timeout, follow_redirects=True) as client:
                response = client.get(url)
                response.raise_for_status()
                return response.text

        except Exception as e:
            raise GalleryFetchError(url, str(e), e)

    def fetch_json(self, url: str) -> dict[str, Any]:
        """Fetch and parse JSON from a URL.

        Args:
            url: URL to fetch

        Returns:
            Parsed JSON as dict

        Raises:
            GalleryFetchError: If fetch or parse fails
        """
        content = self.fetch(url)
        try:
            return json.loads(content)
        except json.JSONDecodeError as e:
            raise GalleryFetchError(url, f"Invalid JSON: {e}", e)

    def fetch_yaml(self, url: str) -> dict[str, Any]:
        """Fetch and parse YAML from a URL.

        Args:
            url: URL to fetch

        Returns:
            Parsed YAML as dict

        Raises:
            GalleryFetchError: If fetch or parse fails
        """
        content = self.fetch(url)
        try:
            return yaml.safe_load(content)
        except yaml.YAMLError as e:
            raise GalleryFetchError(url, f"Invalid YAML: {e}", e)

    def fetch_cached(
        self,
        url: str,
        max_age_seconds: int = 3600,
    ) -> str:
        """Fetch content with caching.

        Args:
            url: URL to fetch
            max_age_seconds: Maximum cache age in seconds

        Returns:
            Response content (from cache if valid)

        Raises:
            GalleryFetchError: If fetch fails
        """
        import time

        cache_key = self._get_cache_key(url)
        cache_path = self._cache_dir / f"{cache_key}.txt"
        meta_path = self._cache_dir / f"{cache_key}.meta"

        # Check cache
        if cache_path.exists() and meta_path.exists():
            try:
                meta = json.loads(meta_path.read_text())
                cached_time = meta.get("fetched_at", 0)
                if time.time() - cached_time < max_age_seconds:
                    logger.debug(f"Using cached content for {url}")
                    return cache_path.read_text()
            except Exception:
                pass  # Invalid cache, will refetch

        # Fetch fresh content
        content = self.fetch(url)

        # Save to cache
        try:
            cache_path.write_text(content)
            meta_path.write_text(json.dumps({
                "url": url,
                "fetched_at": time.time(),
            }))
        except Exception as e:
            logger.warning(f"Failed to cache content: {e}")

        return content

    def _get_cache_key(self, url: str) -> str:
        """Generate a cache key from a URL.

        Args:
            url: URL to hash

        Returns:
            Cache key string
        """
        return hashlib.sha256(url.encode()).hexdigest()[:32]

    def clear_cache(self) -> int:
        """Clear all cached content.

        Returns:
            Number of files deleted
        """
        count = 0
        for path in self._cache_dir.glob("*"):
            try:
                path.unlink()
                count += 1
            except Exception:
                pass
        return count

    def get_cache_size(self) -> int:
        """Get total size of cached content.

        Returns:
            Size in bytes
        """
        total = 0
        for path in self._cache_dir.glob("*"):
            try:
                total += path.stat().st_size
            except Exception:
                pass
        return total
