"""T2 HTTP models. List-style endpoints + the role assignment 422 negative path."""

from __future__ import annotations

import httpx
import pytest

from conftest import HTTP_FAST_TIMEOUT, HTTP_SLOW_TIMEOUT


@pytest.mark.http
def test_models_installed_returns_200(server_url: str) -> None:
    response = httpx.get(f"{server_url}/api/models/installed", timeout=HTTP_SLOW_TIMEOUT)
    assert response.status_code == httpx.codes.OK
    payload = response.json()
    # Response shape varies (sometimes a list, sometimes {"models": [...]}).
    assert isinstance(payload, dict | list), payload


@pytest.mark.http
def test_models_catalog_returns_200(server_url: str) -> None:
    """Featured catalog should always be available; doesn't depend on installed models."""
    response = httpx.get(f"{server_url}/api/models/catalog", timeout=HTTP_SLOW_TIMEOUT)
    assert response.status_code == httpx.codes.OK
    payload = response.json()
    assert isinstance(payload, dict | list)


@pytest.mark.http
def test_catalog_includes_fit_and_size_variants(server_url: str) -> None:
    """`/api/models/catalog` rows carry server-computed `fit` and
    `size_variants` fields that the TUI fit-chip and size-strip widgets
    bind to. A regression that drops these would silently break TUI
    rendering, so gate the response shape here.

    Doesn't pin specific fit values (those depend on the runner's RAM)
    or specific variants (those depend on the featured catalog data).
    Asserts the keys exist, the types match, and at least one row is
    present so the assertion isn't vacuous on an empty catalog.
    """
    response = httpx.get(
        f"{server_url}/api/models/catalog",
        params={"task": "chat", "limit": 5},
        timeout=HTTP_SLOW_TIMEOUT,
    )
    assert response.status_code == httpx.codes.OK, response.text
    payload = response.json()

    # Endpoint shape: either {"models": [...], ...} envelope OR a bare list.
    rows = payload.get("models", payload) if isinstance(payload, dict) else payload
    assert isinstance(rows, list), payload
    assert rows, f"catalog returned no chat-task rows: {payload}"

    sample = rows[0]
    assert "fit" in sample, f"catalog row missing `fit` field: {sample}"
    assert "size_variants" in sample, f"catalog row missing `size_variants` field: {sample}"

    # `fit` is FitLevel | None: string label or null.
    fit = sample["fit"]
    assert fit is None or isinstance(fit, str), f"unexpected fit type: {fit!r}"

    variants = sample["size_variants"]
    assert isinstance(variants, list), f"size_variants must be a list: {variants!r}"
    # If any variants exist, each should carry a human label and a pull ref.
    for variant in variants:
        assert isinstance(variant, dict), f"variant must be dict: {variant!r}"
        assert "ref" in variant, f"variant missing `ref`: {variant}"
        assert "size_label" in variant or "label" in variant, (
            f"variant missing a label key: {variant}"
        )


@pytest.mark.http
def test_unknown_role_assignment_does_not_5xx(server_url: str) -> None:
    """`PUT /api/models/<role>` with an unknown role must never 5xx. The
    surface rejects via 4xx (auth, validation, method-not-allowed,
    not-found) depending on routing. Asserts the server didn't crash; does
    not pin which 4xx, since that's routing-dependent."""
    response = httpx.put(
        f"{server_url}/api/models/not-a-real-role",
        json={"model": "anything"},
        timeout=HTTP_FAST_TIMEOUT,
    )
    assert httpx.codes.BAD_REQUEST <= response.status_code < httpx.codes.INTERNAL_SERVER_ERROR
