"""Transcription service for Trax platform.

This module provides a protocol-based TranscriptionService for transcribing
audio files using OpenAI's Whisper API with high accuracy and efficient processing.
"""

import asyncio
import json
import logging
import time
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
from uuid import UUID

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

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

logger = logging.getLogger(__name__)


class TranscriptionStatus(Enum):
    """Transcription processing status."""

    PENDING = "pending"
    PROCESSING = "processing"
    COMPLETED = "completed"
    FAILED = "failed"


class TranscriptionError(Exception):
    """Base exception for transcription errors."""
    pass


class WhisperAPIError(TranscriptionError):
    """Exception raised when Whisper API fails."""
    pass


class AudioProcessingError(TranscriptionError):
    """Exception raised when audio processing fails."""
    pass


class TranscriptionRecoveryError(TranscriptionError):
    """Exception raised when recovery mechanisms fail."""
    pass


@dataclass
class ErrorInfo:
    """Information about transcription errors."""
    
    error_type: str
    error_message: str
    timestamp: datetime
    chunk_index: Optional[int] = None
    retry_count: int = 0
    recovery_attempted: bool = False
    recovery_successful: bool = False


@dataclass
class RecoveryStrategy:
    """Recovery strategy configuration."""
    
    max_retries: int = 3
    retry_delay: float = 1.0
    exponential_backoff: bool = True
    fallback_model: Optional[str] = None
    enable_partial_results: bool = True
    save_error_logs: bool = True


@dataclass
class TranscriptionProgress:
    """Transcription progress information."""

    stage: str
    current_step: int
    total_steps: int
    status: str
    message: str
    start_time: float
    elapsed_time: float = 0.0


@dataclass
class TranscriptionConfig:
    """Configuration for transcription processing."""

    model: str = "whisper-1"  # OpenAI Whisper model
    response_format: str = "verbose_json"  # Get detailed JSON response
    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
    optimize_for_m3: bool = True  # Enable M3-specific optimizations
    parallel_chunks: int = 2  # Number of chunks to process in parallel (M3 can handle 2-3)
    memory_limit_mb: int = 2048  # Memory limit for processing (2GB for M3)


@dataclass
class TranscriptionResult:
    """Transcription result data."""

    raw_content: Dict[str, Any]
    text_content: str
    segments: List[Dict[str, Any]]
    model_used: str
    processing_time_ms: int
    word_count: int
    accuracy_estimate: float
    quality_warnings: List[str]
    language: Optional[str] = None
    confidence_scores: Optional[List[float]] = None
    metadata: Optional[Dict[str, Any]] = None


@runtime_checkable
class TranscriptionServiceProtocol(Protocol):
    """Protocol for transcription service."""

    async def transcribe_file(
        self, 
        media_file: MediaFile, 
        config: Optional[TranscriptionConfig] = None
    ) -> TranscriptionResult:
        """Transcribe a media file."""
        ...

    async def transcribe_audio(
        self, 
        audio_path: Path, 
        config: Optional[TranscriptionConfig] = None
    ) -> TranscriptionResult:
        """Transcribe audio from file path."""
        ...

    async def create_transcription_job(
        self, 
        media_file: MediaFile, 
        config: Optional[TranscriptionConfig] = None
    ) -> TranscriptionJob:
        """Create a transcription job."""
        ...

    async def get_job_status(self, job_id: UUID) -> TranscriptionStatus:
        """Get transcription job status."""
        ...

    async def cancel_job(self, job_id: UUID) -> bool:
        """Cancel a transcription job."""
        ...


class TranscriptionService(BaseService):
    """Transcription service implementation using OpenAI Whisper API."""

    def __init__(
        self,
        repository: TranscriptionRepositoryProtocol,
        config: Optional[Dict[str, Any]] = None
    ):
        super().__init__("transcription", config)
        self.repository = repository
        self.client: Optional[openai.AsyncOpenAI] = None
        self.default_config = TranscriptionConfig()
        self.error_log: List[ErrorInfo] = []
        self.recovery_strategy = RecoveryStrategy()

    async def _initialize_impl(self) -> None:
        """Initialize the transcription service."""
        if not config.OPENAI_API_KEY:
            raise TranscriptionError("OpenAI API key not configured")
        
        self.client = openai.AsyncOpenAI(api_key=config.OPENAI_API_KEY)
        logger.info("Transcription service initialized with OpenAI Whisper API")

    async def _shutdown_impl(self) -> None:
        """Shutdown the transcription service."""
        if self.client:
            await self.client.close()
        logger.info("Transcription service shutdown")

    async def transcribe_file(
        self, 
        media_file: MediaFile, 
        config: Optional[TranscriptionConfig] = None
    ) -> TranscriptionResult:
        """Transcribe a media file."""
        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", str(e))
            logger.error(f"Transcription failed for media file {media_file.id}: {e}")
            raise

    async def transcribe_audio(
        self, 
        audio_path: Path, 
        config: Optional[TranscriptionConfig] = None
    ) -> TranscriptionResult:
        """Transcribe audio from file path."""
        if not self.client:
            raise TranscriptionError("Transcription service not initialized")
        
        config = config or self.default_config
        start_time = time.time()
        
        try:
            # Convert audio to 16kHz mono WAV if needed
            processed_audio_path = await self._preprocess_audio(audio_path)
            
            # Get audio duration
            duration = await self._get_audio_duration(processed_audio_path)
            
            if duration > config.chunk_size_seconds:
                # Process in chunks for long files
                result = await self._transcribe_chunked(processed_audio_path, config)
            else:
                # Process single file
                result = await self._transcribe_single(processed_audio_path, config)
            
            processing_time = time.time() - start_time
            
            # Calculate metrics
            word_count = self._count_words(result.text_content)
            accuracy_estimate = self._estimate_accuracy(result.raw_content)
            quality_warnings = self._generate_quality_warnings(accuracy_estimate, result.raw_content)
            
            return TranscriptionResult(
                raw_content=result.raw_content,
                text_content=result.text_content,
                segments=result.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=result.language,
                confidence_scores=result.confidence_scores
            )
            
        except Exception as e:
            logger.error(f"Transcription failed for {audio_path}: {e}")
            raise TranscriptionError(f"Transcription failed: {e}")

    async def create_transcription_job(
        self, 
        media_file: MediaFile, 
        config: Optional[TranscriptionConfig] = 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,
                "response_format": config.response_format,
                "language": config.language,
                "temperature": config.temperature,
                "chunk_size_seconds": config.chunk_size_seconds
            },
            processing_options={
                "max_retries": config.max_retries,
                "retry_delay": config.retry_delay
            }
        )
        
        logger.info(f"Created transcription job {job.id} for media file {media_file.id}")
        return job

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

    async def cancel_job(self, job_id: UUID) -> bool:
        """Cancel a transcription job."""
        job = await self.repository.get_job(job_id)
        if not job:
            return False
        
        if job.status in ["pending", "processing"]:
            await self.repository.update_job_status(job_id, "cancelled")
            logger.info(f"Cancelled transcription job {job_id}")
            return True
        
        return False

    async def transcribe_batch(
        self,
        audio_files: List[Path],
        config: Optional[TranscriptionConfig] = None,
        progress_callback: Optional[Callable[[str, int, int, str], None]] = None
    ) -> List[TranscriptionResult]:
        """Transcribe multiple audio files with progress tracking."""
        config = config or self.default_config
        results = []
        total_files = len(audio_files)
        
        logger.info(f"Starting batch transcription of {total_files} files")
        
        for i, audio_path in enumerate(audio_files):
            try:
                if progress_callback:
                    progress_callback("transcribing", i + 1, total_files, f"Processing {audio_path.name}")
                
                logger.info(f"Transcribing file {i+1}/{total_files}: {audio_path.name}")
                
                result = await self.transcribe_audio(audio_path, config)
                results.append(result)
                
                logger.info(f"Successfully transcribed {audio_path.name}")
                
            except Exception as e:
                logger.error(f"Failed to transcribe {audio_path.name}: {e}")
                # Create a failed result for tracking
                failed_result = TranscriptionResult(
                    raw_content={"error": str(e), "file": str(audio_path)},
                    text_content="",
                    segments=[],
                    model_used=config.model,
                    processing_time_ms=0,
                    word_count=0,
                    accuracy_estimate=0.0,
                    quality_warnings=[f"Transcription failed: {e}"],
                    language=None,
                    confidence_scores=[]
                )
                results.append(failed_result)
        
        if progress_callback:
            progress_callback("completed", total_files, total_files, "Batch transcription completed")
        
        logger.info(f"Batch transcription completed: {len([r for r in results if r.text_content])}/{total_files} successful")
        return results

    async def _preprocess_audio(self, audio_path: Path) -> Path:
        """Preprocess audio to 16kHz mono WAV format."""
        # Check if the audio is already in the correct format
        if audio_path.suffix.lower() == '.wav':
            # Check if it's already 16kHz mono
            try:
                import subprocess
                cmd = [
                    "ffprobe",
                    "-v", "quiet",
                    "-select_streams", "a:0",
                    "-show_entries", "stream=sample_rate:stream=channels",
                    "-of", "csv=p=0",
                    str(audio_path)
                ]
                
                result = subprocess.run(cmd, capture_output=True, text=True, check=True)
                sample_rate, channels = result.stdout.strip().split(',')
                
                if int(sample_rate) == 16000 and int(channels) == 1:
                    logger.info(f"Audio already in 16kHz mono format: {audio_path}")
                    return audio_path
            except Exception as e:
                logger.warning(f"Could not verify audio format, will convert: {e}")
        
        # Convert to 16kHz mono WAV
        output_path = audio_path.parent / f"{audio_path.stem}_16k_mono.wav"
        
        cmd = [
            "ffmpeg",
            "-i", str(audio_path),
            "-ar", "16000",  # 16kHz sample rate
            "-ac", "1",      # mono
            "-c:a", "pcm_s16le",  # 16-bit PCM
            "-y",            # overwrite output
            str(output_path)
        ]
        
        logger.info(f"Converting audio to 16kHz mono: {audio_path.name} -> {output_path.name}")
        
        try:
            import asyncio
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await process.communicate()
            
            if process.returncode != 0:
                raise AudioProcessingError(f"FFmpeg error: {stderr.decode()}")
            
            logger.info(f"Audio conversion completed: {output_path}")
            return output_path
            
        except Exception as e:
            logger.error(f"Error converting audio: {e}")
            raise AudioProcessingError(f"Audio conversion failed: {e}")

    async def _get_audio_duration(self, audio_path: Path) -> float:
        """Get audio duration using FFmpeg."""
        try:
            import asyncio
            cmd = [
                "ffprobe",
                "-v", "quiet",
                "-show_entries", "format=duration",
                "-of", "csv=p=0",
                str(audio_path)
            ]
            
            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.error(f"FFprobe error: {stderr.decode()}")
                return 0.0
                
            duration_str = stdout.decode().strip()
            if not duration_str:
                return 0.0
                
            duration = float(duration_str)
            logger.info(f"Audio duration: {duration:.2f} seconds")
            return duration
            
        except Exception as e:
            logger.error(f"Error getting audio duration: {e}")
            return 0.0

    async def _transcribe_single(
        self, 
        audio_path: Path, 
        config: TranscriptionConfig
    ) -> TranscriptionResult:
        """Transcribe a single audio file."""
        async for attempt in AsyncRetrying(
            stop=stop_after_attempt(config.max_retries),
            wait=wait_exponential(multiplier=config.retry_delay),
            retry=retry_if_exception_type((openai.APIError, openai.APITimeoutError))
        ):
            with attempt:
                with open(audio_path, "rb") as audio_file:
                    response = await self.client.audio.transcriptions.create(
                        model=config.model,
                        file=audio_file,
                        response_format=config.response_format,
                        language=config.language,
                        temperature=config.temperature
                    )
                    
                    # Parse the response
                    if config.response_format == "verbose_json":
                        raw_content = response.model_dump()
                        text_content = raw_content.get("text", "")
                        segments = raw_content.get("segments", [])
                        language = raw_content.get("language")
                        confidence_scores = [seg.get("confidence", 0.0) for seg in segments]
                    else:
                        raw_content = {"text": response}
                        text_content = response
                        segments = []
                        language = None
                        confidence_scores = []
                    
                    return TranscriptionResult(
                        raw_content=raw_content,
                        text_content=text_content,
                        segments=segments,
                        model_used=config.model,
                        processing_time_ms=0,  # Will be calculated by caller
                        word_count=0,  # Will be calculated by caller
                        accuracy_estimate=0.0,  # Will be calculated by caller
                        quality_warnings=[],  # Will be calculated by caller
                        language=language,
                        confidence_scores=confidence_scores
                    )

    async def _transcribe_chunked(
        self, 
        audio_path: Path, 
        config: TranscriptionConfig
    ) -> TranscriptionResult:
        """Transcribe audio in chunks for long files with M3 optimization."""
        logger.info(f"Processing long audio file in chunks: {audio_path.name}")
        
        # Create temporary directory for chunks
        import tempfile
        import os
        from datetime import datetime
        
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            
            # Split audio into chunks
            chunks = await self._split_audio_into_chunks(
                audio_path, 
                temp_path, 
                config.chunk_size_seconds
            )
            
            logger.info(f"Split audio into {len(chunks)} chunks")
            
            # Transcribe chunks with M3 optimization
            if config.optimize_for_m3 and len(chunks) > 1:
                chunk_results = await self._transcribe_chunks_parallel(chunks, config)
            else:
                chunk_results = await self._transcribe_chunks_sequential(chunks, config)
            
            if not chunk_results:
                raise TranscriptionError("All chunks failed to transcribe")
            
            # Merge chunk results
            merged_result = await self._merge_chunk_results(chunk_results, config)
            
            logger.info(f"Successfully transcribed {len(chunk_results)}/{len(chunks)} chunks")
            return merged_result

    async def _transcribe_chunks_parallel(
        self, 
        chunks: List[Path], 
        config: TranscriptionConfig
    ) -> List[TranscriptionResult]:
        """Transcribe chunks in parallel for M3 optimization."""
        logger.info(f"Processing {len(chunks)} chunks in parallel (M3 optimized)")
        
        chunk_results = []
        failed_chunks = []
        
        # Process chunks in parallel batches
        batch_size = min(config.parallel_chunks, len(chunks))
        
        for i in range(0, len(chunks), batch_size):
            batch = chunks[i:i + batch_size]
            logger.info(f"Processing batch {i//batch_size + 1}: chunks {i+1}-{min(i+batch_size, len(chunks))}")
            
            # Create tasks for parallel processing
            tasks = []
            for j, chunk_path in enumerate(batch):
                task = self._transcribe_single_with_monitoring(chunk_path, config, i + j + 1, len(chunks))
                tasks.append(task)
            
            # Execute batch in parallel
            try:
                batch_results = await asyncio.gather(*tasks, return_exceptions=True)
                
                for j, result in enumerate(batch_results):
                    if isinstance(result, Exception):
                        logger.error(f"Error transcribing chunk {i + j + 1}: {result}")
                        failed_chunks.append({
                            "chunk_index": i + j,
                            "chunk_path": str(batch[j]),
                            "error": str(result),
                            "error_type": type(result).__name__
                        })
                    else:
                        chunk_results.append(result)
                        
            except Exception as e:
                logger.error(f"Error in parallel batch processing: {e}")
                # Fall back to sequential processing for this batch
                for j, chunk_path in enumerate(batch):
                    try:
                        result = await self._transcribe_single(chunk_path, config)
                        chunk_results.append(result)
                    except Exception as chunk_error:
                        logger.error(f"Error transcribing chunk {i + j + 1}: {chunk_error}")
                        failed_chunks.append({
                            "chunk_index": i + j,
                            "chunk_path": str(chunk_path),
                            "error": str(chunk_error),
                            "error_type": type(chunk_error).__name__
                        })
        
        # Log parallel processing results
        if failed_chunks:
            logger.warning(f"Failed to transcribe {len(failed_chunks)} chunks in parallel mode")
        
        return chunk_results

    async def _transcribe_chunks_sequential(
        self, 
        chunks: List[Path], 
        config: TranscriptionConfig
    ) -> List[TranscriptionResult]:
        """Transcribe chunks sequentially (fallback method)."""
        logger.info(f"Processing {len(chunks)} chunks sequentially")
        
        chunk_results = []
        failed_chunks = []
        
        for i, chunk_path in enumerate(chunks):
            logger.info(f"Transcribing chunk {i+1}/{len(chunks)}: {chunk_path.name}")
            
            try:
                chunk_result = await self._transcribe_single_with_monitoring(chunk_path, config, i + 1, len(chunks))
                chunk_results.append(chunk_result)
            except Exception as e:
                logger.error(f"Error transcribing chunk {i+1}: {e}")
                failed_chunks.append({
                    "chunk_index": i,
                    "chunk_path": str(chunk_path),
                    "error": str(e),
                    "error_type": type(e).__name__
                })
                continue
        
        if failed_chunks:
            logger.warning(f"Failed to transcribe {len(failed_chunks)} chunks in sequential mode")
        
        return chunk_results

    async def _transcribe_single_with_monitoring(
        self, 
        audio_path: Path, 
        config: TranscriptionConfig,
        chunk_number: int,
        total_chunks: int
    ) -> TranscriptionResult:
        """Transcribe single chunk with M3 performance monitoring."""
        start_time = time.time()
        
        # Monitor memory usage on M3
        if config.optimize_for_m3:
            await self._check_memory_usage(config.memory_limit_mb)
        
        logger.info(f"Transcribing chunk {chunk_number}/{total_chunks}: {audio_path.name}")
        
        try:
            result = await self._transcribe_single(audio_path, config)
            
            processing_time = time.time() - start_time
            logger.info(f"Chunk {chunk_number}/{total_chunks} completed in {processing_time:.2f}s")
            
            return result
            
        except Exception as e:
            processing_time = time.time() - start_time
            logger.error(f"Chunk {chunk_number}/{total_chunks} failed after {processing_time:.2f}s: {e}")
            raise

    async def _check_memory_usage(self, limit_mb: int) -> None:
        """Check memory usage and warn if approaching limit."""
        try:
            import psutil
            process = psutil.Process()
            memory_mb = process.memory_info().rss / 1024 / 1024
            
            if memory_mb > limit_mb * 0.8:  # Warning at 80% of limit
                logger.warning(f"Memory usage high: {memory_mb:.1f}MB / {limit_mb}MB limit")
            
            if memory_mb > limit_mb:
                logger.error(f"Memory usage exceeded limit: {memory_mb:.1f}MB / {limit_mb}MB")
                raise MemoryError(f"Memory usage exceeded {limit_mb}MB limit")
                
        except ImportError:
            # psutil not available, skip memory monitoring
            pass
        except Exception as e:
            logger.warning(f"Could not check memory usage: {e}")

    def _log_error(self, error: Exception, chunk_index: Optional[int] = None) -> ErrorInfo:
        """Log an error for tracking and recovery."""
        error_info = ErrorInfo(
            error_type=type(error).__name__,
            error_message=str(error),
            timestamp=datetime.now(),
            chunk_index=chunk_index,
            retry_count=0
        )
        
        self.error_log.append(error_info)
        logger.error(f"Error logged: {error_info.error_type} - {error_info.error_message}")
        
        return error_info

    def _attempt_recovery(self, error_info: ErrorInfo, config: TranscriptionConfig) -> bool:
        """Attempt to recover from an error."""
        error_info.recovery_attempted = True
        
        # Check if we should attempt recovery
        if error_info.retry_count >= self.recovery_strategy.max_retries:
            logger.warning(f"Max retries exceeded for error: {error_info.error_type}")
            return False
        
        # Increment retry count
        error_info.retry_count += 1
        
        # Determine recovery strategy based on error type
        if "API" in error_info.error_type or "rate" in error_info.error_message.lower():
            # API-related error - try with different model or wait
            logger.info(f"Attempting API recovery (attempt {error_info.retry_count})")
            return True
            
        elif "memory" in error_info.error_message.lower():
            # Memory error - reduce chunk size or parallel processing
            logger.info(f"Attempting memory recovery (attempt {error_info.retry_count})")
            return True
            
        elif "audio" in error_info.error_type.lower() or "ffmpeg" in error_info.error_message.lower():
            # Audio processing error - try different preprocessing
            logger.info(f"Attempting audio processing recovery (attempt {error_info.retry_count})")
            return True
        
        # Default recovery attempt
        logger.info(f"Attempting general recovery (attempt {error_info.retry_count})")
        return True

    def _get_recovery_config(self, error_info: ErrorInfo, original_config: TranscriptionConfig) -> TranscriptionConfig:
        """Get modified configuration for recovery attempts."""
        recovery_config = TranscriptionConfig(
            model=original_config.model,
            response_format=original_config.response_format,
            language=original_config.language,
            temperature=original_config.temperature,
            chunk_size_seconds=original_config.chunk_size_seconds,
            max_retries=original_config.max_retries,
            retry_delay=original_config.retry_delay,
            optimize_for_m3=original_config.optimize_for_m3,
            parallel_chunks=original_config.parallel_chunks,
            memory_limit_mb=original_config.memory_limit_mb
        )
        
        # Apply recovery modifications based on error type
        if "API" in error_info.error_type:
            # Try fallback model if available
            if self.recovery_strategy.fallback_model:
                recovery_config.model = self.recovery_strategy.fallback_model
            
            # Increase retry delay for API errors
            recovery_config.retry_delay = original_config.retry_delay * (2 ** error_info.retry_count)
            
        elif "memory" in error_info.error_message.lower():
            # Reduce memory usage
            recovery_config.memory_limit_mb = max(1024, original_config.memory_limit_mb // 2)
            recovery_config.parallel_chunks = max(1, original_config.parallel_chunks // 2)
            
        elif "audio" in error_info.error_type.lower():
            # Try different audio processing settings
            recovery_config.chunk_size_seconds = min(300, original_config.chunk_size_seconds)  # Smaller chunks
        
        return recovery_config

    def get_error_summary(self) -> Dict[str, Any]:
        """Get a summary of all errors encountered."""
        if not self.error_log:
            return {"total_errors": 0, "errors": []}
        
        error_counts = {}
        for error in self.error_log:
            error_type = error.error_type
            error_counts[error_type] = error_counts.get(error_type, 0) + 1
        
        return {
            "total_errors": len(self.error_log),
            "error_types": error_counts,
            "recovery_attempts": len([e for e in self.error_log if e.recovery_attempted]),
            "successful_recoveries": len([e for e in self.error_log if e.recovery_successful]),
            "errors": [
                {
                    "type": e.error_type,
                    "message": e.error_message,
                    "timestamp": e.timestamp.isoformat(),
                    "chunk_index": e.chunk_index,
                    "retry_count": e.retry_count,
                    "recovery_attempted": e.recovery_attempted,
                    "recovery_successful": e.recovery_successful
                }
                for e in self.error_log
            ]
        }

    def clear_error_log(self) -> None:
        """Clear the error log."""
        self.error_log.clear()
        logger.info("Error log cleared")

    async def _split_audio_into_chunks(
        self, 
        audio_path: Path, 
        output_dir: Path, 
        chunk_size_seconds: int
    ) -> List[Path]:
        """Split audio file into chunks using FFmpeg."""
        chunks = []
        
        # Get total duration
        total_duration = await self._get_audio_duration(audio_path)
        if total_duration <= 0:
            raise AudioProcessingError("Invalid audio duration")
        
        # Calculate number of chunks needed
        num_chunks = int((total_duration + chunk_size_seconds - 1) / chunk_size_seconds)
        
        for i in range(num_chunks):
            start_time = i * chunk_size_seconds
            end_time = min((i + 1) * chunk_size_seconds, total_duration)
            
            chunk_path = output_dir / f"chunk_{i:03d}.wav"
            
            cmd = [
                "ffmpeg",
                "-i", str(audio_path),
                "-ss", str(start_time),
                "-t", str(end_time - start_time),
                "-ar", "16000",  # 16kHz sample rate
                "-ac", "1",      # mono
                "-c:a", "pcm_s16le",  # 16-bit PCM
                "-y",            # overwrite output
                str(chunk_path)
            ]
            
            logger.info(f"Creating chunk {i+1}/{num_chunks}: {start_time:.1f}s - {end_time:.1f}s")
            
            try:
                import asyncio
                process = await asyncio.create_subprocess_exec(
                    *cmd,
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE
                )
                stdout, stderr = await process.communicate()
                
                if process.returncode != 0:
                    raise AudioProcessingError(f"FFmpeg error creating chunk {i+1}: {stderr.decode()}")
                
                chunks.append(chunk_path)
                
            except Exception as e:
                logger.error(f"Error creating chunk {i+1}: {e}")
                raise AudioProcessingError(f"Failed to create chunk {i+1}: {e}")
        
        return chunks

    async def _merge_chunk_results(
        self, 
        chunk_results: List[TranscriptionResult], 
        config: TranscriptionConfig,
        failed_chunks: Optional[List[Dict[str, Any]]] = None
    ) -> TranscriptionResult:
        """Merge transcription results from multiple chunks."""
        if not chunk_results:
            raise TranscriptionError("No chunk results to merge")
        
        # Merge text content
        merged_text = ""
        merged_segments = []
        merged_confidence_scores = []
        total_processing_time = 0
        total_word_count = 0
        
        # Calculate time offset for each chunk
        chunk_size_seconds = config.chunk_size_seconds
        time_offset = 0
        
        for i, chunk_result in enumerate(chunk_results):
            # Add chunk text to merged text
            if merged_text:
                merged_text += " "
            merged_text += chunk_result.text_content
            
            # Adjust segment timestamps and add to merged segments
            for segment in chunk_result.segments:
                adjusted_segment = segment.copy()
                adjusted_segment["start"] += time_offset
                adjusted_segment["end"] += time_offset
                merged_segments.append(adjusted_segment)
            
            # Add confidence scores
            if chunk_result.confidence_scores:
                merged_confidence_scores.extend(chunk_result.confidence_scores)
            
            # Accumulate processing time and word count
            total_processing_time += chunk_result.processing_time_ms
            total_word_count += chunk_result.word_count
            
            # Update time offset for next chunk
            time_offset += chunk_size_seconds
        
        # Calculate overall accuracy
        if merged_confidence_scores:
            overall_accuracy = sum(merged_confidence_scores) / len(merged_confidence_scores)
        else:
            overall_accuracy = 0.8  # Default accuracy
        
        # Generate quality warnings
        quality_warnings = self._generate_quality_warnings(overall_accuracy, {
            "segments": merged_segments,
            "text": merged_text
        })
        
        # Add partial transcription warnings
        if failed_chunks and len(failed_chunks) > 0:
            quality_warnings.append(f"Partial transcription: {len(chunk_results)}/{len(chunk_results) + len(failed_chunks)} chunks successful")
            quality_warnings.append("Some audio segments may be missing from the transcript")
        
        # Create merged raw content
        merged_raw_content = {
            "text": merged_text,
            "segments": merged_segments,
            "language": chunk_results[0].language if chunk_results else None,
            "chunk_count": len(chunk_results),
            "merged_at": datetime.now().isoformat(),
            "processing_metadata": {
                "total_chunks_processed": len(chunk_results),
                "chunk_processing_successful": True,
                "partial_transcription": failed_chunks is not None and len(failed_chunks) > 0,
                "failed_chunks": failed_chunks or []
            }
        }
        
        return TranscriptionResult(
            raw_content=merged_raw_content,
            text_content=merged_text,
            segments=merged_segments,
            model_used=config.model,
            processing_time_ms=total_processing_time,
            word_count=total_word_count,
            accuracy_estimate=overall_accuracy,
            quality_warnings=quality_warnings,
            language=chunk_results[0].language if chunk_results else None,
            confidence_scores=merged_confidence_scores
        )

    def _count_words(self, text: str) -> int:
        """Count words in text."""
        return len(text.split())

    def _estimate_accuracy(self, raw_content: Dict[str, Any]) -> float:
        """Estimate transcription accuracy based on multiple factors."""
        segments = raw_content.get("segments", [])
        if not segments:
            return 0.8  # Default accuracy for unknown content
        
        # Calculate confidence-based accuracy
        confidences = [seg.get("confidence", 0.8) for seg in segments]
        if not confidences:
            return 0.8
        
        confidence_accuracy = sum(confidences) / len(confidences)
        
        # Calculate length-based accuracy (longer segments tend to be more accurate)
        total_duration = sum(seg.get("duration", 0) for seg in segments)
        avg_segment_length = total_duration / len(segments) if segments else 0
        
        # Normalize segment length factor (0-1 scale)
        # Segments longer than 5 seconds get full credit, shorter segments get reduced credit
        length_factor = min(avg_segment_length / 5.0, 1.0) if avg_segment_length > 0 else 0.5
        
        # Calculate text quality factors
        text = raw_content.get("text", "")
        word_count = len(text.split()) if text else 0
        
        # Check for common transcription issues
        issue_penalty = 0.0
        
        # Penalty for very short text
        if word_count < 10:
            issue_penalty += 0.1
        
        # Penalty for excessive punctuation (potential noise)
        punctuation_count = sum(1 for char in text if char in '.,!?;:')
        if word_count > 0 and punctuation_count / word_count > 0.3:
            issue_penalty += 0.05
        
        # Penalty for repeated words (potential stuttering or noise)
        words = text.split()
        if len(words) > 10:
            word_freq = {}
            for word in words:
                word_freq[word.lower()] = word_freq.get(word.lower(), 0) + 1
            
            max_repetition = max(word_freq.values()) if word_freq else 1
            if max_repetition > len(words) * 0.1:  # More than 10% repetition
                issue_penalty += 0.05
        
        # Combine factors with weights
        final_accuracy = (
            confidence_accuracy * 0.7 +  # 70% weight on confidence
            length_factor * 0.2 +        # 20% weight on segment length
            (1.0 - issue_penalty) * 0.1  # 10% weight on text quality
        )
        
        # Ensure accuracy is between 0 and 1
        return max(0.0, min(1.0, final_accuracy))

    def _generate_quality_warnings(
        self, 
        accuracy: float, 
        raw_content: Dict[str, Any]
    ) -> List[str]:
        """Generate quality warnings based on accuracy and content analysis."""
        warnings = []
        
        # Accuracy-based warnings
        if accuracy < 0.95:
            warnings.append("Accuracy below 95% - review recommended")
        
        if accuracy < 0.8:
            warnings.append("Low accuracy detected - consider re-processing with different settings")
        
        if accuracy < 0.6:
            warnings.append("Very low accuracy - audio quality may be poor or content unclear")
        
        # Content-based warnings
        text = raw_content.get("text", "")
        segments = raw_content.get("segments", [])
        
        if segments:
            # Check for very short segments (potential noise)
            short_segments = [seg for seg in segments if seg.get("duration", 0) < 0.5]
            if len(short_segments) > len(segments) * 0.1:  # More than 10% are very short
                warnings.append("Many short segments detected - possible audio quality issues")
            
            # Check for very long segments (potential issues)
            long_segments = [seg for seg in segments if seg.get("duration", 0) > 30]
            if len(long_segments) > len(segments) * 0.2:  # More than 20% are very long
                warnings.append("Many long segments detected - potential speaker overlap or unclear speech")
            
            # Check for segments with very low confidence
            low_confidence_segments = [seg for seg in segments if seg.get("confidence", 1.0) < 0.5]
            if len(low_confidence_segments) > len(segments) * 0.15:  # More than 15% have low confidence
                warnings.append("Many low-confidence segments - audio may be unclear or contain background noise")
        
        # Text quality warnings
        if text:
            word_count = len(text.split())
            
            # Check for very short transcripts
            if word_count < 10:
                warnings.append("Very short transcript - audio may be silent or too quiet")
            
            # Check for excessive punctuation (potential noise)
            punctuation_count = sum(1 for char in text if char in '.,!?;:')
            if word_count > 0 and punctuation_count / word_count > 0.3:
                warnings.append("Excessive punctuation detected - possible transcription artifacts")
            
            # Check for repeated words (potential stuttering or noise)
            words = text.split()
            if len(words) > 10:
                word_freq = {}
                for word in words:
                    word_freq[word.lower()] = word_freq.get(word.lower(), 0) + 1
                
                max_repetition = max(word_freq.values()) if word_freq else 1
                if max_repetition > len(words) * 0.1:  # More than 10% repetition
                    warnings.append("Excessive word repetition detected - possible audio quality issues")
            
            # Check for all caps (potential shouting or poor audio)
            caps_ratio = sum(1 for word in words if word.isupper()) / len(words) if words else 0
            if caps_ratio > 0.3:  # More than 30% all caps
                warnings.append("Excessive capitalization detected - possible shouting or poor audio quality")
        
        # Language detection warnings
        language = raw_content.get("language")
        if language and language not in ["en", "english"]:
            warnings.append(f"Non-English content detected (language: {language}) - accuracy may vary")
        
        return warnings


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