"""Rich terminal reporter for validation, discovery, and suggestion results."""

from __future__ import annotations

from rich import box
from rich.columns import Columns
from rich.console import Console
from rich.panel import Panel
from rich.syntax import Syntax
from rich.table import Table
from rich.text import Text

from sage_evaluator.models import (
    BenchmarkReport,
    CompareResult,
    DeployedModel,
    GeneratedTool,
    ModelPricing,
    PricingSource,
    Severity,
    Suggestion,
    SuggestionCategory,
    SuggestionReport,
    ValidationResult,
)

# ── Module-level console (can be replaced in tests) ──────────────────────────
console = Console()

# ── Severity styling map ──────────────────────────────────────────────────────
_SEVERITY_STYLE: dict[Severity, tuple[str, str]] = {
    Severity.ERROR: ("bold red", "ERROR"),
    Severity.WARNING: ("bold yellow", "WARN"),
    Severity.INFO: ("bold cyan", "INFO"),
}


def print_validation_results(results: list[ValidationResult]) -> None:
    """Print a rich table summarising *results* to the terminal.

    Each row shows the file path, overall pass/fail status, and a
    comma-separated summary of error / warning / info counts.  A detail
    section below the table lists every individual issue.

    Args:
        results: Sequence of :class:`~sage_evaluator.models.ValidationResult`
            objects produced by :class:`~sage_evaluator.validation.AgentValidator`.
    """
    if not results:
        console.print("[dim]No files to validate.[/dim]")
        return

    # ── Summary table ─────────────────────────────────────────────────────
    table = Table(
        title="Validation Results",
        show_header=True,
        header_style="bold white",
        expand=False,
        box=None,
        padding=(0, 1),
    )
    table.add_column("File", style="white", no_wrap=False, ratio=3)
    table.add_column("Status", justify="center", no_wrap=True)
    table.add_column("Errors", justify="right", no_wrap=True)
    table.add_column("Warnings", justify="right", no_wrap=True)
    table.add_column("Info", justify="right", no_wrap=True)

    for result in results:
        status_text = (
            Text("PASS", style="bold green") if result.valid else Text("FAIL", style="bold red")
        )
        error_count = len(result.errors)
        warning_count = len(result.warnings)
        info_count = sum(1 for i in result.issues if i.severity == Severity.INFO)

        table.add_row(
            result.path,
            status_text,
            _coloured_count(error_count, "bold red"),
            _coloured_count(warning_count, "bold yellow"),
            _coloured_count(info_count, "bold cyan"),
        )

    console.print(table)

    # ── Per-file issue detail ─────────────────────────────────────────────
    for result in results:
        if not result.issues:
            continue

        console.print(f"\n[bold white]{result.path}[/bold white]")
        for issue in result.issues:
            style, label = _SEVERITY_STYLE.get(issue.severity, ("white", str(issue.severity)))
            prefix = Text(f"  [{label}]", style=style)
            console.print(prefix, issue.message)

    # ── Final verdict ─────────────────────────────────────────────────────
    total = len(results)
    passed = sum(1 for r in results if r.valid)
    failed = total - passed

    console.print()
    if failed:
        console.print(f"[bold red]{failed} of {total} file(s) failed validation.[/bold red]")
    else:
        console.print(f"[bold green]All {total} file(s) passed validation.[/bold green]")


# ── Discovered models ─────────────────────────────────────────────────────────

# Colour coding for pricing source labels
_SOURCE_STYLES: dict[PricingSource, str] = {
    PricingSource.LITELLM: "green",
    PricingSource.FALLBACK: "yellow",
    PricingSource.UNKNOWN: "dim",
}


def print_discovered_models(
    models: list[DeployedModel],
    pricing: dict[str, ModelPricing] | None = None,
) -> None:
    """Print a Rich table of discovered Azure AI Foundry models.

    Args:
        models: List of models returned by
            :class:`~sage_evaluator.discovery.azure_models.AzureModelDiscovery`.
        pricing: Optional mapping of model ``litellm_id`` or ``name`` to
            :class:`~sage_evaluator.models.ModelPricing`.  When provided,
            input cost, output cost, and pricing source columns are added.

    Example::

        models = [DeployedModel(name="gpt-4o", litellm_id="azure_ai/gpt-4o")]
        print_discovered_models(models)
    """
    show_pricing = pricing is not None

    table = Table(
        title="Discovered Azure AI Models",
        show_header=True,
        header_style="bold magenta",
        border_style="bright_black",
        expand=False,
    )

    table.add_column("Name", style="cyan", no_wrap=True)
    table.add_column("Version", style="white")
    table.add_column("LiteLLM ID", style="blue")
    table.add_column("Capabilities", style="white")

    if show_pricing:
        table.add_column("Input $/1M", justify="right")
        table.add_column("Output $/1M", justify="right")
        table.add_column("Source", justify="center")

    if not models:
        console.print("[yellow]No models discovered.[/yellow]")
        return

    for model in models:
        caps_str = ", ".join(model.capabilities) if model.capabilities else "-"
        row: list[str | Text] = [
            model.name,
            model.version or "-",
            model.litellm_id or "-",
            caps_str,
        ]

        if show_pricing:
            assert pricing is not None
            p = pricing.get(model.litellm_id) or pricing.get(model.name)
            if p:
                input_cost = f"{p.input_cost_per_token * 1_000_000:.4f}"
                output_cost = f"{p.output_cost_per_token * 1_000_000:.4f}"
                source_style = _SOURCE_STYLES.get(p.source, "white")
                source_text = Text(p.source.value, style=source_style)
            else:
                input_cost = "-"
                output_cost = "-"
                source_text = Text("unknown", style="dim")

            row += [input_cost, output_cost, source_text]

        table.add_row(*row)

    console.print(table)
    console.print(f"[dim]Total: {len(models)} model(s)[/dim]")


# ── Suggestion output ─────────────────────────────────────────────────────────

# Map category enum values to display labels and Rich style strings.
_CATEGORY_META: dict[SuggestionCategory, tuple[str, str]] = {
    SuggestionCategory.PROMPT_IMPROVEMENT: ("Prompt Improvement", "cyan"),
    SuggestionCategory.TOOL_EXTRACTION: ("Tool Extraction", "yellow"),
    SuggestionCategory.GUARDRAIL: ("Guardrail", "red"),
    SuggestionCategory.ARCHITECTURE: ("Architecture", "green"),
}

_IMPACT_STYLE: dict[str, str] = {
    "high": "bold red",
    "medium": "bold yellow",
    "low": "dim green",
}


def _impact_text(impact: str) -> Text:
    """Return a styled Rich Text for an impact label."""
    style = _IMPACT_STYLE.get(impact.lower(), "")
    return Text(impact.upper() if impact else "?", style=style)


def _category_label(category: SuggestionCategory) -> Text:
    """Return a styled Rich Text for a category label."""
    label, style = _CATEGORY_META.get(category, (category.value, "white"))
    return Text(label, style=style)


def print_suggestion_report(report: SuggestionReport) -> None:
    """Render a SuggestionReport to the terminal using Rich.

    Displays:
    - A summary header with config path and analyzer model
    - Suggestions grouped by category, each with a before/after diff
    - Generated tool and guardrail source code in syntax-highlighted panels

    Args:
        report: The :class:`~sage_evaluator.models.SuggestionReport` to display.
    """
    console.rule("[bold blue]Sage Suggestion Report[/bold blue]")
    console.print(f"[bold]Config:[/bold] {report.config_path}")
    console.print(f"[bold]Analyzer model:[/bold] {report.analyzer_model}")
    console.print(f"[bold]Suggestions found:[/bold] {len(report.suggestions)}")
    console.print(f"[bold]Generated tools:[/bold] {len(report.generated_tools)}")
    console.print()

    if not report.suggestions:
        console.print("[dim]No suggestions produced.[/dim]")
    else:
        _print_suggestions_by_category(report.suggestions)

    if report.generated_tools:
        _print_generated_tools(report.generated_tools)

    console.rule()


def _print_suggestions_by_category(suggestions: list[Suggestion]) -> None:
    """Render all suggestions grouped by category."""
    groups: dict[SuggestionCategory, list[Suggestion]] = {}
    for suggestion in suggestions:
        groups.setdefault(suggestion.category, []).append(suggestion)

    for category, group in groups.items():
        label, style = _CATEGORY_META.get(category, (category.value, "white"))
        console.rule(f"[{style}]{label}[/{style}]")
        for idx, s in enumerate(group, start=1):
            _print_single_suggestion(idx, s)
        console.print()


def _print_single_suggestion(idx: int, suggestion: Suggestion) -> None:
    """Render a single suggestion with its before/after diff."""
    header = Text()
    header.append(f"  {idx}. ", style="dim")
    header.append(suggestion.title, style="bold white")
    header.append("  ")
    header.append(_impact_text(suggestion.impact))

    console.print(header)
    console.print(f"     [dim]{suggestion.description}[/dim]")

    if suggestion.before or suggestion.after:
        _print_before_after(suggestion.before, suggestion.after)

    console.print()


def _print_before_after(before: str, after: str) -> None:
    """Render before/after content in coloured panels, side-by-side when both are present."""
    if before and after:
        before_panel = Panel(
            Text(before, style="red"),
            title="[red]Before[/red]",
            border_style="red",
            expand=True,
        )
        after_panel = Panel(
            Text(after, style="green"),
            title="[green]After[/green]",
            border_style="green",
            expand=True,
        )
        console.print(Columns([before_panel, after_panel], padding=(0, 1)))
    elif before:
        console.print(
            Panel(Text(before, style="red"), title="[red]Before[/red]", border_style="red")
        )
    elif after:
        console.print(
            Panel(Text(after, style="green"), title="[green]After[/green]", border_style="green")
        )


def _print_generated_tools(tools: list[GeneratedTool]) -> None:
    """Render generated tools and guardrails in separate sections."""
    extracted = [t for t in tools if t.category == "extracted"]
    guardrails = [t for t in tools if t.category == "guardrail"]

    if extracted:
        console.rule("[yellow]Generated Tools[/yellow]")
        for tool in extracted:
            _print_tool_panel(tool, style="yellow")
        console.print()

    if guardrails:
        console.rule("[red]Generated Guardrails[/red]")
        for tool in guardrails:
            _print_tool_panel(tool, style="red")
        console.print()


def _print_tool_panel(tool: GeneratedTool, style: str = "white") -> None:
    """Render a single generated tool in a Rich Panel with Python syntax highlighting."""
    syntax = Syntax(tool.source_code, "python", theme="monokai", line_numbers=True)
    panel = Panel(
        syntax,
        title=f"[{style}]{tool.name}[/{style}]",
        subtitle=f"[dim]{tool.description[:80]}[/dim]" if tool.description else "",
        border_style=style,
        box=box.ROUNDED,
    )
    console.print(panel)
    console.print()


def print_suggestions_table(suggestions: list[Suggestion]) -> None:
    """Render a compact Rich Table summary of all suggestions.

    This is a lighter alternative to :func:`print_suggestion_report` for
    embedding suggestion summaries inside larger outputs.

    Args:
        suggestions: List of :class:`~sage_evaluator.models.Suggestion` objects.
    """
    table = Table(
        title="Suggestions",
        box=box.ROUNDED,
        show_header=True,
        header_style="bold blue",
        expand=False,
    )
    table.add_column("#", style="dim", width=3, justify="right")
    table.add_column("Category", min_width=18)
    table.add_column("Impact", width=8, justify="center")
    table.add_column("Title", min_width=30)

    for idx, s in enumerate(suggestions, start=1):
        table.add_row(
            str(idx),
            _category_label(s.category),
            _impact_text(s.impact),
            s.title,
        )

    console.print(table)


# ── Benchmark output ─────────────────────────────────────────────────────────


def print_benchmark_report(report: BenchmarkReport) -> None:
    """Render a BenchmarkReport to the terminal using Rich."""

    console.rule("[bold blue]Sage Benchmark Report[/bold blue]")
    console.print(f"[bold]Intent:[/bold] {report.intent.raw_intent}")
    elaborated = report.intent.elaborated_intent
    if elaborated and elaborated != report.intent.raw_intent:
        console.print(f"[bold]Elaborated:[/bold] {elaborated}")
    if report.rubric:
        console.print(f"[bold]Rubric:[/bold] {report.rubric.name}")
    console.print(f"[bold]Evaluator:[/bold] {report.evaluator_model}")
    console.print()

    table = Table(
        title="Model Benchmark Results (ranked by composite score)",
        show_header=True,
        header_style="bold magenta",
        border_style="bright_black",
        expand=False,
    )

    table.add_column("Rank", style="dim", width=4, justify="right")
    table.add_column("Model", style="cyan", no_wrap=True)
    table.add_column("Quality", justify="right")
    table.add_column("Tokens (avg)", justify="right")
    table.add_column("Latency (avg)", justify="right")
    table.add_column("Cost ($)", justify="right")
    table.add_column("Composite", justify="right", style="bold")

    for rank, result in enumerate(report.ranked_results, 1):
        quality = f"{result.quality.overall_score:.2f}/5" if result.quality else "-"
        tokens = f"{result.stats.mean_total_tokens:.0f}"
        latency = f"{result.stats.mean_latency_ms:.0f}ms"
        cost = f"${result.estimated_cost:.6f}" if result.estimated_cost > 0 else "-"
        composite = f"{result.composite_score:.4f}"
        table.add_row(str(rank), result.model, quality, tokens, latency, cost, composite)

    console.print(table)

    for result in report.ranked_results:
        if not result.quality or not result.quality.dimension_scores:
            continue
        console.print(f"\n[bold]{result.model}[/bold] - Quality Dimensions:")
        for ds in result.quality.dimension_scores:
            filled = int((ds.score / 5.0) * 20)
            bar = "[green]" + "#" * filled + "[/green][dim]" + "-" * (20 - filled) + "[/dim]"
            console.print(f"  {ds.dimension:<20} {bar} {ds.score:.1f}/5  [dim]{ds.reasoning}[/dim]")

    console.rule()


# ── Compare output ───────────────────────────────────────────────────────────


def print_compare_report(compare: CompareResult) -> None:
    """Render a CompareResult showing deltas between two configs."""

    console.rule("[bold blue]Sage Comparison Report[/bold blue]")
    console.print(f"[bold]Config A:[/bold] {compare.config_a_path}")
    console.print(f"[bold]Config B:[/bold] {compare.config_b_path}")
    console.print()

    table = Table(
        title="Side-by-Side Comparison",
        show_header=True,
        header_style="bold magenta",
        border_style="bright_black",
        expand=False,
    )

    table.add_column("Model", style="cyan", no_wrap=True)
    table.add_column("Metric", style="white")
    table.add_column("Config A", justify="right")
    table.add_column("Config B", justify="right")
    table.add_column("Delta", justify="right")

    results_a = {r.model: r for r in compare.report_a.results}
    results_b = {r.model: r for r in compare.report_b.results}
    all_models = list(dict.fromkeys(list(results_a.keys()) + list(results_b.keys())))

    for model in all_models:
        ra = results_a.get(model)
        rb = results_b.get(model)
        rows = _build_compare_rows(ra, rb)
        for i, (metric, val_a, val_b, delta) in enumerate(rows):
            table.add_row(model if i == 0 else "", metric, val_a, val_b, delta)
        table.add_section()

    console.print(table)
    console.rule()


def _build_compare_rows(ra, rb) -> list[tuple[str, str, str, str]]:
    """Build delta comparison rows for a single model."""
    rows = []

    qa = ra.quality.overall_score if ra and ra.quality else 0.0
    qb = rb.quality.overall_score if rb and rb.quality else 0.0
    rows.append(("Quality", f"{qa:.2f}", f"{qb:.2f}", _fmt_delta(qb - qa)))

    ta = ra.stats.mean_total_tokens if ra else 0.0
    tb = rb.stats.mean_total_tokens if rb else 0.0
    rows.append(("Tokens (avg)", f"{ta:.0f}", f"{tb:.0f}", _fmt_delta(tb - ta, lower_better=True)))

    la = ra.stats.mean_latency_ms if ra else 0.0
    lb = rb.stats.mean_latency_ms if rb else 0.0
    rows.append(("Latency (ms)", f"{la:.0f}", f"{lb:.0f}", _fmt_delta(lb - la, lower_better=True)))

    ca = ra.estimated_cost if ra else 0.0
    cb = rb.estimated_cost if rb else 0.0
    rows.append(("Cost ($)", f"{ca:.6f}", f"{cb:.6f}", _fmt_delta(cb - ca, lower_better=True)))

    sa = ra.composite_score if ra else 0.0
    sb = rb.composite_score if rb else 0.0
    rows.append(("Composite", f"{sa:.4f}", f"{sb:.4f}", _fmt_delta(sb - sa)))

    return rows


def _fmt_delta(delta: float, lower_better: bool = False) -> str:
    """Format a delta value with color indicating improvement/regression."""
    if abs(delta) < 0.001:
        return "[dim]~0[/dim]"
    sign = "+" if delta > 0 else ""
    is_good = (delta < 0) if lower_better else (delta > 0)
    color = "green" if is_good else "red"
    if abs(delta) < 1:
        return f"[{color}]{sign}{delta:.4f}[/{color}]"
    return f"[{color}]{sign}{delta:.1f}[/{color}]"


# ── Private helpers ───────────────────────────────────────────────────────────


def _coloured_count(count: int, style: str) -> Text:
    """Return a styled count, dimmed when zero."""
    if count == 0:
        return Text("0", style="dim")
    return Text(str(count), style=style)
