"""サーバの状況（GPU 一覧・実行中ジョブ）を表示用の文字列に整形する純関数群."""

from __future__ import annotations

from collections.abc import Sequence

from tias_judge_model.remote.gpu import (
    GpuInfo,
    GpuProcess,
    GpuSelection,
    GpuState,
    ServerProbe,
    display_user_name,
    gpu_users,
)
from tias_judge_model.remote.observe import format_elapsed

# 計算プロセスの一覧が取れなかった GPU の利用者の表示（run の警告と GPU 一覧で共通）
_UNKNOWN_USERS = "利用者不明（計算プロセスの一覧を取得できない）"


def _format_value(value: int | None, unit: str) -> str:
    return "?" if value is None else f"{value}{unit}"


def describe_gpu_users(gpu: GpuInfo, own_user: str) -> str:
    """GPU の利用者（計算プロセスの所有ユーザー）を表示用に 1 句で返す.

    Args:
        gpu: GPU の状況．
        own_user: 自分のユーザー名（ssh の接続ユーザー．一致するものは「自分」）．

    Returns:
        `利用者: 自分, alice` の形．計算プロセスの一覧が取れなければ `利用者不明`，
        計算プロセスが無ければ空文字．
    """
    if not gpu.processes_known:
        return _UNKNOWN_USERS
    users = gpu_users(gpu, own_user)
    return f"利用者: {', '.join(users)}" if users else ""


def describe_usage(gpu: GpuInfo, *, observed: bool) -> str:
    """GPU の使い方（型と使用率）を表示用に 1 句で返す.

    Args:
        gpu: GPU の状況．
        observed: 観測したか（False なら瞬間値だけを示し，型は付けない）．

    Returns:
        観測したときは `断続的に使用・平均 21%・最大 74%（0% 超 7/20）` の形．観測サンプルが
        無ければ `観測できない`．観測しないときは `使用率 29%（瞬間値）`．
    """
    usage = gpu.usage
    if not observed:
        return f"使用率 {_format_value(gpu.utilization_percent, '%')}（瞬間値）"
    if usage is None:
        return "観測できない（サンプル無し）"
    pattern = gpu.pattern.value if gpu.pattern is not None else "使い方不明"
    return (
        f"{pattern}・平均 {usage.mean_utilization_percent:.0f}%"
        f"・最大 {usage.max_utilization_percent}%"
        f"（0% 超 {usage.active_count}/{usage.sample_count}）"
    )


def describe_process(process: GpuProcess, own_user: str) -> str:
    """計算プロセス 1 件を表示用に 1 句で返す.

    Args:
        process: 計算プロセス．
        own_user: 自分のユーザー名（ssh の接続ユーザー．一致するものは「自分」）．

    Returns:
        `ando  経過 27.7 時間  SM 最大 74%` の形（取れない項目は省く．所有者が取れなければ
        `不明`）．
    """
    parts = [display_user_name(process.user, own_user)]
    if process.elapsed_sec is not None:
        parts.append(f"経過 {format_elapsed(process.elapsed_sec)}")
    if process.max_sm_percent is not None:
        parts.append(f"SM 最大 {process.max_sm_percent}%")
    return "  ".join(parts)


def _format_gpu_lines(gpu: GpuInfo, own_user: str, *, observed: bool) -> list[str]:
    index = "?" if gpu.index is None else str(gpu.index)
    memory = f"{_format_value(gpu.memory_used_mib, '')} / {_format_value(gpu.memory_total_mib, '')}"
    lines = [
        f"    GPU {index:>2}  {gpu.name}  メモリ {memory} MiB  [{gpu.state.value}]"
        f"  {describe_usage(gpu, observed=observed)}"
    ]
    if gpu.state is GpuState.UNKNOWN and gpu.usage is None and not gpu.processes:
        return lines
    if not gpu.processes_known:
        lines.append(f"        {_UNKNOWN_USERS}")
    else:
        lines.extend(f"        - {describe_process(p, own_user)}" for p in gpu.processes)
    return lines


def format_server_header(probe: ServerProbe) -> str:
    """サーバ 1 台の見出し行を返す.

    Args:
        probe: サーバの状況．

    Returns:
        `[名前] アドレス priority=N データ=... 観測 10 秒` の形の 1 行（接続不可なら理由を
        添える）．
    """
    server = probe.server
    data = "配置済み" if server.data_ready else "未配置"
    header = f"[{server.name}] {server.address}  priority={server.priority}  データ={data}"
    if probe.output is None:
        reason = f"（{probe.error}）" if probe.error else ""
        return f"{header}  接続不可{reason}"
    observe_sec = probe.output.observe_sec
    return f"{header}  {'観測 ' + str(observe_sec) + ' 秒' if observe_sec else '瞬間値のみ'}"


def format_server_report(probes: Sequence[ServerProbe], *, show_jobs: bool) -> str:
    """全サーバの GPU 一覧（状態と利用者．と実行中ジョブ）を表示用に整形する.

    Args:
        probes: 各サーバの状況（表示順）．
        show_jobs: 実行中ジョブも表示するか（status 用）．

    Returns:
        末尾に改行を含む複数行の文字列．
    """
    lines: list[str] = []
    for probe in probes:
        lines.append(format_server_header(probe))
        output = probe.output
        if output is None:
            continue
        if show_jobs:
            jobs = ", ".join(output.jobs) if output.jobs else "（なし）"
            lines.append(f"  実行中のジョブ: {jobs}")
            if output.other_sessions:
                lines.append(
                    f"  （このツール以外の tmux セッション {len(output.other_sessions)} 件）"
                )
        if not output.gpu_query_ok:
            lines.append("  GPU: nvidia-smi が失敗した（このサーバの GPU は候補から外す）")
        elif not output.gpus:
            lines.append("  GPU: なし")
        else:
            lines.append("  GPU:")
            for gpu in output.gpus:
                lines.extend(
                    _format_gpu_lines(gpu, probe.server.user, observed=output.observe_sec > 0)
                )
    return "\n".join(lines) + "\n"


def format_selection(selection: GpuSelection | None) -> str:
    """run が選ぶ GPU の説明を 1 行で返す.

    Args:
        selection: `select_gpu` の結果．

    Returns:
        表示用の 1 行（改行なし）．
    """
    if selection is None:
        return "run が選ぶ GPU: なし（未使用・空き（利用者あり）の GPU が無い）"
    text = (
        f"run が選ぶ GPU: {selection.server.name} の GPU {selection.gpu_index}"
        f"（{selection.state.value}）"
    )
    if selection.state is GpuState.SHARED and selection.gpu is not None:
        text += f"  {describe_gpu_users(selection.gpu, selection.server.user)}"
    return text


def format_shared_warning(selection: GpuSelection) -> str | None:
    """「空き（利用者あり）」の GPU を選んだときの警告文を返す（それ以外は None）.

    Args:
        selection: `select_gpu` の結果．

    Returns:
        警告文（改行なし）．
    """
    if selection.state is not GpuState.SHARED or selection.gpu is None:
        return None
    users = describe_gpu_users(selection.gpu, selection.server.user)
    return (
        f"{selection.server.name} の GPU {selection.gpu_index} には計算プロセスがある"
        f"（{users}）．他の計算とメモリ・計算時間を分け合う"
    )
