from __future__ import annotations

import time
from collections.abc import Iterable

from rich import box
from rich.console import Group
from rich.table import Table

from hytop.core.format import fmt_elapsed, fmt_window
from hytop.core.history import SlidingHistory
from hytop.gpu.metrics import render_columns_for_show_flags


def build_renderable(
    window: float,
    hosts: list[str],
    histories: dict[tuple[str, int], SlidingHistory],
    monitored_keys: Iterable[tuple[str, int]],
    errors: dict[str, str],
    show_flags: Iterable[str],
    poll_interval: float,
    elapsed_since_start: float,
) -> Group:
    """Build the Rich renderable for the current monitor frame.

    Args:
        window: Rolling window length in seconds.
        hosts: Host order used for row ordering and error table.
        histories: Sliding histories by host+gpu key.
        monitored_keys: Effective host+gpu keys to show.
        errors: Latest host-level errors.
        poll_interval: Configured sampling interval in seconds.
        elapsed_since_start: Total runtime in seconds.

    Returns:
        A Group containing main data table and optional error table.
    """

    now = time.monotonic()
    host_rank = {host: idx for idx, host in enumerate(hosts)}
    key_list = sorted(monitored_keys, key=lambda x: (host_rank.get(x[0], len(hosts)), x[1]))
    title = (
        f"hytop gpu | interval={poll_interval:.2f}s | elapsed={fmt_elapsed(elapsed_since_start)}"
    )
    table = Table(
        title=title,
        box=box.MINIMAL_HEAVY_HEAD,
        expand=True,
    )
    table.add_column("Host", justify="left", no_wrap=True)
    table.add_column("GPU", justify="right")
    columns = render_columns_for_show_flags(show_flags)
    for col in columns:
        table.add_column(col.label, justify="right")
        if col.avg_label is not None:
            table.add_column(f"{col.avg_label}@{fmt_window(window)}", justify="right")

    for key in key_list:
        history = histories.get(key)
        if history is None:
            continue
        latest = history.latest()
        if latest is None:
            continue
        host, gpu = key
        stale = (now - latest.ts) > window
        if stale:
            table.add_row(host, str(gpu), *["-"] * (len(table.columns) - 2))
            continue
        values: list[str] = []
        for col in columns:
            metric_value = getattr(latest, col.metric, None)
            values.append(_format_metric(col.metric, metric_value))
            if col.avg_label is not None:
                if metric_value is None:
                    values.append("-")
                else:
                    values.append(_format_metric(col.metric, history.avg(col.metric, window, now)))
        table.add_row(
            host,
            str(gpu),
            *values,
        )

    if table.row_count == 0:
        table.add_row("No data yet.", *[""] * (len(table.columns) - 1))

    if not errors:
        return Group(table)
    err_table = Table(title="Host errors", box=box.MINIMAL_HEAVY_HEAD, expand=True)
    err_table.add_column("Host", justify="left", no_wrap=True)
    err_table.add_column("Error", justify="left")
    for host in hosts:
        err = errors.get(host)
        if err:
            err_table.add_row(host, err)
    return Group(table, err_table)


def _format_metric(metric: str, value: object) -> str:
    if value is None:
        return "-"
    if metric == "temp_c":
        return f"{float(value):7.1f}C"
    if metric == "avg_pwr_w":
        return f"{float(value):8.1f}W"
    if metric in {"vram_pct", "hcu_pct"}:
        return f"{float(value):7.2f}%"
    if metric == "sclk_mhz":
        return f"{float(value):7.0f}MHz"
    return str(value)
