"""Model download from HuggingFace Hub with progress and verification."""

from __future__ import annotations

import contextlib
import logging
import os
import time

from llmhosts.models.checksums import verify_file as _verify_checksum
from llmhosts.models.registry import MODELS
from llmhosts.models.registry import models_dir as _default_models_dir

logger = logging.getLogger(__name__)

_MAX_DOWNLOAD_ATTEMPTS = 5  # retry limit for large file downloads


def _hf_download_with_retry(*, repo_id: str, filename: str, local_dir: str, revision: str) -> str:
    """Download a file from HuggingFace Hub with exponential backoff retry.

    huggingface_hub 1.4+ resumes incomplete downloads automatically via Range
    requests. This wrapper adds retry logic for transient network failures,
    which are common on residential connections for 26GB+ files (GAP-19).

    Returns the local path to the downloaded file.
    """
    try:
        from huggingface_hub import hf_hub_download
    except ImportError:
        raise RuntimeError(
            "huggingface_hub is required for model downloads. Install with: pip install llmhosts[smart]"
        ) from None

    last_exc: Exception | None = None
    for attempt in range(1, _MAX_DOWNLOAD_ATTEMPTS + 1):
        try:
            if attempt > 1:
                wait = min(2 ** (attempt - 1), 60)
                logger.info(
                    "Download attempt %d/%d for %s (waiting %ds)...",
                    attempt,
                    _MAX_DOWNLOAD_ATTEMPTS,
                    filename,
                    wait,
                )
                time.sleep(wait)
            else:
                logger.info("Downloading %s from %s...", filename, repo_id)

            # hf_hub_download resumes from an incomplete partial file automatically.
            # local_dir mode stores files at predictable paths (not cache hash dirs).
            return hf_hub_download(  # nosec B615 -- revision explicitly pinned to "main"
                repo_id=repo_id,
                filename=filename,
                local_dir=local_dir,
                revision=revision,
            )
        except Exception as exc:
            last_exc = exc
            logger.warning(
                "Download attempt %d/%d failed for %s: %s",
                attempt,
                _MAX_DOWNLOAD_ATTEMPTS,
                filename,
                exc,
            )
            if attempt == _MAX_DOWNLOAD_ATTEMPTS:
                break

    raise RuntimeError(
        f"Failed to download {filename} after {_MAX_DOWNLOAD_ATTEMPTS} attempts. "
        f"Last error: {last_exc}. "
        "Tip: Re-run `llmhosts pull` — the download will resume from where it stopped."
    ) from last_exc


def ensure_models_dir(path: str | None = None) -> str:
    """Create the models directory if it doesn't exist."""
    d = path or _default_models_dir()
    os.makedirs(d, exist_ok=True)
    return d


def is_downloaded(name: str, models_dir: str | None = None) -> bool:
    """Check if a model's GGUF file exists locally."""
    info = MODELS.get(name)
    if info is None:
        return False
    d = models_dir or _default_models_dir()
    return os.path.exists(os.path.join(d, info["file"]))


def list_local_models(models_dir: str | None = None) -> list[str]:
    """List model names whose GGUF files are present locally."""
    d = models_dir or _default_models_dir()
    if not os.path.exists(d):
        return []
    local_files = set(os.listdir(d))
    return [name for name, info in MODELS.items() if info["file"] in local_files]


def download_model(name: str, models_dir: str | None = None) -> str:
    """Download a GGUF model from HuggingFace Hub.

    Returns the local path to the downloaded file. Raises ValueError for
    unknown models, RuntimeError if huggingface_hub is not installed.
    """
    info = MODELS.get(name)
    if info is None:
        raise ValueError(f"Unknown model: {name}")

    d = ensure_models_dir(models_dir)
    dest = os.path.join(d, info["file"])

    if os.path.exists(dest):
        logger.info("Model already downloaded: %s", dest)
        return dest

    if info["size_gb"] >= 4:
        logger.info(
            "Downloading %.1fGB model %s — will resume automatically if interrupted",
            info["size_gb"],
            info["file"],
        )

    path = _hf_download_with_retry(
        repo_id=info["repo"],
        filename=info["file"],
        local_dir=d,
        revision="main",
    )

    # Verify SHA-256 against the LLMHosts-controlled manifest (GAP-15).
    # Protects against compromised third-party HuggingFace uploaders.
    verified = _verify_checksum(path, info["file"])
    if verified is False:
        # Hash mismatch — file is corrupt or tampered. Remove it and abort.
        with contextlib.suppress(OSError):
            os.remove(path)
        raise RuntimeError(
            f"SHA-256 mismatch for {info['file']} — file removed. "
            "This may indicate a supply chain compromise. "
            "Run `llmhosts doctor` and report to security@llmhosts.com."
        )

    # Also download tokenizer for the model
    try:
        _hf_download_with_retry(
            repo_id=info["tokenizer_repo"],
            filename="tokenizer.json",
            local_dir=d,
            revision="main",
        )
        logger.info("Tokenizer downloaded for %s", name)
    except Exception as exc:
        logger.warning("Could not download tokenizer for %s: %s", name, exc)

    return path
