"""Local transcription service using faster-whisper.

This module provides a local transcription service that uses faster-whisper
for offline transcription without requiring OpenAI API keys.
"""

import asyncio
import logging
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional
from uuid import UUID

from faster_whisper import WhisperModel

from ..base.services import BaseService
from ..config import config
from ..database.models import TranscriptionJob, TranscriptionResult, MediaFile
from ..repositories.transcription_repository import TranscriptionRepositoryProtocol, create_transcription_repository
from .transcription_service import TranscriptionConfig, TranscriptionResult as ServiceTranscriptionResult, TranscriptionError
from .ffmpeg_optimizer import create_ffmpeg_optimizer, FFmpegOptimizer

logger = logging.getLogger(__name__)


@dataclass
class LocalTranscriptionConfig:
    """Configuration for local transcription processing."""
    model: str = "distil-large-v3"  # Local Whisper model
    language: Optional[str] = None  # Auto-detect if None
    temperature: float = 0.0  # Deterministic output
    chunk_size_seconds: int = 600  # 10 minutes for chunking
    max_retries: int = 3
    retry_delay: float = 1.0
    # M3 optimization settings
    device: str = "cpu"  # Use 'cpu' for faster-whisper (MPS not supported)
    compute_type: str = "int8_float32"  # Optimized for M3
    beam_size: int = 1  # Reduce memory usage
    best_of: int = 1  # Reduce memory usage
    # M3 preprocessing optimization
    enable_m3_preprocessing: bool = True  # Enable M3-optimized preprocessing
    use_hardware_acceleration: bool = True  # Use VideoToolbox when available
    m3_threading: bool = True  # Optimize threading for M3 cores


class LocalTranscriptionService(BaseService):
    """Local transcription service using faster-whisper."""

    def __init__(
        self,
        repository: TranscriptionRepositoryProtocol,
        config: Optional[Dict[str, Any]] = None
    ):
        super().__init__("local_transcription", config)
        self.repository = repository
        self.model: Optional[WhisperModel] = None
        self.default_config = LocalTranscriptionConfig()
        self.error_log: List[Dict[str, Any]] = []

    async def _initialize_impl(self) -> None:
        """Initialize the local transcription service with M3 optimizations."""
        try:
            # Load the Whisper model
            model_config = self.default_config
            
            # M3 hardware detection and optimization
            # Note: faster-whisper doesn't support MPS, so we use CPU for the model
            # but still enable M3 preprocessing optimizations
            device_to_use = "cpu"  # faster-whisper only supports CPU
            
            # Log M3 status for preprocessing
            if model_config.enable_m3_preprocessing:
                logger.info("M3 preprocessing optimizations enabled (CPU model + M3 preprocessing)")
            else:
                logger.info("Standard transcription mode (no M3 preprocessing)")
            
            # Initialize FFmpeg optimizer for M3 preprocessing
            if model_config.enable_m3_preprocessing:
                try:
                    ffmpeg_optimizer = create_ffmpeg_optimizer()
                    if ffmpeg_optimizer.validate_installation():
                        logger.info("M3 FFmpeg optimization enabled with VideoToolbox support")
                    else:
                        logger.warning("FFmpeg validation failed, M3 preprocessing disabled")
                        model_config.enable_m3_preprocessing = False
                except Exception as e:
                    logger.warning(f"Failed to initialize FFmpeg optimizer: {e}, M3 preprocessing disabled")
                    model_config.enable_m3_preprocessing = False
            
            self.model = WhisperModel(
                model_config.model,
                device=device_to_use,
                compute_type=model_config.compute_type,
                download_root=None,  # Use default cache directory
                local_files_only=False
            )
            logger.info(f"Local transcription service initialized with {model_config.model} on {device_to_use}")

        except Exception as e:
            logger.error(f"Failed to initialize local transcription service: {e}")
            raise TranscriptionError(f"Failed to initialize local transcription service: {e}")

    async def _shutdown_impl(self) -> None:
        """Shutdown the local transcription service."""
        if self.model:
            # Clean up model resources
            del self.model
            self.model = None
        logger.info("Local transcription service shutdown")

    async def transcribe_file(
        self, 
        media_file: MediaFile, 
        config: Optional[LocalTranscriptionConfig] = None
    ) -> ServiceTranscriptionResult:
        """Transcribe a media file using local Whisper model."""
        if not media_file.local_path:
            raise TranscriptionError(f"Media file {media_file.id} has no local path")
        
        audio_path = Path(media_file.local_path)
        if not audio_path.exists():
            raise TranscriptionError(f"Audio file not found: {audio_path}")
        
        # Create transcription job
        job = await self.create_transcription_job(media_file, config)
        
        try:
            # Update job status to processing
            await self.repository.update_job_status(job.id, "processing")
            
            # Transcribe the audio
            result = await self.transcribe_audio(audio_path, config)
            
            # Save result to database
            db_result = await self.repository.create_result(
                job_id=job.id,
                media_file_id=media_file.id,
                content=result.raw_content,
                segments=result.segments,
                confidence_scores=result.confidence_scores,
                accuracy=result.accuracy_estimate,
                word_count=result.word_count,
                processing_time=result.processing_time_ms / 1000.0,  # Convert to seconds
                model_used=result.model_used,
                model_config=config.__dict__ if config else None,
                pipeline_version="v1"
            )
            
            # Update job status to completed
            await self.repository.update_job_progress(
                job.id,
                processing_time=result.processing_time_ms / 1000.0,
                completed_at=datetime.now().isoformat()
            )
            await self.repository.update_job_status(job.id, "completed")
            
            logger.info(f"Transcription completed and saved to database: {db_result.id}")
            return result
            
        except Exception as e:
            # Update job status to failed
            await self.repository.update_job_status(job.id, "failed")
            logger.error(f"Transcription failed for {media_file.id}: {e}")
            raise

    async def transcribe_audio(
        self, 
        audio_path: Path, 
        config: Optional[LocalTranscriptionConfig] = None
    ) -> ServiceTranscriptionResult:
        """Transcribe audio using local Whisper model with M3 optimizations."""
        if not self.model:
            raise TranscriptionError("Transcription model not initialized")
        
        config = config or self.default_config
        start_time = time.time()
        
        try:
            # M3-optimized audio preprocessing
            preprocessed_audio_path = await self._preprocess_audio_m3_optimized(audio_path, config)
            
            # Run transcription in thread pool to avoid blocking
            loop = asyncio.get_event_loop()
            segments, info = await loop.run_in_executor(
                None,
                self._transcribe_sync,
                preprocessed_audio_path,
                config
            )
            
            # Process results
            processing_time = time.time() - start_time
            
            # Convert segments to expected format
            processed_segments = []
            confidence_scores = []
            full_text = ""
            
            for segment in segments:
                processed_segments.append({
                    "id": segment.id,
                    "start": segment.start,
                    "end": segment.end,
                    "text": segment.text,
                    "confidence": getattr(segment, 'avg_logprob', 0.0)
                })
                confidence_scores.append(getattr(segment, 'avg_logprob', 0.0))
                full_text += segment.text + " "
            
            # Calculate word count
            word_count = len(full_text.split())
            
            # Estimate accuracy based on confidence scores
            avg_confidence = sum(confidence_scores) / len(confidence_scores) if confidence_scores else 0.0
            accuracy_estimate = max(0.0, min(1.0, (avg_confidence + 1.0) / 2.0))  # Convert from logprob to 0-1 scale
            
            # Generate quality warnings
            quality_warnings = self._generate_quality_warnings(accuracy_estimate, processed_segments)
            
            # Create raw content structure
            raw_content = {
                "text": full_text.strip(),
                "segments": processed_segments,
                "language": info.language,
                "language_probability": info.language_probability,
                "duration": info.duration,
                "m3_optimizations": {
                    "device_used": config.device,
                    "m3_preprocessing_enabled": config.enable_m3_preprocessing,
                    "hardware_acceleration_used": config.use_hardware_acceleration,
                    "m3_threading_enabled": config.m3_threading
                }
            }
            
            # Add M3 performance metrics
            m3_metrics = {
                "device_used": config.device,
                "m3_preprocessing_enabled": config.enable_m3_preprocessing,
                "hardware_acceleration_used": config.use_hardware_acceleration,
                "m3_threading_enabled": config.m3_threading,
                "compute_type": config.compute_type
            }
            
            return ServiceTranscriptionResult(
                raw_content=raw_content,
                text_content=full_text.strip(),
                segments=processed_segments,
                model_used=config.model,
                processing_time_ms=int(processing_time * 1000),
                word_count=word_count,
                accuracy_estimate=accuracy_estimate,
                quality_warnings=quality_warnings,
                language=info.language,
                confidence_scores=confidence_scores,
                metadata={"m3_optimizations": m3_metrics}
            )
            
        except Exception as e:
            logger.error(f"Transcription failed for {audio_path}: {e}")
            raise TranscriptionError(f"Transcription failed: {e}")

    def _transcribe_sync(self, audio_path: Path, config: LocalTranscriptionConfig):
        """Synchronous transcription using faster-whisper."""
        return self.model.transcribe(
            str(audio_path),
            language=config.language,
            temperature=config.temperature,
            beam_size=config.beam_size,
            best_of=config.best_of
        )

    async def create_transcription_job(
        self, 
        media_file: MediaFile, 
        config: Optional[LocalTranscriptionConfig] = None
    ) -> TranscriptionJob:
        """Create a transcription job."""
        config = config or self.default_config
        
        job = await self.repository.create_job(
            media_file_id=media_file.id,
            model_config={
                "model": config.model,
                "language": config.language,
                "temperature": config.temperature,
                "chunk_size_seconds": config.chunk_size_seconds,
                "device": config.device,
                "compute_type": config.compute_type
            },
            processing_options={
                "max_retries": config.max_retries,
                "retry_delay": config.retry_delay
            }
        )
        
        logger.info(f"Created local transcription job {job.id} for media file {media_file.id}")
        return job

    async def _preprocess_audio_m3_optimized(
        self, 
        audio_path: Path, 
        config: LocalTranscriptionConfig
    ) -> Path:
        """Preprocess audio using M3-optimized FFmpeg settings."""
        if not config.enable_m3_preprocessing:
            logger.info("M3 preprocessing disabled, using standard processing")
            return audio_path
        
        try:
            # Initialize FFmpeg optimizer for M3
            ffmpeg_optimizer = create_ffmpeg_optimizer()
            
            # Check if hardware acceleration is available
            if not ffmpeg_optimizer.validate_installation():
                logger.warning("FFmpeg validation failed, falling back to standard processing")
                return audio_path
            
            # Get M3-optimized parameters for transcription
            input_format = audio_path.suffix.lower().lstrip('.')
            optimized_params = ffmpeg_optimizer.get_transcription_optimized_params(
                input_format=input_format,
                output_format="wav"
            )
            
            # Create output path for preprocessed audio
            output_path = audio_path.parent / f"{audio_path.stem}_m3_optimized.wav"
            
            # Build FFmpeg command with M3 optimizations
            cmd = [
                "ffmpeg",
                *optimized_params,
                "-i", str(audio_path),
                "-y",  # Overwrite output
                str(output_path)
            ]
            
            logger.info(f"M3-optimized preprocessing: {audio_path.name} -> {output_path.name}")
            logger.debug(f"FFmpeg command: {' '.join(cmd)}")
            
            # Execute FFmpeg with M3 optimizations
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await process.communicate()
            
            if process.returncode != 0:
                logger.warning(f"M3 preprocessing failed: {stderr.decode()}, falling back to standard")
                return audio_path
            
            logger.info(f"M3-optimized preprocessing completed: {output_path}")
            return output_path
            
        except Exception as e:
            logger.warning(f"Error in M3 preprocessing: {e}, falling back to standard processing")
            return audio_path

    def _generate_quality_warnings(
        self, 
        accuracy: float, 
        segments: List[Dict[str, Any]]
    ) -> List[str]:
        """Generate quality warnings based on transcription results."""
        warnings = []
        
        if accuracy < 0.8:
            warnings.append("Low accuracy detected - review transcript")
        
        if not segments:
            warnings.append("No segments generated - check audio file")
        
        # Check for very short segments (potential issues)
        short_segments = [s for s in segments if s["end"] - s["start"] < 0.5]
        if len(short_segments) > len(segments) * 0.1:  # More than 10% are very short
            warnings.append("Many short segments detected - audio may have issues")
        
        return warnings

    async def get_job_status(self, job_id: UUID) -> str:
        """Get transcription job status."""
        job = await self.repository.get_job(job_id)
        if not job:
            raise TranscriptionError(f"Job {job_id} not found")
        
        return job.status

    async def cancel_job(self, job_id: UUID) -> bool:
        """Cancel a transcription job."""
        # For local transcription, we can't easily cancel running jobs
        # Just mark as cancelled in database
        try:
            await self.repository.update_job_status(job_id, "cancelled")
            logger.info(f"Cancelled transcription job {job_id}")
            return True
        except Exception as e:
            logger.error(f"Failed to cancel job {job_id}: {e}")
            return False
    
    def get_m3_optimization_status(self) -> Dict[str, Any]:
        """Get current M3 optimization status and capabilities."""
        config = self.default_config
        
        status = {
            "device": config.device,
            "m3_preprocessing_enabled": config.enable_m3_preprocessing,
            "hardware_acceleration_enabled": config.use_hardware_acceleration,
            "m3_threading_enabled": config.m3_threading,
            "compute_type": config.compute_type,
            "model": config.model
        }
        
        # Note: faster-whisper doesn't support MPS, so we use CPU for the model
        # but still enable M3 preprocessing optimizations
        status["mps_available"] = False  # faster-whisper limitation
        status["mps_device_count"] = 0
        status["m3_preprocessing_available"] = True  # M3 preprocessing is available
        
        # Check FFmpeg optimization
        try:
            ffmpeg_optimizer = create_ffmpeg_optimizer()
            status["ffmpeg_optimization_available"] = ffmpeg_optimizer.validate_installation()
            status["videotoolbox_support"] = "videotoolbox" in ffmpeg_optimizer.get_supported_formats()
        except Exception:
            status["ffmpeg_optimization_available"] = False
            status["videotoolbox_support"] = False
        
        return status


def create_local_transcription_service(
    repository: Optional[TranscriptionRepositoryProtocol] = None,
    config: Optional[Dict[str, Any]] = None
) -> LocalTranscriptionService:
    """Create a local transcription service instance."""
    if repository is None:
        repository = create_transcription_repository()
    
    return LocalTranscriptionService(repository, config)
