# -*- coding: utf-8 -*-
"""Integration tests for miscellaneous agent-scoped endpoints.

Covers tools config, heartbeat, console chat, and MCP OAuth start
through ``/api/agents/{agentId}/...`` paths.
"""
from __future__ import annotations

import httpx
import pytest

from tests.integration.helpers import (
    create_agent,
    default_http_timeout,
    delete_agent_quietly,
    scoped,
)

# Same floor as Windows CI (QWENPAW_INTEGRATION_HTTP_TIMEOUT=120): list tools
# awaits workspace startup and can exceed the 15s default on slow runners.
_HTTP_TIMEOUT = default_http_timeout(120.0)


# ------------------------------------------------------------------ #
# tools
# ------------------------------------------------------------------ #


@pytest.mark.integration
@pytest.mark.p0
def test_tools_list_returns_array(app_server) -> None:
    """Test purpose:
    - Verify GET /api/agents/{agentId}/tools returns 200 with a list
      payload. Agent-scoped tools listing may be empty or contain
      built-in entries depending on configuration.

    Test flow:
    1. Create agent.
    2. GET /agents/{id}/tools.
    3. Assert 200 and JSON is a list.

    API endpoints:
    - GET /api/agents/{agentId}/tools
    """
    agent_id = "integ_misc_tools_list_01"
    create_agent(app_server, agent_id)
    try:
        resp = app_server.api_request(
            "GET",
            scoped(agent_id, "/tools"),
            timeout=_HTTP_TIMEOUT,
        )
        assert resp.status_code == 200, app_server.logs_tail()
        assert isinstance(resp.json(), list)
    finally:
        delete_agent_quietly(app_server, agent_id)


@pytest.mark.integration
@pytest.mark.p0
def test_tools_config_update_unknown_returns_500(app_server) -> None:
    """Test purpose:
    - Verify POST /api/agents/{agentId}/tools/{name}/config with an
      unknown tool name returns 500 because the PluginRegistry rejects
      config writes for non-existent tools.

    Test flow:
    1. Create agent.
    2. POST /{bogus_tool}/config.
    3. Assert 500 and detail mentions ``not found``.

    API endpoints:
    - POST /api/agents/{agentId}/tools/{tool_name}/config
    """
    agent_id = "integ_misc_tools_cfg_unk_01"
    create_agent(app_server, agent_id)
    try:
        resp = app_server.api_request(
            "POST",
            scoped(agent_id, "/tools/integ-nosuch-tool/config"),
            json={"config": {"api_key": "test"}},
            timeout=_HTTP_TIMEOUT,
        )
        assert resp.status_code == 500, app_server.logs_tail()
        assert "not found" in resp.json().get("detail", "").lower()
    finally:
        delete_agent_quietly(app_server, agent_id)


@pytest.mark.integration
@pytest.mark.p0
def test_tools_config_get_nonexistent_returns_empty(
    app_server,
) -> None:
    """Test purpose:
    - Verify GET /api/agents/{agentId}/tools/{name}/config for a
      non-existent tool returns 200 with an empty dict (graceful
      fallback, not 404).

    Test flow:
    1. Create agent.
    2. GET /{bogus_tool}/config.
    3. Assert 200 and body is ``{}``.

    API endpoints:
    - GET /api/agents/{agentId}/tools/{tool_name}/config
    """
    agent_id = "integ_misc_tools_cfg_get_01"
    create_agent(app_server, agent_id)
    try:
        resp = app_server.api_request(
            "GET",
            scoped(agent_id, "/tools/integ-nosuch-tool/config"),
            timeout=_HTTP_TIMEOUT,
        )
        assert resp.status_code == 200, app_server.logs_tail()
        assert resp.json() == {}
    finally:
        delete_agent_quietly(app_server, agent_id)


# ------------------------------------------------------------------ #
# heartbeat
# ------------------------------------------------------------------ #


@pytest.mark.integration
@pytest.mark.p0
def test_heartbeat_run_returns_started(app_server) -> None:
    """Test purpose:
    - Verify POST /api/agents/{agentId}/config/heartbeat/run fires a
      background heartbeat and returns ``{"started": true}``
      immediately. The background task may fail (no LLM configured)
      but the synchronous response must be hermetic.

    Test flow:
    1. Create agent.
    2. POST heartbeat/run.
    3. Assert 200 and ``started == True``.

    API endpoints:
    - POST /api/agents/{agentId}/config/heartbeat/run
    """
    agent_id = "integ_misc_heartbeat_01"
    create_agent(app_server, agent_id)
    try:
        resp = app_server.api_request(
            "POST",
            scoped(agent_id, "/config/heartbeat/run"),
            timeout=_HTTP_TIMEOUT,
        )
        assert resp.status_code == 200, app_server.logs_tail()
        assert resp.json().get("started") is True
    finally:
        delete_agent_quietly(app_server, agent_id)


# ------------------------------------------------------------------ #
# console chat
# ------------------------------------------------------------------ #


@pytest.mark.integration
@pytest.mark.p0
def test_console_chat_returns_sse(app_server) -> None:
    """Test purpose:
    - Verify POST /api/agents/{agentId}/console/chat with a valid
      minimal body returns a 200 SSE response (``text/event-stream``
      content-type). The stream content itself depends on LLM
      availability; we only assert the HTTP-level contract.

    Test flow:
    1. Create agent.
    2. POST console/chat with a minimal valid body using httpx
       streaming mode (avoids blocking on body read).
    3. Assert 200 and content-type starts with ``text/event-stream``.

    API endpoints:
    - POST /api/agents/{agentId}/console/chat
    """
    agent_id = "integ_misc_console_chat_01"
    create_agent(app_server, agent_id)
    try:
        url = f"{app_server.base_url}" f"{scoped(agent_id, '/console/chat')}"
        body = {
            "input": [
                {
                    "content": [
                        {"type": "text", "text": "hello"},
                    ],
                },
            ],
            "user_id": "integ-test",
            "session_id": "integ-session-01",
        }
        with app_server.client.stream(
            "POST",
            url,
            json=body,
            timeout=httpx.Timeout(10.0, read=3.0),
        ) as resp:
            assert resp.status_code == 200, app_server.logs_tail()
            ct = resp.headers.get("content-type", "")
            assert "text/event-stream" in ct
    except httpx.ReadTimeout:
        pass
    finally:
        delete_agent_quietly(app_server, agent_id)


@pytest.mark.integration
@pytest.mark.p0
def test_console_chat_invalid_body_returns_422(app_server) -> None:
    """Test purpose:
    - Verify POST /api/agents/{agentId}/console/chat with an
      unparseable body returns 422 (FastAPI validation error).

    Test flow:
    1. POST console/chat with body ``"not-json-object"`` (a bare
       string, not dict/AgentRequest).
    2. Assert 422.

    API endpoints:
    - POST /api/agents/{agentId}/console/chat
    """
    resp = app_server.api_request(
        "POST",
        scoped("default", "/console/chat"),
        content='"just-a-string"',
        headers={"Content-Type": "application/json"},
        timeout=_HTTP_TIMEOUT,
    )
    assert resp.status_code == 422, app_server.logs_tail()


# ------------------------------------------------------------------ #
# mcp-oauth start
# ------------------------------------------------------------------ #


@pytest.mark.integration
@pytest.mark.p0
def test_mcp_oauth_start_returns_auth_url(app_server) -> None:
    """Test purpose:
    - Verify POST /api/agents/{agentId}/mcp/oauth/start/{client_key}
      with explicit ``auth_endpoint`` and ``token_endpoint`` returns
      200 and an ``auth_url`` without making any network calls
      (PKCE flow is computed locally).

    Test flow:
    1. Create agent.
    2. POST oauth/start with explicit mock endpoints.
    3. Assert 200 and response contains ``auth_url`` and ``session_id``.

    API endpoints:
    - POST /api/agents/{agentId}/mcp/oauth/start/{client_key}
    """
    agent_id = "integ_misc_oauth_start_01"
    client_key = "integ-mock-mcp-client"
    create_agent(app_server, agent_id)
    try:
        # 2.0: MCP card must exist before OAuth start (driver-based arch).
        create_resp = app_server.api_request(
            "POST",
            scoped(agent_id, "/mcp"),
            json={
                "client_key": client_key,
                "client": {
                    "name": "Mock MCP Client",
                    "transport": "streamable_http",
                    "url": "http://localhost:19999/mcp",
                    "enabled": True,
                },
            },
            timeout=_HTTP_TIMEOUT,
        )
        assert create_resp.status_code in (200, 201), (
            f"MCP client create failed: {create_resp.status_code} "
            f"{create_resp.text}"
        )
        resp = app_server.api_request(
            "POST",
            scoped(
                agent_id,
                f"/mcp/oauth/start/{client_key}",
            ),
            json={
                "url": "http://localhost:19999/mcp",
                "auth_endpoint": "http://localhost:19999/authorize",
                "token_endpoint": "http://localhost:19999/token",
                "scope": "read",
            },
            timeout=_HTTP_TIMEOUT,
        )
        assert resp.status_code == 200, app_server.logs_tail()
        payload = resp.json()
        assert "auth_url" in payload
        assert "session_id" in payload
        assert "localhost:19999/authorize" in payload["auth_url"]
    finally:
        delete_agent_quietly(app_server, agent_id)
