"""Core module for downloading and extracting GDSFactory+ sample projects.

Provides async functions to download sample project ZIPs from the registry
and extract sample files (Python, YAML) for use by AI assistants.
"""

from __future__ import annotations

import asyncio
import io
import logging
import mimetypes
import zipfile
from dataclasses import dataclass, field

import httpx

from .config import MCPConfig

__all__ = [
    "GENERAL_PDK_PROJECTS",
    "SAMPLE_EXTENSIONS",
    "SampleFile",
    "SampleProject",
    "get_sample_file_content",
    "list_sample_projects",
]

logger = logging.getLogger(__name__)

GENERAL_PDK_PROJECTS = [
    "photonics--basic--public--pdk",
    "photonics--full--public--pdk",
]

SAMPLE_EXTENSIONS = {".py", ".yml", ".yaml"}


@dataclass
class SampleProject:
    """A sample project with its list of sample file paths."""

    name: str
    files: list[str] = field(default_factory=list)


@dataclass
class SampleFile:
    """A single sample file with its content."""

    project: str
    path: str
    content: str
    mime_type: str


async def _download_project_zip(api_key: str, project_name: str) -> bytes:
    """Download a project ZIP from the registry.

    Args:
        api_key: GFP API key for authentication.
        project_name: Name of the project to download.

    Returns:
        Raw ZIP file bytes.

    Raises:
        httpx.HTTPStatusError: If the request fails.
    """
    url = f"{MCPConfig.REGISTRY_API_URL}/project/download/{project_name}"
    headers = {"X-API-Key": api_key}

    async with httpx.AsyncClient() as client:
        response = await client.get(url, headers=headers, timeout=300.0)
        response.raise_for_status()
        return response.content


def _extract_sample_files(zip_data: bytes, project_name: str) -> list[str]:
    """Extract sample file paths from a project ZIP.

    Finds files under any ``samples/`` directory, filters by extension,
    and excludes ``__init__.py`` and ``__pycache__`` entries.

    Args:
        zip_data: Raw ZIP file bytes.
        project_name: Project name (unused, kept for future use).

    Returns:
        Sorted list of file paths within the ZIP that are sample files.
    """
    sample_files = []

    with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
        for name in zf.namelist():
            if zf.getinfo(name).is_dir():
                continue

            # Must be under a samples/ directory
            parts = name.split("/")
            if "samples" not in parts:
                continue

            # Filter by extension
            ext = "." + name.rsplit(".", 1)[-1] if "." in name else ""
            if ext not in SAMPLE_EXTENSIONS:
                continue

            # Exclude __init__.py and __pycache__
            basename = parts[-1]
            if basename == "__init__.py" or "__pycache__" in parts:
                continue

            sample_files.append(name)

    return sorted(sample_files)


def _extract_file_content(zip_data: bytes, file_path: str) -> str:
    """Extract a single file's content from a ZIP.

    Args:
        zip_data: Raw ZIP file bytes.
        file_path: Path within the ZIP to extract.

    Returns:
        UTF-8 decoded file content.

    Raises:
        KeyError: If the file is not found in the ZIP.
    """
    with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
        return zf.read(file_path).decode("utf-8")


def _get_mime_type(path: str) -> str:
    """Determine MIME type from file path.

    Args:
        path: File path to check.

    Returns:
        MIME type string, defaults to ``text/plain``.
    """
    mime_type, _ = mimetypes.guess_type(path)
    return mime_type or "text/plain"


async def list_sample_projects(api_key: str) -> list[SampleProject]:
    """Download both General PDK ZIPs and list their sample files.

    Args:
        api_key: GFP API key for authentication.

    Returns:
        List of SampleProject instances with file listings.
    """
    tasks = [
        _download_project_zip(api_key, project) for project in GENERAL_PDK_PROJECTS
    ]
    results = await asyncio.gather(*tasks, return_exceptions=True)

    projects = []
    for project_name, result in zip(GENERAL_PDK_PROJECTS, results):
        if isinstance(result, Exception):
            logger.warning("Failed to download project %s: %s", project_name, result)
            projects.append(SampleProject(name=project_name, files=[]))
            continue

        files = _extract_sample_files(result, project_name)
        projects.append(SampleProject(name=project_name, files=files))

    return projects


async def get_sample_file_content(api_key: str, project: str, path: str) -> SampleFile:
    """Download a project ZIP and extract a specific file's content.

    Args:
        api_key: GFP API key for authentication.
        project: Project name (must be in GENERAL_PDK_PROJECTS).
        path: File path within the ZIP.

    Returns:
        SampleFile with the file's content and metadata.

    Raises:
        ValueError: If the project name is not recognized.
        KeyError: If the file is not found in the ZIP.
    """
    if project not in GENERAL_PDK_PROJECTS:
        raise ValueError(
            f"Unknown sample project: {project}. "
            f"Available projects: {GENERAL_PDK_PROJECTS}"
        )

    zip_data = await _download_project_zip(api_key, project)
    content = _extract_file_content(zip_data, path)
    mime_type = _get_mime_type(path)

    return SampleFile(
        project=project,
        path=path,
        content=content,
        mime_type=mime_type,
    )
