"""Async audio download pool with Rich progress bars and resume support."""

from __future__ import annotations

import asyncio
from pathlib import Path

import aiohttp
import structlog

from xc_dl.core.manifest import Manifest, ManifestEntry
from xc_dl.core.progress import DownloadState
from xc_dl.core.verifier import verify_download

logger = structlog.get_logger()


async def _download_one(
    session: aiohttp.ClientSession,
    entry: ManifestEntry,
    data_dir: Path,
    semaphore: asyncio.Semaphore,
    download_state: DownloadState,
    max_retries: int = 3,
    file_progress_callback: object = None,
    overall_callback: object = None,
) -> bool:
    """Download a single audio file with retries.

    Returns True if download succeeded and verified.
    """
    dest = data_dir / entry.original_path
    dest.parent.mkdir(parents=True, exist_ok=True)

    for attempt in range(1, max_retries + 1):
        async with semaphore:
            download_state.mark_downloading(entry.xc_id)
            try:
                timeout = aiohttp.ClientTimeout(total=300, sock_read=60)
                async with session.get(entry.download_url, timeout=timeout) as resp:
                    if resp.status != 200:
                        logger.warning(
                            "download_http_error",
                            xc_id=entry.xc_id,
                            status=resp.status,
                            attempt=attempt,
                        )
                        if resp.status in (429, 503) and attempt < max_retries:
                            await asyncio.sleep(min(2**attempt, 60))
                            continue
                        if attempt >= max_retries:
                            download_state.mark_failed(entry.xc_id, f"HTTP {resp.status}", attempt)
                            return False
                        continue

                    expected_size = resp.content_length
                    received = 0

                    # Notify progress callback about this file starting
                    task_id = None
                    if file_progress_callback:
                        task_id = await file_progress_callback(
                            "start", entry.xc_id, entry.original_filename, expected_size or 0
                        )

                    tmp = dest.with_suffix(dest.suffix + ".tmp")
                    with open(tmp, "wb") as f:
                        async for chunk in resp.content.iter_chunked(1 << 16):
                            f.write(chunk)
                            received += len(chunk)
                            if file_progress_callback and task_id is not None:
                                await file_progress_callback("progress", task_id, None, received)

                    if file_progress_callback and task_id is not None:
                        await file_progress_callback("done", task_id, None, None)

                    # Verify
                    ok, sha256 = await verify_download(tmp, expected_size)
                    if not ok:
                        logger.warning(
                            "download_verify_failed",
                            xc_id=entry.xc_id,
                            attempt=attempt,
                        )
                        tmp.unlink(missing_ok=True)
                        if attempt < max_retries:
                            continue
                        download_state.mark_failed(entry.xc_id, "verification_failed", attempt)
                        return False

                    tmp.rename(dest)
                    download_state.mark_verified(entry.xc_id, sha256, dest.stat().st_size)

                    logger.debug(
                        "download_complete",
                        xc_id=entry.xc_id,
                        species=f"{entry.gen} {entry.sp}",
                        size_bytes=dest.stat().st_size,
                    )

                    if overall_callback:
                        await overall_callback()

                    return True

            except (aiohttp.ClientError, asyncio.TimeoutError, OSError) as exc:
                logger.warning(
                    "download_error",
                    xc_id=entry.xc_id,
                    error=str(exc),
                    attempt=attempt,
                )
                if attempt < max_retries:
                    await asyncio.sleep(min(2**attempt, 60))
                    continue
                download_state.mark_failed(entry.xc_id, str(exc), attempt)
                return False

    return False


async def download_recordings(
    session: aiohttp.ClientSession,
    entries: list[ManifestEntry],
    data_dir: Path,
    concurrency: int = 8,
    max_retries: int = 3,
    retry_failed: bool = True,
    skip_existing: bool = True,
    file_list_ids: set[str] | None = None,
    node_suffix: str = "",
    file_progress_callback: object = None,
    overall_callback: object = None,
) -> tuple[int, int, int]:
    """Download audio files for the given manifest entries.

    Returns (succeeded, failed, skipped) counts.
    """
    progress_dir = data_dir / ".progress"
    progress_dir.mkdir(parents=True, exist_ok=True)

    download_state = DownloadState(progress_dir, suffix=node_suffix)
    semaphore = asyncio.Semaphore(concurrency)

    # Filter entries
    to_download: list[ManifestEntry] = []
    skipped = 0
    for entry in entries:
        if file_list_ids and entry.xc_id not in file_list_ids:
            skipped += 1
            continue
        status = download_state.get_status(entry.xc_id)
        if status == "verified" and skip_existing:
            # Also check file actually exists
            if (data_dir / entry.original_path).exists():
                skipped += 1
                continue
        if status == "failed" and not retry_failed:
            skipped += 1
            continue
        to_download.append(entry)

    logger.info(
        "download_plan",
        total=len(entries),
        to_download=len(to_download),
        skipped=skipped,
    )

    if not to_download:
        return 0, 0, skipped

    tasks = [
        _download_one(
            session,
            entry,
            data_dir,
            semaphore,
            download_state,
            max_retries=max_retries,
            file_progress_callback=file_progress_callback,
            overall_callback=overall_callback,
        )
        for entry in to_download
    ]

    results = await asyncio.gather(*tasks, return_exceptions=True)

    succeeded = sum(1 for r in results if r is True)
    failed = len(results) - succeeded

    logger.info(
        "download_summary",
        succeeded=succeeded,
        failed=failed,
        skipped=skipped,
    )

    # Update manifest with download status
    manifest = Manifest(data_dir)
    for entry in to_download:
        status = download_state.get_status(entry.xc_id)
        if status == "verified":
            state = download_state._state.get(entry.xc_id, {})
            manifest.update_download_status(
                entry.xc_id,
                sha256=state.get("sha256", ""),
                file_size=state.get("file_size_bytes", 0),
            )

    return succeeded, failed, skipped
