"""Download and extract a PyPI package, returning a populated PackageInfo."""
from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any

from pyguard.models import PackageInfo, PackageMetadata, PyPIRelease
from pyguard.utils.pypi_client import (
    download_package,
    extract_archive,
    fetch_metadata,
    fetch_release_history,
)

IGNORED_DIR_NAMES = {
    ".git",
    ".hg",
    ".mypy_cache",
    ".pytest_cache",
    ".ruff_cache",
    ".venv",
    "build",
    "dist",
    "__pycache__",
    "site-packages",
    "*.egg-info",
}


def _load_files(source_dir: Path) -> dict[str, str]:
    files: dict[str, str] = {}
    for path in source_dir.rglob("*.py"):
        relative = path.relative_to(source_dir)
        if any(part in IGNORED_DIR_NAMES for part in relative.parts):
            continue
        try:
            files[str(relative)] = path.read_text(encoding="utf-8")
        except (UnicodeDecodeError, OSError):
            continue
    return files


def _pick_asset(
    assets: list[dict[str, Any]],
) -> dict[str, Any] | None:
    """Prefer sdist (.tar.gz) over wheel for source-level analysis."""
    sdists = [a for a in assets if a.get("packagetype") == "sdist"]
    wheels = [a for a in assets if a.get("packagetype") == "bdist_wheel"]
    return (sdists or wheels or [None])[0]


async def download_and_extract(
    name: str, version: str, work_dir: Path | None = None
) -> PackageInfo:
    """
    Fetch *name=={version}* from PyPI, download the best available asset,
    extract it to a temp directory, and return a populated PackageInfo.
    """
    data = await fetch_metadata(name, version)
    info: dict[str, Any] = data.get("info", {})
    urls: list[dict[str, Any]] = data.get("urls", [])

    # Build metadata
    raw_history = await fetch_release_history(name)
    release_history = [
        PyPIRelease(
            version=r["version"],
            upload_time=r["upload_time"],
            yanked=r.get("yanked", False),
        )
        for r in raw_history
    ]
    project_urls: dict[str, str] = info.get("project_urls") or {}
    metadata = PackageMetadata(
        author=info.get("author") or None,
        author_email=info.get("author_email") or None,
        maintainer=info.get("maintainer") or None,
        maintainer_email=info.get("maintainer_email") or None,
        home_page=info.get("home_page") or None,
        project_urls={k: v for k, v in project_urls.items() if v},
        summary=info.get("summary") or None,
        license=info.get("license") or None,
        release_history=release_history,
    )

    asset = _pick_asset(urls)
    if asset is None:
        # No downloadable asset; return bare PackageInfo with metadata only
        return PackageInfo(
            name=name,
            version=version,
            source_path=Path("."),
            metadata=metadata,
        )

    url: str = asset["url"]
    expected_sha256: str = asset.get("digests", {}).get("sha256", "")
    filename: str = asset["filename"]

    parent = work_dir or Path(tempfile.mkdtemp(prefix="pyguard_"))
    archive_path = parent / filename
    extract_dir = parent / filename.replace(".tar.gz", "").replace(".whl", "").replace(".zip", "")

    if expected_sha256:
        await download_package(url, expected_sha256, archive_path)
    else:
        # Download without hash verification (rare for PyPI)
        import httpx  # noqa: PLC0415

        async with httpx.AsyncClient(timeout=httpx.Timeout(120.0), follow_redirects=True) as client:
            resp = await client.get(url)
            resp.raise_for_status()
            archive_path.write_bytes(resp.content)

    extract_archive(archive_path, extract_dir)
    files = _load_files(extract_dir)

    return PackageInfo(
        name=name,
        version=version,
        source_path=extract_dir,
        files=files,
        metadata=metadata,
    )
