"""Optimized I/O operations for better performance."""

from __future__ import annotations

import asyncio
import os
from pathlib import Path
from typing import AsyncGenerator, Generator

from pytola.core.logger import get_logger

logger = get_logger(__name__)


class OptimizedIO:
    """Optimized I/O operations with batching and buffering."""

    def __init__(self, buffer_size: int = 8192, batch_size: int = 100):
        """Initialize optimized I/O.

        Args:
            buffer_size: Buffer size for file operations
            batch_size: Batch size for directory operations
        """
        self.buffer_size = buffer_size
        self.batch_size = batch_size

    async def read_file_async(self, file_path: Path) -> str:
        """Read file asynchronously with optimized buffering.

        Args:
            file_path: Path to file

        Returns
        -------
            File content as string
        """
        try:
            # Use thread pool for I/O bound operations
            loop = asyncio.get_event_loop()
            return await loop.run_in_executor(None, file_path.read_text, "utf-8")
        except Exception as e:
            logger.debug(f"Failed to read {file_path}: {e}")
            return ""

    async def write_file_async(self, file_path: Path, content: str) -> bool:
        """Write file asynchronously with optimized buffering.

        Args:
            file_path: Path to file
            content: Content to write

        Returns
        -------
            Success status
        """
        try:
            # Use thread pool for I/O bound operations
            loop = asyncio.get_event_loop()
            # Ensure parent directory exists
            file_path.parent.mkdir(parents=True, exist_ok=True)
            await loop.run_in_executor(None, file_path.write_text, content, "utf-8")
            return True
        except Exception as e:
            logger.warning(f"Failed to write {file_path}: {e}")
            return False

    def read_file_batch(self, file_paths: list[Path]) -> dict[Path, str]:
        """Read multiple files in batch with optimized I/O.

        Args:
            file_paths: List of file paths to read

        Returns
        -------
            Dictionary mapping file paths to content
        """
        results = {}

        # Process in batches to avoid memory issues
        for i in range(0, len(file_paths), self.batch_size):
            batch = file_paths[i : i + self.batch_size]
            batch_results = {}

            for file_path in batch:
                try:
                    with file_path.open("r", encoding="utf-8", buffering=self.buffer_size) as f:
                        batch_results[file_path] = f.read()
                except Exception as e:
                    logger.debug(f"Failed to read {file_path}: {e}")
                    batch_results[file_path] = ""

            results.update(batch_results)

        return results

    async def read_file_batch_async(self, file_paths: list[Path]) -> dict[Path, str]:
        """Read multiple files asynchronously in batch.

        Args:
            file_paths: List of file paths to read

        Returns
        -------
            Dictionary mapping file paths to content
        """
        results = {}

        # Process in batches
        for i in range(0, len(file_paths), self.batch_size):
            batch = file_paths[i : i + self.batch_size]
            batch_tasks = [self.read_file_async(file_path) for file_path in batch]

            batch_contents = await asyncio.gather(*batch_tasks, return_exceptions=True)

            for file_path, content in zip(batch, batch_contents):
                if isinstance(content, Exception):
                    logger.debug(f"Failed to read {file_path}: {content}")
                    results[file_path] = ""
                else:
                    results[file_path] = content

        return results

    def copy_files_batch(self, source_dest_pairs: list[tuple[Path, Path]]) -> int:
        """Copy multiple files in batch with optimized I/O.

        Args:
            source_dest_pairs: List of (source, destination) path pairs

        Returns
        -------
            Number of successfully copied files
        """
        success_count = 0

        for i in range(0, len(source_dest_pairs), self.batch_size):
            batch = source_dest_pairs[i : i + self.batch_size]

            for source, dest in batch:
                try:
                    # Ensure destination directory exists
                    dest.parent.mkdir(parents=True, exist_ok=True)

                    # Copy with buffering
                    with source.open("rb") as src_file, dest.open("wb") as dst_file:
                        while True:
                            chunk = src_file.read(self.buffer_size)
                            if not chunk:
                                break
                            dst_file.write(chunk)

                    success_count += 1
                except Exception as e:
                    logger.debug(f"Failed to copy {source} to {dest}: {e}")

        return success_count

    async def copy_files_batch_async(self, source_dest_pairs: list[tuple[Path, Path]]) -> int:
        """Copy multiple files asynchronously in batch.

        Args:
            source_dest_pairs: List of (source, destination) path pairs

        Returns
        -------
            Number of successfully copied files
        """
        success_count = 0

        for i in range(0, len(source_dest_pairs), self.batch_size):
            batch = source_dest_pairs[i : i + self.batch_size]
            batch_tasks = []

            for source, dest in batch:
                # Ensure destination directory exists
                dest.parent.mkdir(parents=True, exist_ok=True)
                batch_tasks.append(self._copy_file_async(source, dest))

            results = await asyncio.gather(*batch_tasks, return_exceptions=True)
            success_count += sum(1 for result in results if result is True)

        return success_count

    async def _copy_file_async(self, source: Path, dest: Path) -> bool:
        """Copy single file asynchronously."""
        try:
            # Use thread pool for I/O bound operations
            loop = asyncio.get_event_loop()

            def sync_copy():
                # Ensure destination directory exists
                dest.parent.mkdir(parents=True, exist_ok=True)

                # Copy with buffering
                with source.open("rb") as src_file, dest.open("wb") as dst_file:
                    while True:
                        chunk = src_file.read(self.buffer_size)
                        if not chunk:
                            break
                        dst_file.write(chunk)
                return True

            return await loop.run_in_executor(None, sync_copy)
        except Exception as e:
            logger.debug(f"Failed to copy {source} to {dest}: {e}")
            return False

    def scan_directory_optimized(self, directory: Path, pattern: str = "*") -> Generator[Path, None, None]:
        """Scan directory with optimized performance.

        Args:
            directory: Directory to scan
            pattern: File pattern to match

        Yields
        ------
            Path objects matching the pattern
        """
        try:
            if not directory.exists():
                return

            # Use os.walk for better performance on large directories
            for root, dirs, files in os.walk(directory):
                root_path = Path(root)

                # Filter files
                for file_name in files:
                    if pattern == "*" or self._matches_pattern(file_name, pattern):
                        yield root_path / file_name

                # Optimize directory traversal by modifying dirs in-place
                dirs[:] = [d for d in dirs if not d.startswith(".")]

        except Exception as e:
            logger.debug(f"Failed to scan directory {directory}: {e}")

    async def scan_directory_async(self, directory: Path, pattern: str = "*") -> AsyncGenerator[Path, None]:
        """Scan directory asynchronously.

        Args:
            directory: Directory to scan
            pattern: File pattern to match

        Yields
        ------
            Path objects matching the pattern
        """
        try:
            if not directory.exists():
                return

            # Use thread pool for I/O bound directory scanning
            loop = asyncio.get_event_loop()

            def sync_scan():
                return list(self.scan_directory_optimized(directory, pattern))

            paths = await loop.run_in_executor(None, sync_scan)
            for path in paths:
                yield path

        except Exception as e:
            logger.debug(f"Failed to scan directory {directory} asynchronously: {e}")

    def _matches_pattern(self, filename: str, pattern: str) -> bool:
        """Check if filename matches pattern (simple implementation)."""
        if pattern == "*":
            return True
        if "*" in pattern:
            import fnmatch

            return fnmatch.fnmatch(filename, pattern)
        return filename == pattern


# Global optimized I/O instance
optimized_io = OptimizedIO()
