"""Machine-readable output formatters for the Ilum CLI."""

from __future__ import annotations

import csv
import json
from dataclasses import dataclass, field
from enum import StrEnum
from io import StringIO
from typing import TYPE_CHECKING, Any

from ruamel.yaml import YAML

if TYPE_CHECKING:
    from ilum.cli.output import IlumConsole


class OutputFormat(StrEnum):
    """Supported output formats for CLI commands."""

    TABLE = "table"
    JSON = "json"
    YAML = "yaml"
    CSV = "csv"


@dataclass
class CommandResult:
    """Machine-readable result of a CLI command."""

    data: dict[str, Any] | list[Any]
    summary: str
    exit_code: int = 0
    columns: list[str] | None = field(default=None)


def _apply_field_selector(
    data: dict[str, Any] | list[Any], fields: list[str]
) -> dict[str, Any] | list[Any]:
    """Filter *data* to only include the requested *fields*.

    For a list of dicts, each dict is filtered to the requested keys.
    For a single dict, top-level keys are filtered.
    """
    if isinstance(data, list):
        return [
            {k: item[k] for k in fields if k in item} for item in data if isinstance(item, dict)
        ]
    return {k: data[k] for k in fields if k in data}


class ResultFormatter:
    """Dispatches CommandResult rendering to the appropriate format."""

    def format(
        self,
        result: CommandResult,
        fmt: OutputFormat,
        console: IlumConsole,
        *,
        no_headers: bool = False,
        field_selector: list[str] | None = None,
    ) -> None:
        """Render *result* according to *fmt*."""
        data = result.data
        if field_selector:
            data = _apply_field_selector(data, field_selector)

        if fmt == OutputFormat.JSON:
            self._format_json(data, console)
        elif fmt == OutputFormat.YAML:
            self._format_yaml(data, console)
        elif fmt == OutputFormat.CSV:
            self._format_csv(data, console, columns=result.columns, no_headers=no_headers)
        else:
            self._format_table(result, console)

    @staticmethod
    def _format_json(data: dict[str, Any] | list[Any], console: IlumConsole) -> None:
        console._console.print_json(json.dumps(data, indent=2, default=str))

    @staticmethod
    def _format_yaml(data: dict[str, Any] | list[Any], console: IlumConsole) -> None:
        yaml = YAML()
        yaml.default_flow_style = False
        buf = StringIO()
        yaml.dump(data, buf)
        console._console.print(buf.getvalue(), highlight=False)

    @staticmethod
    def _format_csv(
        data: dict[str, Any] | list[Any],
        console: IlumConsole,
        *,
        columns: list[str] | None = None,
        no_headers: bool = False,
    ) -> None:
        """Render *data* as CSV to the console.

        When *data* is a list of dicts, each dict becomes a row.
        When *data* is a single dict, it is rendered as a single row.
        """
        buf = StringIO()
        writer = csv.writer(buf)

        if isinstance(data, list):
            if not data:
                return
            # Derive columns from first dict item, or use provided columns
            if columns:
                headers = columns
            elif isinstance(data[0], dict):
                headers = list(data[0].keys())
            else:
                # Flat list — single column
                if not no_headers:
                    writer.writerow(["value"])
                for item in data:
                    writer.writerow([item])
                console._console.print(buf.getvalue().rstrip(), highlight=False)
                return

            if not no_headers:
                writer.writerow(headers)
            for item in data:
                if isinstance(item, dict):
                    writer.writerow([item.get(h, "") for h in headers])
                else:
                    writer.writerow([item])
        else:
            headers = columns or list(data.keys())
            if not no_headers:
                writer.writerow(headers)
            writer.writerow([data.get(h, "") for h in headers])

        console._console.print(buf.getvalue().rstrip(), highlight=False)

    @staticmethod
    def _format_table(result: CommandResult, console: IlumConsole) -> None:
        """Table format delegates to the caller — just print the summary."""
        console.info(result.summary)
