from __future__ import annotations

import os
import shlex
import sys
from collections.abc import Sequence
from contextlib import AbstractContextManager
from pathlib import Path
from typing import Any

from .models import (
    Coverage,
    HarnessRun,
    RunStatus,
    Strategy,
    section_confidence,
)

STATUS_GLYPHS = {
    RunStatus.NOT_RUN: "·",
    RunStatus.QUEUED: "○",
    RunStatus.RUNNING: "◉",
    RunStatus.PASSED: "✓",
    RunStatus.FAILED: "✗",
    RunStatus.SKIPPED: "↷",
    RunStatus.ERROR: "!",
    RunStatus.MISSING: "?",
    RunStatus.PLANNED: "—",
    RunStatus.NOT_APPLICABLE: "n/a",
}

STATUS_STYLES = {
    RunStatus.QUEUED: "dim",
    RunStatus.RUNNING: "bold cyan",
    RunStatus.PASSED: "bold green",
    RunStatus.FAILED: "bold red",
    RunStatus.SKIPPED: "yellow",
    RunStatus.ERROR: "bold red",
    RunStatus.MISSING: "magenta",
    RunStatus.PLANNED: "dim",
    RunStatus.NOT_APPLICABLE: "dim",
}


def _format_duration(seconds: float) -> str:
    if seconds < 1:
        return f"{seconds * 1000:.0f}ms"
    if seconds < 60:
        return f"{seconds:.1f}s"
    return f"{int(seconds // 60)}m {seconds % 60:.0f}s"


def _rerun_command(nodeid: str) -> str:
    if nodeid.startswith("unit-suite:"):
        return "uv run python -m tests.rust-python-harness.strategies.unit_tests.runner --plain"
    return f"poetry run pytest {shlex.quote(nodeid)} -q -o consider_namespace_packages=true"


def _summary(run: HarnessRun) -> tuple[int, int, int, int]:
    outcomes: dict[str, RunStatus] = {}
    for result in run.results.values():
        outcomes.update(result.outcomes)
    return (
        list(outcomes.values()).count(RunStatus.PASSED),
        list(outcomes.values()).count(RunStatus.FAILED),
        list(outcomes.values()).count(RunStatus.ERROR),
        list(outcomes.values()).count(RunStatus.SKIPPED),
    )


def _cell_text(run: HarnessRun, strategy_id: str, sdk_function: str, surface: str = "sdk") -> tuple[str, str]:
    key = f"{strategy_id}:{sdk_function}" if surface == "sdk" else f"{strategy_id}:gateway:{sdk_function}"
    result = run.results.get(key)
    if result is None:
        return "", ""
    counts = ""
    if result.total:
        counts = f" {len(result.completed)}/{result.total}"
    coverage = " ◐" if result.case.coverage is Coverage.PARTIAL else ""
    return f"{STATUS_GLYPHS[result.status]}{counts}{coverage}", STATUS_STYLES.get(
        result.status, ""
    )


class RichDashboard(AbstractContextManager["RichDashboard"]):
    def __init__(
        self,
        strategies: Sequence[Strategy],
        confidence_strategies: Sequence[Strategy],
    ) -> None:
        from rich.console import Console
        from rich.live import Live

        self.strategies = strategies
        self.confidence_strategies = confidence_strategies
        self.console = Console()
        self.live: Any = Live(
            console=self.console, refresh_per_second=12, transient=False
        )

    def _table(self, run: HarnessRun) -> Any:
        from rich import box
        from rich.table import Table
        from rich.text import Text

        columns = tuple(dict.fromkeys((case.surface, case.sdk_function) for strategy in self.strategies for case in strategy.cases))
        narrow = self.console.width < 96
        if narrow:
            table = Table(box=box.SIMPLE_HEAVY, expand=True, show_header=False)
            table.add_column("Strategy", ratio=3)
            table.add_column("Results", ratio=5)
            for strategy in self.strategies:
                values = []
                for surface, sdk_function in columns:
                    value, style = _cell_text(run, strategy.id, sdk_function, surface)
                    if value:
                        values.append(
                            Text.assemble((f"{surface}/{sdk_function} ", "dim"), (value, style))
                        )
                table.add_row(strategy.label, Text("  ").join(values))
            return table

        table = Table(box=box.ROUNDED, expand=True, title="Strategy × API")
        table.add_column("Strategy", ratio=3)
        for surface, label in columns:
            table.add_column(label if surface == "sdk" else f"gateway/{label}", justify="center", ratio=1)
        for strategy in self.strategies:
            cells = []
            for surface, sdk_function in columns:
                value, style = _cell_text(run, strategy.id, sdk_function, surface)
                cells.append(Text(value, style=style))
            table.add_row(strategy.label, *cells)
        return table

    def __enter__(self) -> "RichDashboard":
        self.live.__enter__()
        return self

    def __exit__(self, *args: object) -> None:
        self.live.__exit__(*args)

    def update(self, run: HarnessRun) -> None:
        from rich.markup import escape
        from rich.panel import Panel

        active = run.current_nodeid or "Waiting for test events…"
        if len(active) > max(40, self.console.width - 16):
            active = f"…{active[-(self.console.width - 17):]}"
        passed, failed, errors, skipped = _summary(run)
        progress = (
            f"[bold]{run.completed_tests}/{run.unique_tests}[/bold] tests  "
            f"[green]{passed} passed[/green]  [red]{failed + errors} failed[/red]  "
            f"[yellow]{skipped} skipped[/yellow]  [dim]{_format_duration(run.duration)}[/dim]"
        )
        legend = "✓ pass  ✗ fail  ! error  ↷ skip\n? configured test missing  — planned  ◐ partial coverage"
        self.live.update(
            Panel(
                self._table(run),
                title="⚡ Rust ↔ Python parity lab",
                subtitle=f"{progress}\n[dim]{escape(active)}[/dim]\n{legend}",
                border_style="cyan",
            )
        )

    def finish(self, run: HarnessRun, exit_code: int) -> None:
        self.update(run)
        if run.failures:
            from rich.markup import escape
            from rich.panel import Panel

            for nodeid, detail in run.failures[:5]:
                rerun = _rerun_command(nodeid)
                self.console.print(
                    Panel(
                        f"{escape(detail)}\n\n[bold]Rerun just this test[/bold]\n"
                        f"[cyan]{escape(rerun)}[/cyan]",
                        title=f"✗ {escape(nodeid)}",
                        border_style="red",
                    )
                )
        durations: dict[str, float] = {}
        for result in run.results.values():
            for nodeid, duration in result.durations.items():
                durations[nodeid] = max(duration, durations.get(nodeid, 0.0))
        if durations:
            slow = sorted(durations.items(), key=lambda item: item[1], reverse=True)[:3]
            self.console.print(
                "[bold]Slowest tests[/bold]  "
                + "  •  ".join(
                    f"{Path(nodeid).name} [dim]{_format_duration(duration)}[/dim]"
                    for nodeid, duration in slow
                )
            )
        from rich import box
        from rich.table import Table

        confidence_table = Table(
            title="Port confidence by API", box=box.ROUNDED, expand=True
        )
        confidence_table.add_column("SDK section")
        confidence_table.add_column("Score", justify="right")
        confidence_table.add_column("Confidence")
        confidence_table.add_column("Strategy evidence", ratio=4)
        confidence_styles = {"HIGH": "green", "MEDIUM": "yellow", "LOW": "red"}
        for score in section_confidence(run, self.confidence_strategies):
            confidence_table.add_row(
                score.sdk_function,
                f"{score.verified_strategies}/{score.required_strategies}  {score.percentage}%",
                f"[{confidence_styles[score.level.value]}]{score.level.value}[/]",
                "  ".join(score.details),
            )
        self.console.print(confidence_table)
        self.console.print(
            "[dim]Score = required strategies with passing evidence. "
            "LOC coverage remains a separate report.[/dim]"
        )
        style = "green" if exit_code == 0 else "red"
        self.console.print(
            f"[{style}]Harness finished in {_format_duration(run.duration)} "
            f"(exit {exit_code})[/{style}]"
        )


class PlainDashboard(AbstractContextManager["PlainDashboard"]):
    def __init__(
        self,
        strategies: Sequence[Strategy],
        confidence_strategies: Sequence[Strategy],
    ) -> None:
        self.strategies = strategies
        self.confidence_strategies = confidence_strategies
        self._seen: dict[str, tuple[RunStatus, int]] = {}

    def __enter__(self) -> "PlainDashboard":
        print("Rust <-> Python SDK parity harness", flush=True)
        return self

    def __exit__(self, *args: object) -> None:
        return None

    def update(self, run: HarnessRun) -> None:
        for key, result in run.results.items():
            state = (result.status, len(result.completed))
            if self._seen.get(key) != state:
                self._seen[key] = state
                progress = (
                    f" {len(result.completed)}/{result.total}" if result.total else ""
                )
                print(
                    f"{STATUS_GLYPHS[result.status]} {key}: {result.status.value}{progress}",
                    flush=True,
                )

    def finish(self, run: HarnessRun, exit_code: int) -> None:
        self.update(run)
        passed, failed, errors, skipped = _summary(run)
        print(
            f"Summary: {passed} passed, {failed} failed, {errors} errors, "
            f"{skipped} skipped in {_format_duration(run.duration)}",
            flush=True,
        )
        for nodeid, detail in run.failures[:5]:
            print(f"{nodeid}: {detail}", flush=True)
            print(f"Rerun: {_rerun_command(nodeid)}", flush=True)
        print("Port confidence by API", flush=True)
        for score in section_confidence(run, self.confidence_strategies):
            print(
                f"  {score.sdk_function:12} "
                f"{score.verified_strategies}/{score.required_strategies} "
                f"{score.percentage:3}% {score.level.value:6}  "
                f"{' | '.join(score.details)}",
                flush=True,
            )
        print(
            "  Score = required strategies with passing evidence; LOC is reported separately.",
            flush=True,
        )
        print(f"Harness finished with exit code {exit_code}", flush=True)


def make_dashboard(
    strategies: Sequence[Strategy],
    plain: bool = False,
    confidence_strategies: Sequence[Strategy] | None = None,
) -> RichDashboard | PlainDashboard:
    confidence_strategies = confidence_strategies or strategies
    interactive_terminal = (
        sys.stdout.isatty()
        and not os.environ.get("CI")
        and os.environ.get("TERM") != "dumb"
    )
    if not plain and interactive_terminal:
        try:
            import rich  # noqa: F401

            return RichDashboard(strategies, confidence_strategies)
        except ImportError:
            pass
    return PlainDashboard(strategies, confidence_strategies)
