"""Main audio analysis orchestrator."""
from typing import Dict, List, Optional, Callable
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from pydantic import BaseModel
from datetime import datetime

from .audio_processor import extract_all_features
from .essentia_classifier import EssentiaClassifier
from .file_scanner import get_file_metadata, scan_folder, validate_audio_files
from ..utils.logging import get_logger
from ..utils.config import settings

logger = get_logger(__name__)


class AudioAnalysis(BaseModel):
    """Complete audio analysis result."""

    # File info
    filepath: str
    filename: str
    file_size_bytes: int
    format: str
    duration_seconds: Optional[float] = None
    analyzed_at: datetime

    # Audio features
    tempo_bpm: Optional[float] = None
    key: Optional[str] = None
    time_signature: Optional[str] = None
    energy: Optional[float] = None
    danceability: Optional[float] = None
    valence: Optional[float] = None
    loudness_lufs: Optional[float] = None
    spectral_centroid: Optional[float] = None
    zero_crossing_rate: Optional[float] = None

    # Classification
    genre_primary: Optional[str] = None
    genre_secondary: Optional[List[str]] = None
    genre_confidence: Optional[float] = None
    mood_primary: Optional[str] = None
    mood_secondary: Optional[List[str]] = None
    mood_arousal: Optional[float] = None
    mood_valence: Optional[float] = None
    instruments: Optional[List[str]] = None

    # Vocals (future)
    has_vocals: Optional[bool] = None
    vocal_gender: Optional[str] = None

    # Metadata
    metadata: Optional[Dict] = None

    class Config:
        json_encoders = {
            datetime: lambda v: v.isoformat()
        }


class AudioAnalyzer:
    """Main audio analyzer orchestrating all processing steps."""

    def __init__(self):
        """Initialize analyzer with classifier."""
        self.classifier = EssentiaClassifier()
        self.num_workers = settings.ANALYSIS_NUM_WORKERS

    def analyze_file(self, filepath: str) -> AudioAnalysis:
        """Analyze a single audio file.

        Args:
            filepath: Path to audio file

        Returns:
            AudioAnalysis object with all extracted data

        Raises:
            Exception if analysis fails
        """
        logger.info(f"Analyzing file: {filepath}")

        try:
            # 1. Get file metadata
            file_metadata = get_file_metadata(filepath)

            # 2. Extract audio features (librosa)
            audio_features = extract_all_features(filepath)

            # 3. Classify with Essentia
            genre = self.classifier.predict_genre(filepath)
            mood = self.classifier.predict_mood(filepath)
            instruments_list = self.classifier.predict_instruments(filepath)

            # Extract instrument names only
            instrument_names = [inst["name"] for inst in instruments_list]

            # 4. Combine all data
            analysis = AudioAnalysis(
                # File info
                filepath=file_metadata["filepath"],
                filename=file_metadata["filename"],
                file_size_bytes=file_metadata["file_size_bytes"],
                format=file_metadata["format"],
                duration_seconds=audio_features.get("duration_seconds"),
                analyzed_at=datetime.utcnow(),

                # Audio features
                tempo_bpm=audio_features.get("tempo_bpm"),
                key=audio_features.get("key"),
                time_signature=audio_features.get("time_signature"),
                energy=audio_features.get("energy"),
                danceability=audio_features.get("danceability"),
                valence=audio_features.get("valence"),
                loudness_lufs=audio_features.get("loudness_lufs"),
                spectral_centroid=audio_features.get("spectral_centroid"),
                zero_crossing_rate=audio_features.get("zero_crossing_rate"),

                # Classification
                genre_primary=genre.get("primary"),
                genre_secondary=genre.get("secondary"),
                genre_confidence=genre.get("confidence"),
                mood_primary=mood.get("primary"),
                mood_secondary=mood.get("secondary"),
                mood_arousal=mood.get("arousal"),
                mood_valence=mood.get("valence"),
                instruments=instrument_names,

                # Metadata
                metadata=file_metadata.get("id3_tags"),
            )

            logger.info(f"Successfully analyzed: {filepath}")
            return analysis

        except Exception as e:
            logger.error(f"Failed to analyze {filepath}: {e}")
            raise

    def analyze_folder(
        self,
        path: str,
        recursive: bool = True,
        progress_callback: Optional[Callable[[int, int, str], None]] = None,
    ) -> List[AudioAnalysis]:
        """Analyze all audio files in a folder.

        Args:
            path: Directory path
            recursive: If True, scan recursively
            progress_callback: Optional callback(current, total, filename)

        Returns:
            List of AudioAnalysis objects
        """
        logger.info(f"Analyzing folder: {path}")

        # 1. Scan for files
        audio_files = scan_folder(path, recursive=recursive)
        total_files = len(audio_files)

        if total_files == 0:
            logger.warning(f"No audio files found in {path}")
            return []

        logger.info(f"Found {total_files} files to analyze")

        # 2. Analyze files in parallel
        results = []
        errors = []

        with ThreadPoolExecutor(max_workers=self.num_workers) as executor:
            # Submit all tasks
            future_to_file = {
                executor.submit(self._analyze_file_safe, filepath): filepath
                for filepath in audio_files
            }

            # Process completed tasks
            for i, future in enumerate(as_completed(future_to_file), 1):
                filepath = future_to_file[future]
                filename = Path(filepath).name

                # Call progress callback
                if progress_callback:
                    progress_callback(i, total_files, filename)

                try:
                    analysis = future.result()
                    if analysis:
                        results.append(analysis)
                        logger.info(f"[{i}/{total_files}] ✓ {filename}")
                    else:
                        errors.append(filepath)
                        logger.warning(f"[{i}/{total_files}] ✗ {filename}")

                except Exception as e:
                    errors.append(filepath)
                    logger.error(f"[{i}/{total_files}] ✗ {filename}: {e}")

        logger.info(f"Analysis complete: {len(results)} succeeded, {len(errors)} failed")

        if errors:
            logger.warning(f"Failed files: {errors[:10]}")  # Log first 10

        return results

    def _analyze_file_safe(self, filepath: str) -> Optional[AudioAnalysis]:
        """Safely analyze a file (catches exceptions).

        Args:
            filepath: Path to audio file

        Returns:
            AudioAnalysis or None if failed
        """
        try:
            return self.analyze_file(filepath)
        except Exception as e:
            logger.error(f"Analysis failed for {filepath}: {e}")
            return None
