"""Output formatting for CLI commands.

Provides abstraction for rendering output in both table and JSON formats.
"""

from __future__ import annotations

import json
import sys
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

import typer
from rich import box
from rich.table import Table

from .styles import PRIMARY_TEXT_STYLE, SECONDARY_TEXT_STYLE

if TYPE_CHECKING:
    from rich.console import Console


@dataclass
class OutputFormatter:
    """Handles output formatting based on mode (table vs JSON).

    Attributes:
        console: Rich console for table output.
        json_mode: If True, output JSON instead of tables.
    """

    console: Console
    json_mode: bool = False

    def print_table(
        self,
        *,
        title: str | None = None,
        columns: list[dict[str, Any]],
        rows: list[list[str]],
        json_data: list[dict[str, Any]] | None = None,
    ) -> None:
        """Print data as either a table or JSON.

        Args:
            title: Table title (ignored in JSON mode).
            columns: List of column definitions with keys:
                - name: Column header text
                - style: Optional Rich style (default: PRIMARY_TEXT_STYLE)
                - justify: Optional justification ("left", "right", "center")
                - overflow: Optional overflow mode ("fold", "crop", "ellipsis")
            rows: List of row data (list of strings per row).
            json_data: Data to output in JSON mode. If None, uses rows as-is.
        """
        if self.json_mode:
            output = json_data if json_data is not None else rows
            typer.echo(json.dumps(output, indent=2, default=str))
            return

        table = Table(
            title=title,
            box=box.SIMPLE_HEAVY,
            border_style=SECONDARY_TEXT_STYLE,
            header_style=SECONDARY_TEXT_STYLE,
        )

        for col in columns:
            table.add_column(
                col["name"],
                style=col.get("style", PRIMARY_TEXT_STYLE),
                justify=col.get("justify", "left"),
                overflow=col.get("overflow"),
            )

        for row in rows:
            table.add_row(*row)

        self.console.print(table)

    def print_json(self, data: Any) -> None:
        """Print data as JSON.

        Args:
            data: Any JSON-serializable data.
        """
        typer.echo(json.dumps(data, indent=2, default=str))

    def print_message(self, message: str, style: str | None = None) -> None:
        """Print a message (styled in table mode, plain in JSON mode).

        In JSON mode, messages are suppressed to keep output machine-parseable.

        Args:
            message: The message to print.
            style: Optional Rich style string (e.g., "[green]", "[yellow]").
        """
        if self.json_mode:
            # Suppress messages in JSON mode for clean machine output
            return
        if style:
            # Extract style name from brackets (e.g., "[green]" -> "green")
            style_name = style.strip("[]")
            self.console.print(f"[{style_name}]{message}[/{style_name}]")
        else:
            self.console.print(message)

    def print_success(self, message: str) -> None:
        """Print a success message (suppressed in JSON mode)."""
        self.print_message(message, style="[green]")

    def print_warning(self, message: str) -> None:
        """Print a warning message (suppressed in JSON mode)."""
        self.print_message(message, style="[yellow]")

    def print_error(self, message: str) -> None:
        """Print an error message.

        Unlike other messages, errors are printed to stderr even in JSON mode.
        """
        if self.json_mode:
            # In JSON mode, errors go to stderr
            sys.stderr.write(f"Error: {message}\n")
        else:
            self.console.print(f"[red]Error:[/red] {message}")


@dataclass
class JSONError:
    """Structured error response for JSON output.

    Schema based on issue #260 requirements:
    - Required: error (code), message (human-readable)
    - Optional: hint, request_id, status_code (integer), context

    Note: response_body is intentionally excluded for security.

    Attributes:
        error: Error type/code (e.g., "not_found", "authentication_required").
        message: Human-readable error message.
        hint: Optional actionable hint for resolution.
        request_id: Optional backend request ID for debugging.
        status_code: Optional HTTP status code (integer type).
        context: Optional context dict with additional error details.
    """

    error: str
    message: str
    hint: str | None = None
    request_id: str | None = None
    status_code: int | None = None
    context: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON serialization.

        Only includes non-None optional fields to keep output clean.
        """
        result: dict[str, Any] = {
            "error": self.error,
            "message": self.message,
        }
        if self.hint is not None:
            result["hint"] = self.hint
        if self.request_id is not None:
            result["request_id"] = self.request_id
        if self.status_code is not None:
            result["status_code"] = self.status_code
        if self.context:
            result["context"] = self.context
        return result

    def to_json(self) -> str:
        """Convert to JSON string."""
        return json.dumps(self.to_dict(), indent=2, default=str)


# Error code constants matching issue #260 specification
ERROR_NOT_FOUND = "not_found"
ERROR_AUTHENTICATION_ERROR = "authentication_error"
ERROR_VALIDATION_ERROR = "validation_error"
ERROR_CONFLICT_ERROR = "conflict_error"
ERROR_PRECONDITION_FAILED = "precondition_failed"
ERROR_CONFIGURATION_ERROR = "configuration_error"
ERROR_CLI_ERROR = "cli_error"
ERROR_CONFIG_ERROR = "config_error"
ERROR_KEYRING_ERROR = "keyring_error"
ERROR_BACKEND_ERROR = "backend_error"
ERROR_USAGE_ERROR = "usage_error"
ERROR_CLICK_ERROR = "click_error"
ERROR_CONNECTION_ERROR = "connection_error"
ERROR_TIMEOUT_ERROR = "timeout_error"
ERROR_HTTP_ERROR = "http_error"
ERROR_ABORTED = "aborted"
ERROR_UNEXPECTED_ERROR = "unexpected_error"


def format_json_error(
    *,
    error: str,
    message: str,
    hint: str | None = None,
    request_id: str | None = None,
    status_code: int | None = None,
    context: dict[str, Any] | None = None,
    # Legacy parameters for backwards compatibility
    available_projects: list[str] | None = None,
    similar_items: list[str] | None = None,
    suggested_key: str | None = None,
) -> JSONError:
    """Create a structured JSON error with optional context.

    Args:
        error: Error type/code (e.g., "not_found", "authentication_required").
        message: Human-readable error message.
        hint: Optional actionable hint for resolution.
        request_id: Optional backend request ID for debugging.
        status_code: Optional HTTP status code (integer).
        context: Optional context dict with additional error details.
        available_projects: List of available projects (legacy, moved to context).
        similar_items: List of similar item keys (legacy, moved to context).
        suggested_key: Suggested alternative key (legacy, moved to context).

    Returns:
        JSONError instance with structured error data.
    """
    # Build context dict, merging explicit context with legacy parameters
    merged_context: dict[str, Any] = context.copy() if context else {}
    if available_projects is not None:
        merged_context["available_projects"] = available_projects
    if similar_items is not None:
        merged_context["similar_items"] = similar_items
    if suggested_key is not None:
        merged_context["suggested_key"] = suggested_key

    return JSONError(
        error=error,
        message=message,
        hint=hint,
        request_id=request_id,
        status_code=status_code,
        context=merged_context,
    )


def print_json_error(error: JSONError) -> None:
    """Print a JSON error to stdout and flush.

    JSON errors go to stdout (exit code indicates failure), matching
    the behavior of gh CLI and kubectl.

    Args:
        error: The JSONError to print.
    """
    sys.stdout.write(error.to_json())
    sys.stdout.write("\n")
    sys.stdout.flush()
