# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0

"""Tests for the llama-server /metrics -> engine_stats translator: generation
throughput comes from generated-token metrics (not llama_decode() calls), and
the unexposed kv_cache_usage_ratio is never fabricated into the log line."""

from core.inference.llama_stats import LlamaServerStatsLogger


class _Capture:
    def __init__(self):
        self.events = []

    def info(self, event, **kw):
        self.events.append((event, dict(kw)))

    def debug(self, *a, **k):
        pass


def _drive(snaps):
    """Run _run() synchronously over `snaps`, then stop deterministically."""
    cap = _Capture()
    lg = LlamaServerStatsLogger("http://127.0.0.1:0", cap)
    lg._interval = 0.001  # bypass the 1s floor for a fast, synchronous run
    state = {"i": 0}

    def fake_scrape():
        i = state["i"]
        state["i"] += 1
        if i >= len(snaps):
            lg.stop()
            return None
        return snaps[i]

    lg._scrape = fake_scrape
    lg._run()
    return [kw for ev, kw in cap.events if ev == "engine_stats"]


def test_gen_tok_s_uses_token_metrics_not_decode_calls():
    # tokens_predicted_total jumps 95 while n_decode_total only moves 9; the
    # gauge reports 95 tok/s. Decode-call rate (9) must not be reported.
    snaps = [
        {
            "tokens_predicted_total": 0.0,
            "prompt_tokens_total": 0.0,
            "n_decode_total": 0.0,
            "predicted_tokens_seconds": 95.0,
            "prompt_tokens_seconds": 30.0,
            "requests_processing": 1.0,
        },
        {
            "tokens_predicted_total": 95.0,
            "prompt_tokens_total": 30.0,
            "n_decode_total": 9.0,
            "predicted_tokens_seconds": 95.0,
            "prompt_tokens_seconds": 30.0,
            "requests_processing": 1.0,
        },
    ]
    stats = _drive(snaps)
    assert stats, "expected engine_stats while a request is processing"
    assert all(s["gen_tok_s"] == 95.0 for s in stats)
    assert all(s["prompt_tok_s"] == 30.0 for s in stats)


def test_kv_cache_pct_not_emitted_when_metric_absent():
    # llama.cpp does not expose kv_cache_usage_ratio, so it must not appear.
    snaps = [
        {
            "tokens_predicted_total": 0.0,
            "prompt_tokens_total": 0.0,
            "predicted_tokens_seconds": 10.0,
            "requests_processing": 1.0,
        },
        {
            "tokens_predicted_total": 10.0,
            "prompt_tokens_total": 5.0,
            "predicted_tokens_seconds": 10.0,
            "requests_processing": 1.0,
        },
    ]
    stats = _drive(snaps)
    assert stats
    assert all("kv_cache_pct" not in s for s in stats)


def test_scrape_parses_labelled_and_bare_metrics(monkeypatch):
    # Prometheus samples may carry labels; both labelled and bare lines parse.
    import core.inference.llama_stats as ls

    body = (
        'llamacpp:tokens_predicted_total{model="m"} 20\n'
        'llamacpp:prompt_tokens_total{model="m"} 5\n'
        "llamacpp:requests_processing 1\n"
        "# HELP llamacpp:ignored ignored\n"
    )

    class _Resp:
        status = 200

        def read(self):
            return body.encode()

        def __enter__(self):
            return self

        def __exit__(self, *a):
            return False

    monkeypatch.setattr(ls.urllib.request, "urlopen", lambda *a, **k: _Resp())
    m = ls.LlamaServerStatsLogger("http://127.0.0.1:0", _Capture())._scrape()
    assert m["tokens_predicted_total"] == 20.0
    assert m["prompt_tokens_total"] == 5.0
    assert m["requests_processing"] == 1.0


def test_counter_delta_fallback_without_gauges():
    # Older binaries expose only the counters; throughput falls back to deltas.
    snaps = [
        {"tokens_predicted_total": 100.0, "prompt_tokens_total": 0.0, "requests_processing": 1.0},
        {"tokens_predicted_total": 100.0, "prompt_tokens_total": 0.0, "requests_processing": 1.0},
    ]
    stats = _drive(snaps)
    # running=1 keeps it emitting; gen_tok_s falls back to the (here zero) delta.
    assert stats and all(s["gen_tok_s"] >= 0.0 for s in stats)
