"""Unified output layer for Hikaflow CLI.

Provides consistent output formatting with support for:
- Rich terminal output (tables, panels, colors)
- JSON machine-parseable output (--json flag)
- Plain text output (NO_COLOR env var or --no-color flag)
- Progress indicators (spinners and bars)
"""

from __future__ import annotations

import json as json_module
import os
from contextlib import contextmanager
from typing import Any, Dict, List, Optional

from rich.console import Console
from rich.panel import Panel
from rich.progress import (
    BarColumn,
    Progress,
    SpinnerColumn,
    TextColumn,
    TimeRemainingColumn,
)
from rich.table import Table


class OutputFormatter:
    """Unified output formatter for CLI commands.

    Supports three modes:
    - Rich (default): Colorized tables, panels, and progress indicators
    - JSON (--json): Machine-parseable JSON output
    - Plain (NO_COLOR): Plain text without ANSI escape codes
    """

    def __init__(self, json_mode: bool = False, no_color: bool = False):
        self.json_mode = json_mode
        self._json_buffer: List[Dict[str, Any]] = []

        if json_mode:
            self.console = Console(quiet=True)
        elif no_color or os.environ.get("NO_COLOR"):
            self.console = Console(no_color=True, highlight=False)
        else:
            self.console = Console()

    def success(self, message: str) -> None:
        """Print a success message."""
        if self.json_mode:
            self._json_buffer.append({"type": "success", "message": message})
        else:
            self.console.print(f"[green]{message}[/]")

    def error(self, message: str, detail: Optional[str] = None) -> None:
        """Print an error message with optional remediation detail."""
        if self.json_mode:
            entry: Dict[str, Any] = {"type": "error", "message": message}
            if detail:
                entry["detail"] = detail
            self._json_buffer.append(entry)
            # Auto-flush: errors are typically followed by raise typer.Exit(1)
            # which would skip flush_json(), losing the error output.
            self.flush_json()
        else:
            self.console.print(f"[red]{message}[/]")
            if detail:
                self.console.print(f"  [dim]{detail}[/]")

    def warning(self, message: str) -> None:
        """Print a warning message."""
        if self.json_mode:
            self._json_buffer.append({"type": "warning", "message": message})
        else:
            self.console.print(f"[yellow]{message}[/]")

    def info(self, message: str) -> None:
        """Print an informational message."""
        if self.json_mode:
            self._json_buffer.append({"type": "info", "message": message})
        else:
            self.console.print(message)

    def dim(self, message: str) -> None:
        """Print a dimmed/secondary message."""
        if self.json_mode:
            pass  # Skip dim messages in JSON mode
        else:
            self.console.print(f"[dim]{message}[/]")

    def table(
        self,
        title: str,
        columns: List[Dict[str, Any]],
        rows: List[List[str]],
    ) -> None:
        """Print a formatted table.

        Args:
            title: Table title
            columns: List of column dicts with 'name' and optional 'style', 'justify'
            rows: List of row data (list of strings)
        """
        if self.json_mode:
            col_names = [c["name"] for c in columns]
            json_rows = [dict(zip(col_names, row)) for row in rows]
            self._json_buffer.append({
                "type": "table",
                "title": title,
                "columns": col_names,
                "rows": json_rows,
            })
        else:
            t = Table(title=title)
            for col in columns:
                t.add_column(
                    col["name"],
                    style=col.get("style"),
                    justify=col.get("justify"),
                )
            for row in rows:
                t.add_row(*row)
            self.console.print(t)

    def panel(self, content: str, title: Optional[str] = None, border_style: str = "green") -> None:
        """Print a bordered panel."""
        if self.json_mode:
            self._json_buffer.append({
                "type": "panel",
                "title": title,
                "content": content,
            })
        else:
            self.console.print(Panel(content, title=title, border_style=border_style))

    def json_out(self, data: Any) -> None:
        """Output raw JSON data (always included in JSON mode)."""
        if self.json_mode:
            self._json_buffer.append({"type": "data", "data": data})
        else:
            self.console.print_json(data=data)

    # Alias for json_out — used in commands that produce structured data
    data = json_out

    def flush_json(self) -> None:
        """Flush buffered JSON output to stdout.

        Call this at the end of a command when in JSON mode.
        """
        if self.json_mode and self._json_buffer:
            # If there's a single data entry, output just the data
            if len(self._json_buffer) == 1 and self._json_buffer[0].get("type") == "data":
                print(json_module.dumps(self._json_buffer[0]["data"], indent=2, default=str))
            else:
                print(json_module.dumps(self._json_buffer, indent=2, default=str))
            self._json_buffer.clear()

    @contextmanager
    def progress_spinner(self, description: str = "Working..."):
        """Context manager for an indeterminate spinner.

        Usage:
            with output.progress_spinner("Uploading...") as status:
                status.update("Uploading file 1...")
                do_work()
        """
        if self.json_mode:
            yield _NoOpStatus()
        else:
            with self.console.status(f"[bold green]{description}") as status:
                yield status

    @contextmanager
    def progress_bar(self, description: str = "Processing", total: Optional[int] = None):
        """Context manager for a progress bar.

        Usage:
            with output.progress_bar("Scanning tests", total=100) as progress:
                for item in items:
                    progress.advance()
        """
        if self.json_mode:
            yield _NoOpProgress()
        else:
            progress = Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                BarColumn(),
                TextColumn("{task.completed}/{task.total}" if total else ""),
                TimeRemainingColumn(),
                console=self.console,
            )
            with progress:
                task_id = progress.add_task(description, total=total)
                yield _ProgressWrapper(progress, task_id)


class _NoOpStatus:
    """No-op status for JSON mode."""

    def update(self, description: str) -> None:
        pass


class _NoOpProgress:
    """No-op progress for JSON mode."""

    def advance(self, amount: int = 1) -> None:
        pass

    def update(self, description: str) -> None:
        pass


class _ProgressWrapper:
    """Wrapper around Rich Progress for simpler API."""

    def __init__(self, progress: Progress, task_id):
        self._progress = progress
        self._task_id = task_id

    def advance(self, amount: int = 1) -> None:
        self._progress.update(self._task_id, advance=amount)

    def update(self, description: str) -> None:
        self._progress.update(self._task_id, description=description)
