# -*- coding: utf-8 -*-
"""Integration tests for the local-model (llama.cpp) router."""
from __future__ import annotations

import pytest
from helpers import default_http_timeout

_LOCAL_MODELS_HTTP_TIMEOUT = default_http_timeout(15.0)


@pytest.mark.integration
@pytest.mark.p1
def test_local_models_server_status_returns_contract(app_server) -> None:
    """Test purpose:
    - Verify GET /api/local-models/server returns the ServerStatus
      contract: the three boolean fields (available / installable /
      installed) are always present so the Console can render the
      local-model dashboard regardless of whether llama.cpp is installed.

    Test flow:
    1. GET /api/local-models/server.
    2. Assert 200 and the response contains the three boolean keys with
       boolean values.

    API endpoints:
    - GET /api/local-models/server
    """
    resp = app_server.api_request(
        "GET",
        "/api/local-models/server",
        timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
    )
    assert resp.status_code == 200, app_server.logs_tail()
    payload = resp.json()
    for key in ("available", "installable", "installed"):
        assert key in payload, f"missing key: {key}"
        assert isinstance(payload[key], bool)


@pytest.mark.integration
@pytest.mark.p1
def test_local_models_models_list_returns_array(app_server) -> None:
    """Test purpose:
    - Verify GET /api/local-models/models returns an array of models
      (recommended + downloaded). Console populates the local-model
      picker from this list; a regression hides every local model.

    Test flow:
    1. GET /api/local-models/models.
    2. Assert 200 and the body is a list (may be empty in environments
       where neither recommendations nor downloaded models exist).

    API endpoints:
    - GET /api/local-models/models
    """
    resp = app_server.api_request(
        "GET",
        "/api/local-models/models",
        timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
    )
    assert resp.status_code == 200, app_server.logs_tail()
    assert isinstance(resp.json(), list)


@pytest.mark.integration
@pytest.mark.p2
def test_local_models_delete_unknown_model_returns_404(app_server) -> None:
    """Test purpose:
    - Verify DELETE /api/local-models/models/{model_id:path} returns 404
      with a descriptive detail when the model has never been downloaded,
      so Console can surface a clear error to the user.

    Test flow:
    1. DELETE /api/local-models/models/<unknown-id>.
    2. Assert 404 status and a non-empty detail field.

    API endpoints:
    - DELETE /api/local-models/models/{model_id:path}
    """
    unknown_id = "integ-unknown-model-xyz"
    resp = app_server.api_request(
        "DELETE",
        f"/api/local-models/models/{unknown_id}",
        timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
    )
    assert resp.status_code == 404, app_server.logs_tail()
    assert isinstance(resp.json().get("detail"), str) and resp.json()["detail"]


# ------------------------------------------------------------------ #
# Sprint 3.1-D additions
# ------------------------------------------------------------------ #


@pytest.mark.integration
@pytest.mark.p2
def test_local_models_config_roundtrip(app_server) -> None:
    """Test purpose:
    - Verify PUT /api/local-models/config persists max_context_length and
      GET returns the updated value.

    Test flow:
    1. GET baseline config.
    2. PUT a new max_context_length (must be >= 32768 per validator).
    3. GET and assert the new value is returned.
    4. Restore baseline.

    API endpoints:
    - GET /api/local-models/config
    - PUT /api/local-models/config
    """
    get_resp = app_server.api_request(
        "GET",
        "/api/local-models/config",
        timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
    )
    assert get_resp.status_code == 200, app_server.logs_tail()
    baseline = get_resp.json()
    assert isinstance(baseline, dict), baseline
    original_ctx = baseline.get("max_context_length")

    new_ctx = 65536 if original_ctx != 65536 else 98304
    try:
        put_resp = app_server.api_request(
            "PUT",
            "/api/local-models/config",
            json={"max_context_length": new_ctx},
            timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
        )
        assert put_resp.status_code == 200, app_server.logs_tail()
        assert put_resp.json().get("status") == "ok", put_resp.json()

        get_after = app_server.api_request(
            "GET",
            "/api/local-models/config",
            timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
        )
        body = get_after.json()
        assert (
            body.get("max_context_length") == new_ctx
        ), f"max_context_length not persisted: {body}"
    finally:
        if original_ctx is not None:
            app_server.api_request(
                "PUT",
                "/api/local-models/config",
                json={"max_context_length": original_ctx},
                timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
            )


@pytest.mark.integration
@pytest.mark.p2
def test_local_models_server_update_info_contract(app_server) -> None:
    """Test purpose:
    - Verify GET /api/local-models/server/update returns a structured
      response so Console can decide whether to prompt the user about
      a server upgrade. Endpoint must respond 200 even when no update is
      pending.

    Test flow:
    1. GET /api/local-models/server/update.
    2. Assert 200 and the response is a dict.

    API endpoints:
    - GET /api/local-models/server/update
    """
    resp = app_server.api_request(
        "GET",
        "/api/local-models/server/update",
        timeout=_LOCAL_MODELS_HTTP_TIMEOUT,
    )
    assert resp.status_code == 200, app_server.logs_tail()
    body = resp.json()
    assert isinstance(body, dict), body
