from __future__ import annotations

import json

import httpx

from grantflow.mcp.server import handle_request
from grantflow.mcp.tools import GrantFlowToolClient, GrantFlowToolError, list_tools


def test_list_tools_exposes_manifest_tools_with_grantflow_prefix():
    tools = {tool["name"]: tool for tool in list_tools()}

    assert {
        "grantflow_onboard_agent",
        "grantflow_create_session",
        "grantflow_introspect_agent",
        "grantflow_exchange_oauth_token",
        "grantflow_rotate_credential",
        "grantflow_revoke_credential",
        "grantflow_register_agent",
        "grantflow_run_preflight",
        "grantflow_start_generation",
        "grantflow_get_status",
        "grantflow_get_quality",
        "grantflow_get_events",
        "grantflow_run_sandbox_happy_path",
        # New tools added with RAG ingest, HITL, and export coverage
        "grantflow_ingest_text",
        "grantflow_hitl_approve",
        "grantflow_hitl_list_pending",
        "grantflow_get_export_payload",
    }.issubset(set(tools))
    assert tools["grantflow_onboard_agent"]["annotations"]["path"] == "/agents/onboarding"
    assert tools["grantflow_create_session"]["annotations"]["path"] == "/agents/session"
    assert tools["grantflow_create_session"]["inputSchema"]["required"] == ["agent_id", "tenant_id"]
    assert tools["grantflow_introspect_agent"]["annotations"]["path"] == "/agents/introspect"
    assert tools["grantflow_exchange_oauth_token"]["annotations"]["path"] == "/agents/oauth/token"
    assert tools["grantflow_rotate_credential"]["annotations"]["path"] == "/agents/credentials/rotate"
    assert tools["grantflow_revoke_credential"]["annotations"]["path"] == "/agents/credentials/revoke"
    assert tools["grantflow_register_agent"]["inputSchema"]["required"] == ["agent_name"]
    assert tools["grantflow_start_generation"]["annotations"]["path"] == "/generate"
    assert tools["grantflow_run_sandbox_happy_path"]["annotations"]["workflow"] == "sandbox_generate"


def test_call_tool_maps_to_http_endpoint_and_auth_header():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(
            200,
            json={"status": "registered", "agent_id": "agent_proposal_worker"},
            request=request,
        )

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(
        base_url="http://grantflow.test",
        api_key="secret",
        transport=transport,
    ) as client:
        response = client.call_tool("grantflow_register_agent", {"agent_name": "Proposal Worker"})

    assert response["agent_id"] == "agent_proposal_worker"
    assert len(requests) == 1
    request = requests[0]
    assert request.method == "POST"
    assert request.url.path == "/agents/register"
    assert request.headers["X-API-Key"] == "secret"
    assert json.loads(request.content) == {"agent_name": "Proposal Worker"}


def test_call_tool_prefers_bearer_token_when_configured():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(200, json={"active": True}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(
        base_url="http://grantflow.test",
        api_key="operator-secret",
        bearer_token="agent-access-token",
        transport=transport,
    ) as client:
        response = client.call_tool("grantflow_introspect_agent", {})

    assert response["active"] is True
    assert requests[0].headers["Authorization"] == "Bearer agent-access-token"
    assert "X-API-Key" not in requests[0].headers


def test_call_tool_maps_onboarding_to_http_endpoint():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(200, json={"status": "pending_operator"}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        response = client.call_tool(
            "grantflow_onboard_agent",
            {"agent_name": "Proposal Worker", "auth_type": "oauth_client_credentials"},
        )

    assert response["status"] == "pending_operator"
    assert requests[0].method == "POST"
    assert requests[0].url.path == "/agents/onboarding"


def test_call_tool_maps_session_to_http_endpoint():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(200, json={"status": "pending_operator", "session_id": "ags_123"}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        response = client.call_tool(
            "grantflow_create_session",
            {"agent_id": "agent_proposal_worker", "tenant_id": "sandbox_proposal_worker"},
        )

    assert response["session_id"] == "ags_123"
    assert requests[0].method == "POST"
    assert requests[0].url.path == "/agents/session"


def test_call_tool_maps_introspection_to_http_endpoint_and_auth_header():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(200, json={"active": True, "credential_type": "operator"}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", api_key="secret", transport=transport) as client:
        response = client.call_tool("grantflow_introspect_agent", {})

    assert response["active"] is True
    assert requests[0].method == "POST"
    assert requests[0].url.path == "/agents/introspect"
    assert requests[0].headers["X-API-Key"] == "secret"


def test_call_tool_maps_oauth_token_exchange_to_http_endpoint():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(200, json={"token_type": "Bearer"}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        response = client.call_tool(
            "grantflow_exchange_oauth_token",
            {"client_id": "gf_client_agent", "client_secret": "gf_oauth_client_secret"},
        )

    assert response["token_type"] == "Bearer"
    assert requests[0].method == "POST"
    assert requests[0].url.path == "/agents/oauth/token"


def test_call_tool_maps_credential_lifecycle_to_http_endpoints():
    paths: list[str] = []

    def handler(request: httpx.Request) -> httpx.Response:
        paths.append(request.url.path)
        return httpx.Response(200, json={"status": "ok"}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        client.call_tool("grantflow_rotate_credential", {"credential": "gf_agent_old"})
        client.call_tool("grantflow_revoke_credential", {"credential": "gf_agent_old"})

    assert paths == ["/agents/credentials/rotate", "/agents/credentials/revoke"]


def test_call_tool_maps_job_tools_to_path_parameters():
    paths: list[str] = []

    def handler(request: httpx.Request) -> httpx.Response:
        paths.append(request.url.path)
        return httpx.Response(200, json={"status": "done"}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        client.call_tool("grantflow_get_status", {"job_id": "job_123"})
        client.call_tool("grantflow_get_quality", {"job_id": "job_123"})
        client.call_tool("grantflow_get_events", {"job_id": "job_123"})

    assert paths == ["/status/job_123", "/status/job_123/quality", "/status/job_123/events"]


def test_missing_job_id_returns_structured_tool_error():
    with GrantFlowToolClient(base_url="http://grantflow.test") as client:
        try:
            client.call_tool("grantflow_get_status", {})
        except GrantFlowToolError as exc:
            assert exc.detail["code"] == "missing_required_argument"
            assert exc.detail["required"] == ["job_id"]
        else:
            raise AssertionError("Expected GrantFlowToolError")


def test_sandbox_happy_path_requires_agent_name():
    with GrantFlowToolClient(base_url="http://grantflow.test") as client:
        try:
            client.call_tool("grantflow_run_sandbox_happy_path", {})
        except GrantFlowToolError as exc:
            assert exc.detail["code"] == "missing_required_argument"
            assert exc.detail["required"] == ["agent_name"]
        else:
            raise AssertionError("Expected GrantFlowToolError")


def test_sandbox_happy_path_uses_registration_samples_and_reads_outputs():
    calls: list[tuple[str, str]] = []

    def handler(request: httpx.Request) -> httpx.Response:
        calls.append((request.method, request.url.path))
        if request.url.path == "/agents/register":
            return httpx.Response(
                200,
                json={
                    "sample_requests": {
                        "preflight": {"donor_id": "usaid", "tenant_id": "sandbox_proposal"},
                        "generate": {
                            "donor_id": "usaid",
                            "tenant_id": "sandbox_proposal",
                            "input_context": {"project": "Agent test"},
                        },
                    }
                },
                request=request,
            )
        if request.url.path == "/generate/preflight":
            return httpx.Response(200, json={"go_ahead": True}, request=request)
        if request.url.path == "/generate":
            return httpx.Response(200, json={"job_id": "job_123", "request_id": "req_123"}, request=request)
        if request.url.path == "/status/job_123":
            return httpx.Response(200, json={"status": "done"}, request=request)
        if request.url.path == "/status/job_123/quality":
            return httpx.Response(200, json={"grounded": True}, request=request)
        if request.url.path == "/status/job_123/events":
            return httpx.Response(200, json={"event_count": 3, "events": []}, request=request)
        raise AssertionError(f"Unexpected path: {request.url.path}")

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        result = client.call_tool(
            "grantflow_run_sandbox_happy_path",
            {"agent_name": "Proposal Worker", "poll_interval_seconds": 0, "timeout_seconds": 1},
        )

    assert result["generation"]["job_id"] == "job_123"
    assert result["status"]["status"] == "done"
    assert result["quality"]["grounded"] is True
    assert calls == [
        ("POST", "/agents/register"),
        ("POST", "/generate/preflight"),
        ("POST", "/generate"),
        ("GET", "/status/job_123"),
        ("GET", "/status/job_123/quality"),
        ("GET", "/status/job_123/events"),
    ]


def test_handle_request_lists_and_calls_tools():
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(200, json={"status": "registered"}, request=request)

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        listed = handle_request({"jsonrpc": "2.0", "id": "list", "method": "tools/list"}, client)
        called = handle_request(
            {
                "jsonrpc": "2.0",
                "id": "call",
                "method": "tools/call",
                "params": {"name": "grantflow_register_agent", "arguments": {"agent_name": "Proposal Worker"}},
            },
            client,
        )

    assert listed["result"]["tools"][0]["name"].startswith("grantflow_")
    assert called == {
        "jsonrpc": "2.0",
        "id": "call",
        "result": {
            "content": [{"type": "json", "json": {"status": "registered"}}],
            "isError": False,
        },
    }


def test_call_tool_maps_ingest_text_to_http_endpoint():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(
            200,
            json={"status": "ingested", "chunks_ingested": 5, "namespace": "eu_intpa"},
            request=request,
        )

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", api_key="secret", transport=transport) as client:
        response = client.call_tool(
            "grantflow_ingest_text",
            {"text": "Digital governance pilot context.", "donor_id": "eu_intpa", "title": "pilot-brief"},
        )

    assert response["status"] == "ingested"
    assert response["chunks_ingested"] == 5
    assert len(requests) == 1
    assert requests[0].method == "POST"
    assert requests[0].url.path == "/ingest/text"
    body = json.loads(requests[0].content)
    assert body["donor_id"] == "eu_intpa"
    assert body["title"] == "pilot-brief"


def test_call_tool_maps_hitl_approve_to_http_endpoint():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(
            200,
            json={"status": "approved", "checkpoint_id": "cp_abc"},
            request=request,
        )

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", api_key="secret", transport=transport) as client:
        response = client.call_tool(
            "grantflow_hitl_approve",
            {"checkpoint_id": "cp_abc", "approved": True, "feedback": "Looks good"},
        )

    assert response["status"] == "approved"
    assert len(requests) == 1
    assert requests[0].method == "POST"
    assert requests[0].url.path == "/hitl/approve"
    body = json.loads(requests[0].content)
    assert body["checkpoint_id"] == "cp_abc"
    assert body["approved"] is True
    assert body["feedback"] == "Looks good"


def test_call_tool_maps_hitl_list_pending_to_http_endpoint_with_query_params():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(
            200,
            json={"checkpoints": [], "total": 0},
            request=request,
        )

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        response = client.call_tool(
            "grantflow_hitl_list_pending",
            {"tenant_id": "sandbox_test", "limit": 10},
        )

    assert response["total"] == 0
    assert len(requests) == 1
    assert requests[0].method == "GET"
    assert requests[0].url.path == "/hitl/pending"
    assert "tenant_id=sandbox_test" in str(requests[0].url)
    assert "limit=10" in str(requests[0].url)


def test_call_tool_maps_get_export_payload_to_http_endpoint():
    requests: list[httpx.Request] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        return httpx.Response(
            200,
            json={"job_id": "job_123", "export_readiness": {"readiness_status": "ready"}},
            request=request,
        )

    transport = httpx.MockTransport(handler)
    with GrantFlowToolClient(base_url="http://grantflow.test", transport=transport) as client:
        response = client.call_tool("grantflow_get_export_payload", {"job_id": "job_123"})

    assert response["job_id"] == "job_123"
    assert response["export_readiness"]["readiness_status"] == "ready"
    assert len(requests) == 1
    assert requests[0].method == "GET"
    assert requests[0].url.path == "/status/job_123/export-payload"


def test_handle_request_returns_tool_errors_with_request_id():
    with GrantFlowToolClient(base_url="http://grantflow.test") as client:
        response = handle_request(
            {
                "jsonrpc": "2.0",
                "id": "bad-call",
                "method": "tools/call",
                "params": {"name": "grantflow_run_sandbox_happy_path", "arguments": {}},
            },
            client,
        )

    assert response["id"] == "bad-call"
    assert response["result"]["isError"] is True
    assert response["result"]["content"][0]["json"]["code"] == "missing_required_argument"
