"""The test suite that defines correct behaviour for task-app.py.

Purpose: six failing tests that specify what the usage-log summariser is supposed to do.
    An agent's job in the lab is to make every one of them pass without editing this file.
Platform: all (spark, strix, mac, nvidia). Pure standard library plus pytest.
Minimum memory: none of consequence.
Assumes: pytest installed, and task-app.py in the same directory. The module name has a
    hyphen in it, which is not importable with a plain import statement, so the loader
    below reads it by path. Leave that loader alone; it is not part of the task.
Usage:
    python3 -m pytest -q task-tests.py
"""

from __future__ import annotations

import importlib.util
from pathlib import Path

_SPEC = importlib.util.spec_from_file_location(
    "task_app", Path(__file__).with_name("task-app.py")
)
assert _SPEC is not None and _SPEC.loader is not None
app = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(app)


LOG = "\n".join(
    [
        '{"model": "local/coder", "prompt_tokens": 100, "completion_tokens": 20}',
        "",
        '{"model": "local/chat", "prompt_tokens": 5, "completion_tokens": 6}',
        "this line is not json and a real log will contain one eventually",
        '{"model": "local/chat", "prompt_tokens": 4, "completion_tokens": 6}',
        '{"model": "local/chat", "prompt_tokens": 5, "completion_tokens": 6}',
    ]
)


def test_parse_skips_blank_and_malformed_lines():
    """A log with a blank line and a corrupt line still parses into four records."""
    records = app.parse_usage_lines(LOG)
    assert len(records) == 4
    assert records[0]["model"] == "local/coder"


def test_total_tokens_counts_prompt_and_completion():
    """A call costs its prompt tokens plus its completion tokens."""
    record = {"model": "local/coder", "prompt_tokens": 100, "completion_tokens": 20}
    assert app.total_tokens(record) == 120


def test_total_tokens_tolerates_missing_fields():
    """A record missing a token field counts what it has rather than raising."""
    assert app.total_tokens({"model": "local/chat"}) == 0
    assert app.total_tokens({"model": "local/chat", "completion_tokens": 7}) == 7


def test_summary_is_ordered_by_tokens_descending():
    """The busiest model comes first, so the report answers the question it is asked."""
    rows = app.summarise(app.parse_usage_lines(LOG))
    assert [row["model"] for row in rows] == ["local/coder", "local/chat"]


def test_summary_reports_calls_and_mean_tokens():
    """Each row carries the call count and the mean tokens per call."""
    rows = app.summarise(app.parse_usage_lines(LOG))
    chat = next(row for row in rows if row["model"] == "local/chat")
    assert chat["calls"] == 3
    assert chat["tokens"] == 32
    assert abs(chat["mean_tokens"] - 32 / 3) < 1e-9


def test_format_report_rounds_the_mean():
    """The mean column is rounded to the nearest token, not truncated towards zero."""
    report = app.format_report(app.summarise(app.parse_usage_lines(LOG)))
    rows = [line.split() for line in report.splitlines()]
    assert rows[1] == ["local/coder", "1", "120", "120"]
    assert rows[2] == ["local/chat", "3", "32", "11"]


def test_format_report_handles_an_empty_log():
    """An empty log is a normal state on a quiet day, not an error."""
    assert app.format_report(app.summarise([])) == "no calls recorded"
