"""Output formatter implementations for valn8n."""

import csv
import json
import sys
from pathlib import Path
from typing import IO

from valn8n_lib.output.output import format_diagnostic_text
from valn8n_lib.output.protocol import Formatter
from valn8n_lib.types import Diagnostic, Severity


class RichFormatter:
    """Rich table output with colored severity and summary."""

    def render(
        self,
        results: dict[Path, list[Diagnostic]],
        *,
        quiet: bool = False,
        stream: IO[str] | None = None,
    ) -> None:
        """Render diagnostics as a rich table with colored severity."""
        if stream is None:
            stream = sys.stdout

        from rich.console import Console  # noqa: PLC0415
        from rich.table import Table  # noqa: PLC0415

        console = Console(file=stream)
        all_diags = [d for diags in results.values() for d in diags]

        if not quiet and all_diags:
            table = Table(show_header=True)
            table.add_column("File")
            table.add_column("Code")
            table.add_column("Severity")
            table.add_column("Message")
            table.add_column("Node")
            table.add_column("Field")
            table.add_column("Line")
            for d in all_diags:
                if d.code.severity == Severity.ERROR:
                    severity_style = "red"
                elif d.code.severity == Severity.HINT:
                    severity_style = "cyan"
                else:
                    severity_style = "yellow"
                table.add_row(
                    d.file,
                    d.code.code,
                    f"[{severity_style}]{d.code.severity.value}[/{severity_style}]",
                    d.message,
                    d.location.node_name if d.location else "",
                    d.location.field_path if d.location else "",
                    str(d.line) if d.line is not None else "",
                )
            console.print(table)

        errors = sum(1 for d in all_diags if d.code.severity == Severity.ERROR)
        warnings = sum(1 for d in all_diags if d.code.severity == Severity.WARNING)
        hints = sum(1 for d in all_diags if d.code.severity == Severity.HINT)
        total = len(all_diags)

        if total > 0:
            console.print(
                f"Found {total} issue{'s' if total != 1 else ''} "
                f"({errors} error{'s' if errors != 1 else ''}, "
                f"{warnings} warning{'s' if warnings != 1 else ''}, "
                f"{hints} hint{'s' if hints != 1 else ''})"
            )
        elif not quiet:
            console.print("All checks passed!")


class TextFormatter:
    """Plain text output."""

    def render(
        self,
        results: dict[Path, list[Diagnostic]],
        *,
        quiet: bool = False,
        stream: IO[str] | None = None,
    ) -> None:
        """Render diagnostics as plain text."""
        if stream is None:
            stream = sys.stdout

        all_diags = [d for diags in results.values() for d in diags]

        if not quiet:
            for d in all_diags:
                print(format_diagnostic_text(d), file=stream)

        errors = sum(1 for d in all_diags if d.code.severity == Severity.ERROR)
        warnings = sum(1 for d in all_diags if d.code.severity == Severity.WARNING)
        hints = sum(1 for d in all_diags if d.code.severity == Severity.HINT)
        total = len(all_diags)

        if total > 0:
            print(
                f"Found {total} issue{'s' if total != 1 else ''} "
                f"({errors} error{'s' if errors != 1 else ''}, "
                f"{warnings} warning{'s' if warnings != 1 else ''}, "
                f"{hints} hint{'s' if hints != 1 else ''})",
                file=stream,
            )
        elif not quiet:
            print("All checks passed!", file=stream)


class LstFormatter:
    """One diagnostic code per line, matching .lst fixture format."""

    def render(
        self,
        results: dict[Path, list[Diagnostic]],
        *,
        quiet: bool = False,
        stream: IO[str] | None = None,
    ) -> None:
        """Render diagnostics as one code per line."""
        if stream is None:
            stream = sys.stdout

        for diags in results.values():
            for d in diags:
                print(d.code.code, file=stream)


class JsonFormatter:
    """JSON array output."""

    def render(
        self,
        results: dict[Path, list[Diagnostic]],
        *,
        quiet: bool = False,
        stream: IO[str] | None = None,
    ) -> None:
        """Render diagnostics as a JSON array."""
        if stream is None:
            stream = sys.stdout

        items: list[dict[str, object]] = []
        for diags in results.values():
            for d in diags:
                item: dict[str, object] = {
                    "file": d.file,
                    "code": d.code.code,
                    "severity": d.code.severity.value,
                    "message": d.message,
                }
                if d.location:
                    item["node_name"] = d.location.node_name
                    item["field_path"] = d.location.field_path
                if d.line is not None:
                    item["line"] = d.line
                items.append(item)
        print(json.dumps(items, indent=2), file=stream)


class CsvFormatter:
    """CSV output with headers."""

    def render(
        self,
        results: dict[Path, list[Diagnostic]],
        *,
        quiet: bool = False,
        stream: IO[str] | None = None,
    ) -> None:
        """Render diagnostics as CSV with headers."""
        out = stream if stream is not None else sys.stdout

        writer = csv.writer(out)
        writer.writerow(["file", "code", "severity", "message", "node_name", "field_path", "line"])
        for diags in results.values():
            for d in diags:
                writer.writerow(
                    [
                        d.file,
                        d.code.code,
                        d.code.severity.value,
                        d.message,
                        d.location.node_name if d.location else "",
                        d.location.field_path if d.location else "",
                        str(d.line) if d.line is not None else "",
                    ]
                )


def get_formatter(name: str) -> Formatter:
    """Return a formatter instance by name."""
    formatters: dict[str, Formatter] = {
        "rich": RichFormatter(),
        "text": TextFormatter(),
        "lst": LstFormatter(),
        "json": JsonFormatter(),
        "csv": CsvFormatter(),
    }
    return formatters[name]
