"""File hash computation and integrity verification."""

from __future__ import annotations

import asyncio
import hashlib
import shutil
import subprocess
from pathlib import Path

import structlog

logger = structlog.get_logger()

CHUNK_SIZE = 1 << 16  # 64 KB


async def compute_sha256(filepath: Path) -> str:
    """Compute SHA-256 hash of a file asynchronously (runs in thread pool)."""
    def _hash() -> str:
        h = hashlib.sha256()
        with open(filepath, "rb") as f:
            while True:
                chunk = f.read(CHUNK_SIZE)
                if not chunk:
                    break
                h.update(chunk)
        return h.hexdigest()

    return await asyncio.get_event_loop().run_in_executor(None, _hash)


def verify_content_length(filepath: Path, expected: int | None) -> bool:
    """Check that the downloaded file size matches the Content-Length header."""
    if expected is None:
        return True
    actual = filepath.stat().st_size
    if actual != expected:
        logger.warning(
            "content_length_mismatch",
            path=str(filepath),
            expected=expected,
            actual=actual,
        )
        return False
    return True


def probe_audio(filepath: Path) -> dict | None:
    """Use ffprobe to verify the file is a valid audio container.

    Returns a dict with format info on success, None on failure.
    """
    ffprobe = shutil.which("ffprobe")
    if not ffprobe:
        logger.debug("ffprobe_not_found", message="ffprobe not available, skipping probe")
        return None

    try:
        result = subprocess.run(
            [
                ffprobe,
                "-v", "quiet",
                "-print_format", "json",
                "-show_format",
                "-show_streams",
                str(filepath),
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        if result.returncode != 0:
            logger.warning("ffprobe_failed", path=str(filepath), stderr=result.stderr[:200])
            return None

        import json
        info = json.loads(result.stdout)
        return info
    except (subprocess.TimeoutExpired, Exception) as exc:
        logger.warning("ffprobe_error", path=str(filepath), error=str(exc))
        return None


async def verify_download(filepath: Path, expected_size: int | None = None) -> tuple[bool, str]:
    """Verify a downloaded file. Returns (ok, sha256)."""
    if not filepath.exists():
        return False, ""

    if not verify_content_length(filepath, expected_size):
        return False, ""

    sha256 = await compute_sha256(filepath)
    return True, sha256


async def probe_audio_async(filepath: Path) -> dict | None:
    """Async wrapper around probe_audio (runs in thread pool)."""
    return await asyncio.get_event_loop().run_in_executor(None, probe_audio, filepath)


async def deep_verify(filepath: Path) -> bool:
    """Fully decode the audio stream to detect corruption. Uses ffprobe/ffmpeg."""
    ffmpeg = shutil.which("ffmpeg")
    if not ffmpeg:
        logger.warning("ffmpeg_not_found", message="ffmpeg not available for deep verification")
        return True  # Can't verify without ffmpeg

    def _decode() -> bool:
        result = subprocess.run(
            [ffmpeg, "-v", "error", "-i", str(filepath), "-f", "null", "-"],
            capture_output=True,
            text=True,
            timeout=120,
        )
        if result.returncode != 0 or result.stderr.strip():
            logger.warning(
                "deep_verify_failed",
                path=str(filepath),
                stderr=result.stderr[:200],
            )
            return False
        return True

    return await asyncio.get_event_loop().run_in_executor(None, _decode)
