"""Conformance checklist, automated, run against a real demo agent
(examples/demo_agents/weather_agent.py) built with the actual library:
  1. AgentCard has the required fields and at least one skill.
  2. Unauthenticated calls are rejected (401).
  3. Wrong-audience tokens are rejected (401).
  4. Insufficient-scope tokens are rejected (403).
  5. A correctly authenticated call reaches TASK_STATE_COMPLETED with an artifact.

Runs fully in-process against the real FastAPI app via httpx.ASGITransport --
no subprocess/network needed, fast enough for CI on every agent onboarding.
"""
import sys
import time
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))

import httpx
import jwt
import pytest

from a2a_passport.testing import JWT_SECRET
from examples.demo_agents.weather_agent import AUDIENCE, REQUIRED_SCOPE, build_app


def _mint_token(audience: str, scope: str, secret: str | None = None, exp_delta: int = 60) -> str:
    now = int(time.time())
    claims = {
        "sub": "test-client",
        "aud": audience,
        "scope": scope,
        "iat": now,
        "exp": now + exp_delta,
    }
    return jwt.encode(claims, secret or JWT_SECRET, algorithm="HS256")


@pytest.fixture
def app():
    return build_app()


@pytest.fixture
async def client(app):
    transport = httpx.ASGITransport(app=app)
    async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as c:
        yield c


async def test_agent_card_is_well_formed(client):
    r = await client.get("/.well-known/agent-card.json")
    assert r.status_code == 200
    card = r.json()
    assert card["name"]
    assert card["version"]
    assert len(card["skills"]) >= 1
    assert "capabilities" in card


async def test_unauthenticated_call_is_rejected(client):
    r = await client.post(
        "/", json={"jsonrpc": "2.0", "id": "1", "method": "message/send", "params": {}}
    )
    assert r.status_code == 401
    assert r.json()["error"] == "missing_bearer_token"


async def test_wrong_audience_token_is_rejected(client):
    token = _mint_token(audience="some-other-agent", scope=REQUIRED_SCOPE)
    r = await client.post(
        "/",
        json={"jsonrpc": "2.0", "id": "1", "method": "message/send", "params": {}},
        headers={"Authorization": f"Bearer {token}"},
    )
    assert r.status_code == 401
    assert r.json()["error"] == "invalid_audience"


async def test_insufficient_scope_token_is_rejected(client):
    token = _mint_token(audience=AUDIENCE, scope="some:other:scope")
    r = await client.post(
        "/",
        json={"jsonrpc": "2.0", "id": "1", "method": "message/send", "params": {}},
        headers={"Authorization": f"Bearer {token}"},
    )
    assert r.status_code == 403
    assert r.json()["error"] == "insufficient_scope"


async def test_expired_token_is_rejected(client):
    token = _mint_token(audience=AUDIENCE, scope=REQUIRED_SCOPE, exp_delta=-10)
    r = await client.post(
        "/",
        json={"jsonrpc": "2.0", "id": "1", "method": "message/send", "params": {}},
        headers={"Authorization": f"Bearer {token}"},
    )
    assert r.status_code == 401
    assert r.json()["error"] == "token_expired"


async def test_authenticated_call_completes_with_artifact(client):
    from a2a.client import A2ACardResolver, ClientConfig, create_client
    from a2a.helpers import get_data_parts, new_data_message
    from a2a.types.a2a_pb2 import Role, SendMessageRequest

    token = _mint_token(audience=AUDIENCE, scope=REQUIRED_SCOPE)

    class _StaticAuth(httpx.Auth):
        def sync_auth_flow(self, request):
            request.headers["Authorization"] = f"Bearer {token}"
            yield request

        async def async_auth_flow(self, request):
            request.headers["Authorization"] = f"Bearer {token}"
            yield request

    # Resolve the card through the SAME in-process ASGI transport (no real
    # socket involved), then hand the resolved AgentCard to create_client
    # directly -- this sidesteps create_client's URL-based resolver, which
    # would otherwise attempt a real network fetch against 127.0.0.1:9201.
    plain_transport = httpx.ASGITransport(app=client._transport.app)
    async with httpx.AsyncClient(
        transport=plain_transport, base_url="http://127.0.0.1:9201"
    ) as card_httpx:
        resolver = A2ACardResolver(httpx_client=card_httpx, base_url="http://127.0.0.1:9201")
        card = await resolver.get_agent_card()

    transport = httpx.ASGITransport(app=client._transport.app)
    async with httpx.AsyncClient(
        transport=transport, base_url="http://127.0.0.1:9201", auth=_StaticAuth()
    ) as authed_httpx:
        config = ClientConfig(streaming=False, httpx_client=authed_httpx)
        a2a_client = await create_client(agent=card, client_config=config)
        msg = new_data_message({"city": "Paris"}, role=Role.ROLE_USER)
        req = SendMessageRequest(message=msg)
        last_task = None
        async for chunk in a2a_client.send_message(req):
            last_task = chunk.task
        await a2a_client.close()

    from a2a.types.a2a_pb2 import TaskState

    assert last_task is not None
    assert last_task.status.state == TaskState.TASK_STATE_COMPLETED
    assert len(last_task.artifacts) == 1
    data = get_data_parts(last_task.artifacts[0].parts)[0]
    assert data["city"] == "Paris"
    assert data["condition"] == "light rain"
