#!/usr/bin/env python3
"""Process status display classes for real-time monitoring."""

import logging
import threading
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional

from ci.util.global_interrupt_handler import handle_keyboard_interrupt


if TYPE_CHECKING:
    from ci.util.running_process_group import GroupStatus, RunningProcessGroup

logger = logging.getLogger(__name__)


@dataclass
class DisplayConfig:
    """Configuration for display output format."""

    format_type: str = "ascii"  # "ascii", "rich", "csv"
    use_colors: bool = True
    update_interval: float = 0.1
    max_name_width: int = 20
    max_output_width: int = 40


class ProcessStatusDisplay(ABC):
    """Abstract base class for process status displays."""

    def __init__(
        self, group: "RunningProcessGroup", config: Optional[DisplayConfig] = None
    ):
        self.group = group
        self.config = config or DisplayConfig()
        self._display_thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()

    @abstractmethod
    def format_status_line(
        self, group_status: "GroupStatus", spinner_index: int
    ) -> str:
        """Format the complete status display."""
        pass

    def start_display(self) -> threading.Thread:
        """Start the real-time display in a background thread."""
        if self._display_thread and self._display_thread.is_alive():
            return self._display_thread

        self._stop_event.clear()
        self._display_thread = threading.Thread(target=self._display_loop, daemon=True)
        self._display_thread.start()
        return self._display_thread

    def stop_display(self) -> None:
        """Stop the display thread."""
        if self._display_thread:
            self._stop_event.set()
            self._display_thread.join(timeout=1.0)

    def _display_loop(self) -> None:
        """Main display loop running in background thread."""
        spinner_index = 0
        # Initialize to current time so first status only shows after 5s of activity
        # This prevents duplicate "Running:" messages from appearing immediately
        last_status_time = time.time()
        status_interval = (
            5  # Show status every 5 seconds (reduced noise for short tasks)
        )
        last_printed_message: Optional[str] = None  # Track last message to avoid spam

        while not self._stop_event.is_set():
            try:
                # Wait for monitoring to start, or exit if stopped
                if not self.group._status_monitoring_active:
                    time.sleep(0.1)
                    continue

                current_time = time.time()
                group_status = self.group.get_status()

                # Show periodic status updates instead of continuous display
                if current_time - last_status_time >= status_interval:
                    running_count = sum(1 for p in group_status.processes if p.is_alive)
                    completed_count = group_status.completed_processes

                    if running_count > 0:
                        # Show a clean status update (no spinner noise)
                        running_names = [
                            p.name for p in group_status.processes if p.is_alive
                        ]
                        running_list = (
                            ", ".join(running_names) if running_names else "none"
                        )
                        total = group_status.total_processes
                        # For single item, omit counter; for multiple, use 1-indexed current position
                        if total == 1:
                            status_msg = f"  Running: {running_list}"
                        else:
                            # Show 1-indexed "current/total" (e.g., [1/3] when first is running)
                            current = completed_count + 1
                            status_msg = (
                                f"  [{current}/{total}] Running: {running_list}"
                            )

                        # Only print if status changed to avoid spam
                        if status_msg != last_printed_message:
                            print(status_msg, flush=True)
                            last_printed_message = status_msg

                        last_status_time = current_time

                spinner_index = (spinner_index + 1) % 4
                time.sleep(1.0)  # Check every second but only print every 5 seconds

            except KeyboardInterrupt as ki:
                handle_keyboard_interrupt(ki)
                raise
            except Exception as e:
                logger.warning(f"Display update error: {e}")
                time.sleep(1.0)


class ASCIIStatusDisplay(ProcessStatusDisplay):
    """ASCII-compatible status display."""

    def __init__(
        self, group: "RunningProcessGroup", config: Optional[DisplayConfig] = None
    ):
        if config is None:
            config = DisplayConfig(format_type="ascii")
        super().__init__(group, config)

        self._spinner_chars = ["|", "/", "-", "\\\\"]
        self._status_chars = {
            "running": "|>",
            "done": "OK",
            "failed": "XX",
            "pending": "--",
        }

    def format_status_line(
        self, group_status: "GroupStatus", spinner_index: int
    ) -> str:
        """Format status display using ASCII characters."""
        lines: list[str] = []

        # Header
        lines.append(
            f"Process Group: {group_status.group_name} - Progress: {group_status.completed_processes}/{group_status.total_processes} ({group_status.completion_percentage:.1f}%)"
        )
        lines.append("-" * 80)

        # Process status lines
        for proc_status in group_status.processes:
            # Get status character
            if proc_status.is_alive:
                status_char = self._spinner_chars[
                    spinner_index % len(self._spinner_chars)
                ]
            elif proc_status.is_completed:
                if proc_status.return_value == 0:
                    status_char = self._status_chars["done"]
                else:
                    status_char = self._status_chars["failed"]
            else:
                status_char = self._status_chars["pending"]

            # Format fields
            name = proc_status.name[: self.config.max_name_width].ljust(
                self.config.max_name_width
            )

            if proc_status.is_completed:
                status_text = f"DONE({proc_status.return_value or 0})"
            elif proc_status.is_alive:
                status_text = "RUNNING"
            else:
                status_text = "PENDING"

            duration = f"{proc_status.running_time_seconds:.1f}s"
            output = (proc_status.last_output_line or "")[
                : self.config.max_output_width
            ]

            lines.append(
                f"{status_char} {name} | {status_text:>8} | {duration:>8} | {output}"
            )

        return "\\n".join(lines)


class RichStatusDisplay(ProcessStatusDisplay):
    """Rich library-based status display with enhanced formatting."""

    def __init__(
        self, group: "RunningProcessGroup", config: Optional[DisplayConfig] = None
    ):
        if config is None:
            config = DisplayConfig(format_type="rich")
        super().__init__(group, config)

        try:
            from rich.console import Console
            from rich.live import Live
            from rich.progress import Progress
            from rich.table import Table

            self.Progress = Progress
            self.Live = Live
            self.Table = Table
            self.Console = Console
            self._rich_available = True

            self._spinner_chars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"]
            self._status_chars = {
                "running": "⠋",
                "done": "✓",
                "failed": "✗",
                "pending": " ",
            }
        except ImportError:
            logger.warning("Rich not available, falling back to ASCII display")
            self._rich_available = False
            # Fallback to ASCII chars
            self._spinner_chars = ["|", "/", "-", "\\\\"]
            self._status_chars = {
                "running": "|>",
                "done": "OK",
                "failed": "XX",
                "pending": "--",
            }

    def format_status_line(
        self, group_status: "GroupStatus", spinner_index: int
    ) -> str:
        """Format status display using Rich library features."""
        if not self._rich_available:
            # Fallback to ASCII formatting
            return self._format_ascii_fallback(group_status, spinner_index)

        try:
            return self._format_rich_display(group_status, spinner_index)
        except KeyboardInterrupt as ki:
            handle_keyboard_interrupt(ki)
            raise
        except Exception as e:
            logger.warning(f"Rich formatting failed, using ASCII fallback: {e}")
            return self._format_ascii_fallback(group_status, spinner_index)

    def _format_rich_display(
        self, group_status: "GroupStatus", spinner_index: int
    ) -> str:
        """Format using Rich library."""
        table = self.Table(title=f"Process Group: {group_status.group_name}")

        table.add_column("Status", width=8)
        table.add_column("Process", width=self.config.max_name_width)
        table.add_column("State", width=10)
        table.add_column("Duration", width=10)
        table.add_column("Last Output", width=self.config.max_output_width)

        for proc_status in group_status.processes:
            # Get status character
            if proc_status.is_alive:
                status_char = self._spinner_chars[
                    spinner_index % len(self._spinner_chars)
                ]
            elif proc_status.is_completed:
                if proc_status.return_value == 0:
                    status_char = self._status_chars["done"]
                else:
                    status_char = self._status_chars["failed"]
            else:
                status_char = self._status_chars["pending"]

            # Format fields
            name = proc_status.name[: self.config.max_name_width]

            if proc_status.is_completed:
                status_text = f"DONE({proc_status.return_value or 0})"
                if proc_status.return_value == 0:
                    status_text = f"[green]{status_text}[/green]"
                else:
                    status_text = f"[red]{status_text}[/red]"
            elif proc_status.is_alive:
                status_text = "[yellow]RUNNING[/yellow]"
            else:
                status_text = "[dim]PENDING[/dim]"

            duration = f"{proc_status.running_time_seconds:.1f}s"
            output = (proc_status.last_output_line or "")[
                : self.config.max_output_width
            ]

            table.add_row(status_char, name, status_text, duration, output)

        # Render table to string
        console = self.Console(width=120, force_terminal=False)
        with console.capture() as capture:
            console.print(table)

        return capture.get()

    def _format_ascii_fallback(
        self, group_status: "GroupStatus", spinner_index: int
    ) -> str:
        """Fallback ASCII formatting when Rich fails."""
        lines: list[str] = []

        # Header
        lines.append(
            f"Process Group: {group_status.group_name} - Progress: {group_status.completed_processes}/{group_status.total_processes} ({group_status.completion_percentage:.1f}%)"
        )
        lines.append("-" * 80)

        # Process status lines
        for proc_status in group_status.processes:
            # Get status character
            if proc_status.is_alive:
                status_char = self._spinner_chars[
                    spinner_index % len(self._spinner_chars)
                ]
            elif proc_status.is_completed:
                if proc_status.return_value == 0:
                    status_char = self._status_chars["done"]
                else:
                    status_char = self._status_chars["failed"]
            else:
                status_char = self._status_chars["pending"]

            # Format fields
            name = proc_status.name[: self.config.max_name_width].ljust(
                self.config.max_name_width
            )

            if proc_status.is_completed:
                status_text = f"DONE({proc_status.return_value or 0})"
            elif proc_status.is_alive:
                status_text = "RUNNING"
            else:
                status_text = "PENDING"

            duration = f"{proc_status.running_time_seconds:.1f}s"
            output = (proc_status.last_output_line or "")[
                : self.config.max_output_width
            ]

            lines.append(
                f"{status_char} {name} | {status_text:>8} | {duration:>8} | {output}"
            )

        return "\\n".join(lines)


def get_display_format() -> DisplayConfig:
    """Auto-detect best display format for current environment."""
    import sys

    try:
        # Test Rich availability
        import rich  # noqa: F401

        if sys.stdout.encoding and sys.stdout.encoding.lower() in ["utf-8", "utf8"]:
            return DisplayConfig(format_type="rich")
    except ImportError:
        pass

    # Fallback to ASCII
    return DisplayConfig(format_type="ascii")


def create_status_display(
    group: "RunningProcessGroup", display_type: str = "auto"
) -> ProcessStatusDisplay:
    """Factory function to create best available display."""

    if display_type == "auto":
        config = get_display_format()
        display_type = config.format_type

    if display_type == "rich":
        try:
            return RichStatusDisplay(group)
        except KeyboardInterrupt as ki:
            handle_keyboard_interrupt(ki)
            raise
        except Exception as e:
            logger.warning(f"Rich display creation failed, falling back to ASCII: {e}")
            return ASCIIStatusDisplay(group)

    # Default to ASCII
    return ASCIIStatusDisplay(group)


def display_process_status(
    group: "RunningProcessGroup",
    display_type: str = "auto",
    update_interval: float = 0.1,
) -> threading.Thread:
    """Convenience function to start real-time process status display."""

    display = create_status_display(group, display_type)
    display.config.update_interval = update_interval

    return display.start_display()
