"""
End-to-end demo.

Starts: mock IdP, Weather Agent, Translator Agent, Orchestrator Agent
(each a separate process, on its own port -- simulating separate services/teams).

Then, acting as an external caller, sends plan_trip requests to the
Orchestrator, which internally calls Weather and Translator over real A2A
JSON-RPC calls with per-target scoped JWTs minted by a2a-passport. Also
demonstrates the auth rejection path (calling the Orchestrator with no token).
"""
import asyncio
import subprocess
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
LOG_DIR = REPO_ROOT / "logs"
LOG_DIR.mkdir(exist_ok=True)

sys.path.insert(0, str(REPO_ROOT))

import httpx

from a2a_passport import OAuth2ClientCredentialsProvider, RemoteAgentClient

IDP_URL = "http://127.0.0.1:9000"
ORCHESTRATOR_URL = "http://127.0.0.1:9203"

PROCESSES = [
    ("mock_idp", ["-m", "a2a_passport.testing.mock_idp"]),
    ("weather_agent", ["examples/demo_agents/weather_agent.py"]),
    ("translator_agent", ["examples/demo_agents/translator_agent.py"]),
    ("orchestrator_agent", ["examples/demo_agents/orchestrator_agent.py"]),
]


def start_all():
    procs = []
    for name, args in PROCESSES:
        log_path = LOG_DIR / f"{name}.log"
        p = subprocess.Popen(
            [sys.executable, *args],
            cwd=str(REPO_ROOT),
            stdout=open(log_path, "w"),
            stderr=subprocess.STDOUT,
        )
        procs.append((name, p))
    return procs


def stop_all(procs):
    for _name, p in procs:
        p.terminate()
    for _name, p in procs:
        p.wait(timeout=5)


async def call_orchestrator_no_token():
    print("\n=== Negative case: calling Orchestrator with NO token ===")
    async with httpx.AsyncClient() as c:
        r = await c.post(
            f"{ORCHESTRATOR_URL}/",
            json={"jsonrpc": "2.0", "id": "1", "method": "message/send", "params": {}},
        )
        print(f"HTTP {r.status_code}: {r.text}")
        assert r.status_code == 401, "expected the orchestrator to reject an unauthenticated call"
    print("PASS -- rejected as expected.")


async def call_orchestrator_plan_trip(city: str):
    print(f"\n=== Plan trip for city='{city}' ===")
    token_provider = OAuth2ClientCredentialsProvider(
        token_url=f"{IDP_URL}/token",
        client_id="external-caller",
        client_secret="external-caller-secret",
    )
    async with RemoteAgentClient(
        ORCHESTRATOR_URL, token_provider, audience="orchestrator-agent"
    ) as client:
        result = await client.send_data({"city": city})
    print("Result:", result)
    return result


async def wait_until_ready(name: str, health_url: str, timeout: float = 20.0):
    deadline = asyncio.get_event_loop().time() + timeout
    async with httpx.AsyncClient() as c:
        while asyncio.get_event_loop().time() < deadline:
            try:
                r = await c.get(health_url, timeout=1.0)
                if r.status_code < 500:
                    print(f"  {name} ready ({health_url})")
                    return
            except httpx.TransportError:
                pass
            await asyncio.sleep(0.3)
    log_path = LOG_DIR / f"{name}.log"
    raise RuntimeError(f"{name} did not become ready within {timeout}s -- check {log_path}")


async def main():
    print("Starting mock IdP + Weather + Translator + Orchestrator agents...")
    procs = start_all()
    try:
        print("Waiting for services to become ready...")
        await wait_until_ready("mock_idp", f"{IDP_URL}/docs")
        await wait_until_ready("weather_agent", "http://127.0.0.1:9201/.well-known/agent-card.json")
        await wait_until_ready(
            "translator_agent", "http://127.0.0.1:9202/.well-known/agent-card.json"
        )
        await wait_until_ready(
            "orchestrator_agent", f"{ORCHESTRATOR_URL}/.well-known/agent-card.json"
        )

        await call_orchestrator_no_token()

        for city in ["Paris", "Tokyo", "Bengaluru", "Nowhere-City"]:
            await call_orchestrator_plan_trip(city)

    finally:
        stop_all(procs)
        print(f"\nAll demo processes stopped. Logs are in {LOG_DIR}/<agent_name>.log")


if __name__ == "__main__":
    asyncio.run(main())
