"""
Role: Implements hydra_logger.handlers.console_handler functionality for Hydra Logger.
Used By:
 - Internal `hydra_logger` modules importing this component.
Depends On:
 - asyncio
 - atexit
 - hydra_logger
 - sys
 - time
 - typing
Notes:
 - Implements log destination handling and I/O flow for console handler.
"""

import asyncio
import atexit
import logging
import sys
import time
from typing import List, Optional, TextIO

from ..formatters.base import BaseFormatter
from ..types.records import LogRecord
from .base_handler import BaseHandler

_logger = logging.getLogger(__name__)


class SyncConsoleHandler(BaseHandler):
    """
    Synchronous console handler with buffering.

    Features:
    - buffering for high performance
    - Color support (console only, off by default)
    - Automatic flushing based on size and time
    - LRU cache for color lookups (2.8x speedup)
    """

    def __init__(
        self,
        stream: Optional[TextIO] = None,
        formatter: Optional[BaseFormatter] = None,
        buffer_size: int = 5000,  # PERFORMANCE: Larger default buffer (5K messages)
        flush_interval: float = 0.5,  # PERFORMANCE: Longer flush interval (0.5s)
        use_colors: bool = False,
    ):
        """
        Initialize console handler.

        Args:
            stream: Output stream (defaults to sys.stdout)
            formatter: Log formatter
            buffer_size: Buffer size for batching
            flush_interval: Flush interval in seconds
            use_colors: Whether to use colors (console only, off by default)
        """
        super().__init__()
        self._stream = stream or sys.stdout
        if formatter:
            self.setFormatter(formatter)

        # Buffering configuration
        self._buffer_size = buffer_size
        self._flush_interval = flush_interval
        self._buffer: List[str] = []
        self._last_flush = time.perf_counter()

        # Statistics
        self._messages_processed = 0
        self._total_bytes_written = 0
        self._start_time = time.perf_counter()

        # Color support (console only)
        self._use_colors = use_colors

        # Create formatter instances once for performance
        self._plain_formatter = None
        self._colored_formatter = None

        # Register for automatic cleanup
        atexit.register(self._auto_cleanup)

    def _get_formatter(self):
        """Get the appropriate formatter (lazy initialization for performance)."""
        if self.formatter:
            return self.formatter

        if self._use_colors:
            if self._colored_formatter is None:
                from ..formatters import get_formatter

                self._colored_formatter = get_formatter("colored", use_colors=True)
            return self._colored_formatter
        else:
            if self._plain_formatter is None:
                from ..formatters import get_formatter

                self._plain_formatter = get_formatter("plain-text", use_colors=False)
            return self._plain_formatter

    def emit(self, record: LogRecord) -> None:
        """
        Emit log record to console stream with buffering.

        Args:
            record: Log record to emit
        """
        # Format message using cached formatter for performance
        formatter = self._get_formatter()
        message = formatter.format(record)

        # Add to buffer
        self._buffer.append(message)
        self._messages_processed += 1

        # PERFORMANCE: Batch even with colors (smaller batches for colors, but still batch)
        # Colors can be batched - terminal will handle them correctly
        current_time = time.perf_counter()

        # Use smaller buffer for colors (for faster visual feedback), larger for
        # plain text
        effective_buffer_size = (
            max(10, self._buffer_size // 10) if self._use_colors else self._buffer_size
        )
        effective_flush_interval = (
            max(0.01, self._flush_interval / 5)
            if self._use_colors
            else self._flush_interval
        )

        should_flush = (
            len(self._buffer) >= effective_buffer_size
            or (current_time - self._last_flush) >= effective_flush_interval
        )

        if should_flush:
            self._flush_buffer()

    def _flush_buffer(self) -> None:
        """Flush buffer to stream efficiently."""
        if not self._buffer:
            return
        if getattr(self._stream, "closed", False):
            self._buffer.clear()
            return

        # Join all messages with newlines and write in one operation
        combined_message = "\n".join(self._buffer) + "\n"
        self._stream.write(combined_message)
        self._stream.flush()

        # Update statistics
        self._total_bytes_written += len(combined_message.encode("utf-8"))
        self._last_flush = time.perf_counter()

        # Clear buffer
        self._buffer.clear()

    def _auto_cleanup(self) -> None:
        """Cleanup handler on exit."""
        try:
            if hasattr(self, "_buffer") and self._buffer:
                self._flush_buffer()
        except ValueError as exc:
            if "closed file" in str(exc).lower():
                self._buffer.clear()
                _logger.debug(
                    "Sync console auto cleanup skipped flush for closed stream"
                )
                return
            _logger.exception("Sync console auto cleanup failed")
        except Exception:
            _logger.exception("Sync console auto cleanup failed")

    def get_stats(self) -> dict:
        """Get handler statistics."""
        runtime = time.perf_counter() - self._start_time
        return {
            "messages_processed": self._messages_processed,
            "total_bytes_written": self._total_bytes_written,
            "runtime_seconds": runtime,
            "messages_per_second": (
                self._messages_processed / runtime if runtime > 0 else 0
            ),
            "buffer_size": len(self._buffer),
            "use_colors": self._use_colors,
        }


class AsyncConsoleHandler(BaseHandler):
    """
    Asynchronous console handler with simple, efficient design.

    Features:
    - Simple async queue with batching
    - Color support (console only, off by default)
    - Non-blocking operations with proper cleanup
    - LRU cache for color lookups (2.8x speedup)
    """

    def __init__(
        self,
        stream: Optional[TextIO] = None,
        formatter: Optional[BaseFormatter] = None,
        buffer_size: int = 5000,  # PERFORMANCE: Larger default buffer (5K messages)
        flush_interval: float = 0.5,  # PERFORMANCE: Longer flush interval (0.5s)
        use_colors: bool = False,
    ):
        """
        Initialize async console handler.

        Args:
            stream: Output stream (defaults to sys.stdout)
            formatter: Log formatter
            buffer_size: Buffer size for batching
            flush_interval: Flush interval in seconds
            use_colors: Whether to use colors (console only, off by default)
        """
        super().__init__()
        self._stream = stream or sys.stdout
        if formatter:
            self.setFormatter(formatter)

        # Buffering configuration - auto-detect if None
        if buffer_size is None or flush_interval is None:
            try:
                from ..utils.system_detector import get_optimal_buffer_config

                optimal_config = get_optimal_buffer_config("console")
                buffer_size = buffer_size or optimal_config["buffer_size"]
                flush_interval = flush_interval or optimal_config["flush_interval"]
            except Exception:
                _logger.exception(
                    "Async console optimal buffer detection failed; using defaults"
                )
                # Fallback defaults (optimized for performance)
                buffer_size = buffer_size or 5000
                flush_interval = flush_interval or 0.5

        self._buffer_size = buffer_size
        self._flush_interval = flush_interval

        # PERFORMANCE: Buffering for async emit_async (reduces I/O overhead)
        self._async_buffer: List[str] = []
        self._async_buffer_lock = asyncio.Lock()
        self._last_async_flush = time.perf_counter()

        # Simple async queue (for worker loop if needed)
        self._message_queue: asyncio.Queue[str] = asyncio.Queue(maxsize=buffer_size * 2)
        self._shutdown_event = asyncio.Event()
        self._worker_task: Optional[asyncio.Task[None]] = None
        self._running = False

        # Statistics
        self._messages_processed = 0
        self._messages_dropped = 0
        self._total_bytes_written = 0
        self._start_time = time.perf_counter()

        # Color support (console only)
        self._use_colors = use_colors

        # Create formatter instances once for performance
        self._plain_formatter = None
        self._colored_formatter = None

        # PERFORMANCE: Thread pool for non-blocking console I/O
        self._executor = None

        # Register for automatic cleanup
        atexit.register(self._auto_cleanup)

    def _get_formatter(self):
        """Get the appropriate formatter (lazy initialization for performance)."""
        if self.formatter:
            return self.formatter

        if self._use_colors:
            if self._colored_formatter is None:
                from ..formatters import get_formatter

                self._colored_formatter = get_formatter("colored", use_colors=True)
            return self._colored_formatter
        else:
            if self._plain_formatter is None:
                from ..formatters import get_formatter

                self._plain_formatter = get_formatter("plain-text", use_colors=False)
            return self._plain_formatter

    def emit(self, record: LogRecord) -> None:
        """
        Emit log record to console stream (non-blocking).

        Args:
            record: Log record to emit
        """
        # Format message using cached formatter for performance
        formatter = self._get_formatter()
        message = formatter.format(record)

        # Non-blocking direct output for performance
        # Write directly to stream for immediate output (colors or no colors)
        try:
            self._stream.write(message + "\n")
            self._stream.flush()
            self._messages_processed += 1
        except Exception:
            # If console output fails, don't block the entire system
            self._messages_dropped += 1

    async def emit_async(self, record: LogRecord) -> None:
        """
        Async emit method with buffering and non-blocking I/O.

        PERFORMANCE: Uses buffering and run_in_executor to avoid blocking the event loop.

        Args:
            record: Log record to emit
        """
        # Format message using cached formatter for performance
        formatter = self._get_formatter()
        message = formatter.format(record)

        # PERFORMANCE: Buffer messages and flush in batches (much faster than
        # individual writes)
        async with self._async_buffer_lock:
            self._async_buffer.append(message)
            self._messages_processed += 1

            # Check if we should flush
            current_time = time.perf_counter()
            should_flush = (
                len(self._async_buffer) >= self._buffer_size
                or (current_time - self._last_async_flush) >= self._flush_interval
            )

            if should_flush:
                # Flush buffer using non-blocking I/O
                await self._flush_async_buffer()

    async def _flush_async_buffer(self) -> None:
        """Flush async buffer using non-blocking I/O."""
        if not self._async_buffer:
            return

        # Get messages to flush and clear buffer
        messages_to_flush = self._async_buffer.copy()
        self._async_buffer.clear()
        self._last_async_flush = time.perf_counter()

        # PERFORMANCE: Use run_in_executor for non-blocking console I/O
        # This prevents blocking the event loop with synchronous stream.write()
        try:
            loop = asyncio.get_event_loop()

            # Join all messages and write in one operation (much faster)
            combined_message = "\n".join(messages_to_flush) + "\n"

            # Run I/O in executor to avoid blocking event loop
            await loop.run_in_executor(
                None, lambda: self._write_to_stream(combined_message)
            )

            # Update statistics
            self._total_bytes_written += len(combined_message.encode("utf-8"))

        except Exception:
            # If console output fails, don't block the entire system
            self._messages_dropped += len(messages_to_flush)
            _logger.exception(
                "Async console buffer flush failed; dropped=%s",
                len(messages_to_flush),
            )

    def _write_to_stream(self, message: str) -> None:
        """Write to stream (called from executor to avoid blocking)."""
        try:
            self._stream.write(message)
            self._stream.flush()
        except Exception:
            _logger.exception("Console stream write failed")

    async def _start_worker(self) -> None:
        """Start async worker task."""
        if self._running:
            return

        try:
            self._running = True
            self._worker_task = asyncio.create_task(self._worker_loop())
        except Exception as e:
            _logger.exception("Failed to start async console worker: %s", e)
            self._running = False

    async def _worker_loop(self) -> None:
        """Simple worker loop for processing messages."""
        messages = []
        last_flush = time.perf_counter()

        try:
            while not self._shutdown_event.is_set():
                try:
                    # Check shutdown before waiting - exit immediately if signaled
                    if self._shutdown_event.is_set():
                        break

                    # Wait for messages with timeout - handle loop closure gracefully
                    try:
                        message = await asyncio.wait_for(
                            self._message_queue.get(), timeout=self._flush_interval
                        )
                        messages.append(message)
                    except asyncio.TimeoutError:
                        # Timeout - check if we should flush
                        pass
                    except (RuntimeError, asyncio.CancelledError):
                        # Event loop closed, cancelled, or different loop - exit
                        # immediately
                        break

                    # Check shutdown again after getting message
                    if self._shutdown_event.is_set():
                        break

                    # Process all available messages (non-blocking)
                    while (
                        not self._message_queue.empty()
                        and not self._shutdown_event.is_set()
                    ):
                        try:
                            message = self._message_queue.get_nowait()
                            messages.append(message)
                        except asyncio.QueueEmpty:
                            break
                        except (RuntimeError, asyncio.CancelledError):
                            # Loop closed or cancelled
                            break

                    # Flush if needed
                    current_time = time.perf_counter()
                    should_flush = (
                        len(messages) >= self._buffer_size
                        or (current_time - last_flush) >= self._flush_interval
                    )

                    if should_flush and messages:
                        try:
                            await self._write_messages(messages)
                            messages.clear()
                            last_flush = current_time
                        except (RuntimeError, asyncio.CancelledError):
                            # Loop closed or cancelled - exit
                            break

                except (RuntimeError, asyncio.CancelledError):
                    # Event loop closed or cancelled - exit immediately
                    break
                except Exception:
                    # Clear messages on error, but continue
                    if self._shutdown_event.is_set():
                        break
                    _logger.exception(
                        "Async console worker iteration failed; clearing batch"
                    )
                    messages.clear()
        except (RuntimeError, asyncio.CancelledError):
            # Exit immediately on cancellation or loop closure
            _logger.debug("Async console worker cancelled or runtime closed")
        except Exception:
            # Exit on any other error
            _logger.exception("Async console worker terminated due to unexpected error")
        finally:
            # Process remaining messages if any
            if messages:
                try:
                    await self._write_messages(messages)
                except (RuntimeError, asyncio.CancelledError, Exception):
                    _logger.exception("Async console final message flush failed")

    async def _write_messages(self, messages: list) -> None:
        """
        Write messages to stream efficiently using non-blocking I/O.

        PERFORMANCE: Uses run_in_executor to avoid blocking the event loop.

        Args:
            messages: List of messages to write
        """
        if not messages:
            return

        # Join all messages with newlines and write in one operation
        combined_message = "\n".join(messages) + "\n"

        # PERFORMANCE: Use run_in_executor for non-blocking I/O
        try:
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None, lambda: self._write_to_stream(combined_message)
            )
            # Update statistics
            self._total_bytes_written += len(combined_message.encode("utf-8"))
        except Exception:
            self._messages_dropped += len(messages)

    def _auto_cleanup(self) -> None:
        """Cleanup handler on exit."""
        try:
            if self._worker_task and not self._worker_task.done():
                self._shutdown_event.set()
                # Give it a moment to finish
                try:
                    asyncio.get_event_loop().run_until_complete(
                        asyncio.wait_for(self._worker_task, timeout=1.0)
                    )
                except (asyncio.TimeoutError, RuntimeError):
                    self._worker_task.cancel()
        except Exception:
            _logger.exception("Async console auto cleanup failed")

    async def aclose(self) -> None:
        """Close async handler and cleanup resources."""
        # PERFORMANCE: Flush any remaining buffered messages before closing
        async with self._async_buffer_lock:
            if self._async_buffer:
                await self._flush_async_buffer()

        # Signal shutdown first - worker loop will exit immediately
        self._shutdown_event.set()
        self._running = False

        if self._worker_task and not self._worker_task.done():
            try:
                # Check if task is in same event loop
                try:
                    current_loop = asyncio.get_running_loop()
                    task_loop = (
                        self._worker_task.get_loop()
                        if hasattr(self._worker_task, "get_loop")
                        else None
                    )

                    # If task is in different loop, just cancel it (can't await)
                    if task_loop and task_loop != current_loop:
                        self._worker_task.cancel()
                        return
                except (RuntimeError, AttributeError):
                    # Can't determine loop - try to cancel anyway
                    pass

                # Task is in same loop - cancel and await properly
                self._worker_task.cancel()

                try:
                    # Use wait() with short timeout
                    done, pending = await asyncio.wait(
                        [self._worker_task],
                        timeout=0.3,
                        return_when=asyncio.ALL_COMPLETED,
                    )
                    # If still pending, it's likely cancelled - that's fine
                except RuntimeError as e:
                    # Different loop or loop closed - can't await
                    if "different loop" in str(e) or "closed" in str(e).lower():
                        pass
                    else:
                        raise
                except asyncio.CancelledError:
                    # Task was cancelled - that's expected
                    pass
            except (RuntimeError, asyncio.CancelledError, Exception):
                # If anything fails, just mark as closed
                _logger.exception("Async console close encountered cleanup error")

    def get_stats(self) -> dict:
        """Get handler statistics."""
        runtime = time.perf_counter() - self._start_time
        return {
            "messages_processed": self._messages_processed,
            "messages_dropped": self._messages_dropped,
            "total_bytes_written": self._total_bytes_written,
            "runtime_seconds": runtime,
            "messages_per_second": (
                self._messages_processed / runtime if runtime > 0 else 0
            ),
            "queue_size": self._message_queue.qsize(),
            "use_colors": self._use_colors,
        }


# Factory functions for easy handler creation
def create_sync_console_handler(
    stream: Optional[TextIO] = None,
    formatter: Optional[BaseFormatter] = None,
    buffer_size: int = 1000,
    flush_interval: float = 0.1,
    use_colors: bool = False,
) -> SyncConsoleHandler:
    """
    Create a synchronous console handler.

    Args:
        stream: Output stream
        formatter: Log formatter
        buffer_size: Buffer size
        flush_interval: Flush interval in seconds
        use_colors: Whether to use colors (console only, off by default)

    Returns:
        Configured SyncConsoleHandler
    """
    return SyncConsoleHandler(
        stream, formatter, buffer_size, flush_interval, use_colors
    )


def create_async_console_handler(
    stream: Optional[TextIO] = None,
    formatter: Optional[BaseFormatter] = None,
    buffer_size: int = 1000,
    flush_interval: float = 0.1,
    use_colors: bool = False,
) -> AsyncConsoleHandler:
    """
    Create an asynchronous console handler.

    Args:
        stream: Output stream
        formatter: Log formatter
        buffer_size: Buffer size
        flush_interval: Flush interval in seconds
        use_colors: Whether to use colors (console only, off by default)

    Returns:
        Configured AsyncConsoleHandler
    """
    return AsyncConsoleHandler(
        stream, formatter, buffer_size, flush_interval, use_colors
    )
