"""Media download service for downloading files from various sources."""

import asyncio
import hashlib
import logging
from pathlib import Path
from typing import Any, Dict, Optional

import yt_dlp
from tenacity import AsyncRetrying, stop_after_attempt, wait_exponential, retry_if_exception_type

from .media_types import (
    DownloadError,
    MediaFileInfo,
    ProgressCallback,
    ValidationError,
)
from .media_telemetry import MediaTelemetry, ProgressTracker

logger = logging.getLogger(__name__)


class MediaDownloadService:
    """Service for downloading media files from various sources."""

    def __init__(self, config: Optional[Dict[str, Any]] = None):
        self.max_file_size_mb = config.get("max_file_size_mb", 500) if config else 500
        self.retry_attempts = config.get("retry_attempts", 3) if config else 3
        self.retry_exceptions = config.get("retry_exceptions", (DownloadError,)) if config else (DownloadError,)
        self.telemetry = MediaTelemetry()

    async def download_media(
        self, url: str, output_dir: Path, progress_callback: Optional[ProgressCallback] = None
    ) -> MediaFileInfo:
        """Download media from URL to local directory."""
        telemetry = self.telemetry.create_telemetry("download")

        try:
            output_dir.mkdir(parents=True, exist_ok=True)

            # Configure yt-dlp options
            ydl_opts = {
                "outtmpl": str(output_dir / "%(title)s.%(ext)s"),
                "format": "bestaudio[ext=m4a]/bestaudio[ext=mp3]/bestaudio[ext=wav]/bestaudio[ext=webm]/best",
                "writesubtitles": False,
                "writeautomaticsub": False,
                "ignoreerrors": False,
                "no_warnings": False,
                "quiet": False,
                "verbose": False,
                "progress_hooks": [lambda d: self._progress_hook(d, progress_callback)],
            }

            # Download with retry logic
            async for attempt in AsyncRetrying(
                stop=stop_after_attempt(self.retry_attempts),
                wait=wait_exponential(multiplier=1, min=4, max=10),
                retry=retry_if_exception_type(self.retry_exceptions),
            ):
                with attempt:
                    logger.info(f"Downloading media from {url} (attempt {attempt.retry_state.attempt_number})")

                    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                        # Get info first to validate
                        info = ydl.extract_info(url, download=False)

                        # Check file size if available
                        if "filesize" in info and info["filesize"]:
                            file_size_mb = info["filesize"] / (1024 * 1024)
                            if file_size_mb > self.max_file_size_mb:
                                raise ValidationError(
                                    f"File size {file_size_mb:.1f}MB exceeds limit of {self.max_file_size_mb}MB"
                                )

                        # Download the file
                        ydl.download([url])

                        # Find the downloaded file
                        downloaded_files = list(output_dir.glob("*"))
                        if not downloaded_files:
                            raise DownloadError("No files were downloaded")

                        # Get the most recently modified file
                        downloaded_file = max(downloaded_files, key=lambda f: f.stat().st_mtime)

                        # Calculate file hash
                        file_hash = await self._calculate_file_hash(downloaded_file)

                        media_info = MediaFileInfo(
                            filename=downloaded_file.name,
                            file_size=downloaded_file.stat().st_size,
                            duration=None,  # Will be set by preprocessing service
                            mime_type=None,  # Will be set by preprocessing service
                            source_path=url,
                            local_path=str(downloaded_file),
                            file_hash=file_hash,
                            metadata={},  # Will be populated by preprocessing service
                        )

                        # Update telemetry
                        self.telemetry.update_telemetry_success(telemetry, media_info.file_size)

                        return media_info

        except Exception as e:
            # Update telemetry with error information
            self.telemetry.update_telemetry_error(telemetry, e)

            logger.error(f"Failed to download media from {url}: {str(e)}")
            if isinstance(e, (DownloadError, ValidationError)):
                raise
            else:
                raise DownloadError(f"Download failed: {str(e)}") from e

    def _progress_hook(self, d: Dict[str, Any], progress_callback: Optional[ProgressCallback] = None) -> None:
        """Progress hook for yt-dlp downloads."""
        if d["status"] == "downloading":
            downloaded = d.get("downloaded_bytes", 0)
            total = d.get("total_bytes")
            speed = d.get("speed")
            eta = d.get("eta")

            progress = ProgressTracker.create_download_progress(
                downloaded_bytes=downloaded,
                total_bytes=total,
                speed=speed,
                eta=eta,
                status="downloading",
            )

            ProgressTracker.log_download_progress(progress)
            ProgressTracker.notify_progress(progress, progress_callback)

        elif d["status"] == "finished":
            logger.info("Download completed")
            progress = ProgressTracker.create_download_progress(
                downloaded_bytes=0,
                total_bytes=0,
                speed=0,
                eta=0,
                status="finished",
            )
            ProgressTracker.notify_progress(progress, progress_callback)

    async def _calculate_file_hash(self, file_path: Path) -> str:
        """Calculate SHA-256 hash of file."""
        hash_sha256 = hashlib.sha256()

        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_sha256.update(chunk)

        return hash_sha256.hexdigest()

    def get_telemetry_data(self):
        """Get telemetry data for monitoring."""
        return self.telemetry.get_telemetry_data()

    def clear_telemetry_data(self) -> None:
        """Clear telemetry data."""
        self.telemetry.clear_telemetry_data()
