"""Batch processor for parallel file processing."""

import asyncio
import logging
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

from .processor_types import AsyncProcessor, MediaProcessor, RetryConfig
from .retry_utils import async_retry

logger = logging.getLogger(__name__)


class BatchProcessor(AsyncProcessor):
    """Batch processing with retry and backoff patterns.

    Handles parallel processing of multiple files with intelligent
    error recovery and progress tracking.
    """

    def __init__(self, name: str = "BatchProcessor", config: Optional[Dict[str, Any]] = None):
        """Initialize batch processor.

        Args:
            name: Processor name
            config: Processor configuration

        """
        super().__init__(name, config)

        # Batch processing settings
        self.max_parallel = config.get("max_parallel", 4) if config else 4
        self.batch_size = config.get("batch_size", 10) if config else 10

        # Retry configuration
        self.retry_config = RetryConfig(
            max_attempts=config.get("max_retries", 3) if config else 3,
            initial_delay=1.0,
            backoff_factor=2.0,
            jitter=True,
        )

        # Progress tracking
        self.current_batch: Optional[Dict[str, Any]] = None
        self.processing_stats = {
            "total_processed": 0,
            "total_successful": 0,
            "total_failed": 0,
            "start_time": None,
            "last_batch_time": None,
        }

    async def process_batch(self, files: List[Path], processor: MediaProcessor) -> Dict[str, Any]:
        """Process a batch of files with parallel execution.

        Args:
            files: List of file paths to process
            processor: Media processor to use

        Returns:
            Batch processing results

        """
        self.current_batch = {
            "total": len(files),
            "processed": 0,
            "successful": 0,
            "failed": 0,
            "start_time": datetime.utcnow(),
            "results": [],
            "errors": [],
        }

        # Process in chunks for memory efficiency
        for chunk_start in range(0, len(files), self.batch_size):
            chunk_end = min(chunk_start + self.batch_size, len(files))
            chunk = files[chunk_start:chunk_end]

            # Process chunk in parallel
            await self._process_chunk(chunk, processor)

        # Update global stats
        self._update_stats()

        # Calculate performance metrics
        elapsed = (datetime.utcnow() - self.current_batch["start_time"]).total_seconds()
        self.current_batch["elapsed_seconds"] = elapsed
        self.current_batch["files_per_second"] = len(files) / elapsed if elapsed > 0 else 0

        return self.current_batch

    async def _process_chunk(self, chunk: List[Path], processor: MediaProcessor) -> None:
        """Process a chunk of files in parallel.

        Args:
            chunk: Chunk of files to process
            processor: Media processor to use

        """
        # Create tasks with semaphore for concurrency control
        semaphore = asyncio.Semaphore(self.max_parallel)

        async def process_with_limit(file_path: Path) -> Dict[str, Any]:
            async with semaphore:
                return await self._process_single_with_retry(file_path, processor)

        # Process all files in chunk
        tasks = [process_with_limit(file_path) for file_path in chunk]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Update batch results
        for file_path, result in zip(chunk, results, strict=False):
            self.current_batch["processed"] += 1

            if isinstance(result, Exception):
                self.current_batch["failed"] += 1
                self.current_batch["errors"].append(
                    {
                        "file": str(file_path),
                        "error": str(result),
                        "type": type(result).__name__,
                    }
                )
            else:
                self.current_batch["successful"] += 1
                self.current_batch["results"].append(result)

    @async_retry(max_attempts=3)
    async def _process_single_with_retry(
        self, file_path: Path, processor: MediaProcessor
    ) -> Dict[str, Any]:
        """Process a single file with retry logic.

        Args:
            file_path: File to process
            processor: Media processor to use

        Returns:
            Processing result

        """
        start_time = datetime.utcnow()

        # Check if processor can handle the file
        if not processor.can_process(file_path):
            raise ValueError(f"Cannot process {file_path.suffix} files")

        # Process the file
        result_path = await processor.process(file_path)

        # Calculate processing time
        elapsed = (datetime.utcnow() - start_time).total_seconds()

        return {
            "input": str(file_path),
            "output": str(result_path),
            "processing_time": elapsed,
            "timestamp": datetime.utcnow().isoformat(),
        }

    def _update_stats(self) -> None:
        """Update global processing statistics."""
        if self.current_batch:
            self.processing_stats["total_processed"] += self.current_batch["processed"]
            self.processing_stats["total_successful"] += self.current_batch["successful"]
            self.processing_stats["total_failed"] += self.current_batch["failed"]
            self.processing_stats["last_batch_time"] = datetime.utcnow()

            if self.processing_stats["start_time"] is None:
                self.processing_stats["start_time"] = self.current_batch["start_time"]

    def get_stats(self) -> Dict[str, Any]:
        """Get processing statistics.

        Returns:
            Current processing statistics

        """
        stats = self.processing_stats.copy()

        # Add current batch info if processing
        if self.current_batch:
            stats["current_batch"] = {
                "progress": f"{self.current_batch['processed']}/{self.current_batch['total']}",
                "success_rate": (
                    self.current_batch["successful"] / self.current_batch["processed"]
                    if self.current_batch["processed"] > 0
                    else 0
                ),
            }

        # Calculate overall metrics
        if stats["total_processed"] > 0:
            stats["overall_success_rate"] = stats["total_successful"] / stats["total_processed"]
        else:
            stats["overall_success_rate"] = 0

        return stats

    async def process_async(self, data: Any) -> Any:
        """Async processing interface from base class.

        Args:
            data: Input data (should be a tuple of (files, processor))

        Returns:
            Batch processing results

        """
        if isinstance(data, tuple) and len(data) == 2:
            files, processor = data
            return await self.process_batch(files, processor)
        raise TypeError(f"Expected tuple of (files, processor), got {type(data)}")
