from __future__ import annotations

import json

import httpx

from grantflow.agents.conformance import run_conformance
from grantflow.mcp.tools import GrantFlowToolClient


def test_run_conformance_executes_agent_contract_with_issued_key():
    requests: list[httpx.Request] = []
    # Track the idempotency seed call so replay/conflict can match it.
    idem_seed: dict = {}
    # Track hitl job poll count so we can flip pending_hitl → done.
    hitl_polls: dict = {"count": 0}

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append(request)
        path = request.url.path
        body: dict = json.loads(request.content) if request.content else {}

        if path == "/agents/onboarding":
            return httpx.Response(
                200,
                json={
                    "status": "ready",
                    "auth_type": "api_key",
                    "tenant_id": "sandbox_conformance",
                    "credential": {"value": "gf_agent_test"},
                },
                request=request,
            )

        if path == "/agents/register":
            return httpx.Response(
                200,
                json={
                    "sample_requests": {
                        "preflight": {"donor_id": "usaid", "tenant_id": "sandbox_conformance"},
                        "generate": {
                            "donor_id": "usaid",
                            "tenant_id": "sandbox_conformance",
                            "input_context": {"project": "Agent conformance"},
                        },
                    }
                },
                request=request,
            )

        if path == "/generate/preflight":
            assert request.headers["X-API-Key"] == "gf_agent_test"
            donor_id = str(body.get("donor_id", "")).strip()

            # invalid_donor check: empty donor_id
            if not donor_id:
                return httpx.Response(
                    400,
                    json={
                        "detail": {
                            "code": "missing_donor_id",
                            "message": "donor_id is required.",
                            "retryable": False,
                            "next_action": "Set donor_id to a supported donor such as 'usaid'.",
                        }
                    },
                    request=request,
                )

            # structured_error_contract check: unsupported donor
            if donor_id == "nonexistent_donor_xyz":
                return httpx.Response(
                    400,
                    json={
                        "detail": {
                            "code": "unsupported_donor",
                            "message": "Donor 'nonexistent_donor_xyz' is not supported.",
                            "retryable": False,
                            "next_action": "Use a supported donor_id such as 'usaid', 'eu', or 'worldbank'.",
                        }
                    },
                    request=request,
                )

            return httpx.Response(200, json={"go_ahead": True}, request=request)

        if path == "/generate":
            assert request.headers["X-API-Key"] == "gf_agent_test"
            req_id = body.get("request_id", "")
            donor = body.get("donor_id", "")

            # hitl_flow check: hitl_enabled=True generates a HITL job
            if body.get("hitl_enabled") is True and not req_id.startswith("grantflow-conformance-idem-"):
                return httpx.Response(
                    200,
                    json={"job_id": "job_hitl", "status": "accepted"},
                    request=request,
                )

            # Idempotency seed: first time we see grantflow-conformance-idem-* request_id
            if req_id.startswith("grantflow-conformance-idem-") and not idem_seed:
                idem_seed["request_id"] = req_id
                idem_seed["donor_id"] = donor
                return httpx.Response(
                    200,
                    json={"job_id": "job_idem", "request_id": req_id},
                    request=request,
                )

            # Idempotency replay: same request_id, same donor
            if req_id == idem_seed.get("request_id") and donor == idem_seed.get("donor_id"):
                return httpx.Response(
                    200,
                    json={"job_id": "job_idem", "request_id": req_id, "idempotent_replay": True},
                    request=request,
                )

            # Idempotency conflict: same request_id, different donor
            if req_id == idem_seed.get("request_id") and donor != idem_seed.get("donor_id"):
                return httpx.Response(
                    409,
                    json={
                        "detail": {
                            "code": "request_id_reused_with_different_payload",
                            "message": "request_id is already used for a different action payload.",
                            "retryable": False,
                            "next_action": "Generate a new request_id.",
                            "request_id": req_id,
                            "scope": "generate",
                        }
                    },
                    request=request,
                )

            # First happy-path generate (sandbox)
            assert body.get("request_id")
            return httpx.Response(200, json={"job_id": "job_123"}, request=request)

        if path == "/status/job_123":
            return httpx.Response(200, json={"status": "done"}, request=request)

        if path == "/status/job_123/quality":
            return httpx.Response(200, json={"grounded": True}, request=request)

        if path == "/status/job_123/events":
            return httpx.Response(200, json={"event_count": 2, "events": []}, request=request)

        # hitl_flow: first poll → pending_hitl, second → done
        if path == "/status/job_hitl":
            hitl_polls["count"] += 1
            if hitl_polls["count"] == 1:
                return httpx.Response(
                    200,
                    json={"status": "pending_hitl", "job_id": "job_hitl", "checkpoint_id": "cp_hitl_1"},
                    request=request,
                )
            return httpx.Response(200, json={"status": "done", "job_id": "job_hitl"}, request=request)

        if path == "/hitl/approve":
            cp = body.get("checkpoint_id", "")
            approved = body.get("approved", False)
            if cp == "cp_hitl_1" and approved is True:
                return httpx.Response(
                    200,
                    json={"status": "approved", "checkpoint_id": "cp_hitl_1"},
                    request=request,
                )
            return httpx.Response(404, json={"detail": "Checkpoint not found"}, request=request)

        if path == "/resume/job_hitl":
            return httpx.Response(
                200,
                json={"status": "accepted", "job_id": "job_hitl"},
                request=request,
            )

        if path == "/ingest/text":
            return httpx.Response(
                200,
                json={
                    "status": "ingested",
                    "chunks_ingested": 3,
                    "namespace": "usaid_ads201",
                    "donor_id": body.get("donor_id", ""),
                    "title": body.get("title", ""),
                },
                request=request,
            )

        # missing_job check: /status/__conformance_missing_<hex>__
        if path.startswith("/status/__conformance_missing_"):
            job_id = path.removeprefix("/status/")
            return httpx.Response(
                404,
                json={
                    "detail": {
                        "code": "job_not_found",
                        "message": f"Job '{job_id}' was not found.",
                        "retryable": False,
                        "next_action": "Check the job_id from your /generate response.",
                        "job_id": job_id,
                    }
                },
                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 = run_conformance(client=client, agent_name="Conformance Runner", timeout_seconds=5)

    assert result["status"] == "passed", json.dumps(result["checks"], indent=2)
    assert result["onboarding"] == {
        "status": "ready",
        "auth_type": "api_key",
        "tenant_id": "sandbox_conformance",
        "credential_issued": True,
    }
    assert result["job_id"] == "job_123"
    assert result["job_status"] == "done"
    assert result["quality_available"] is True
    assert result["event_count"] == 2

    checks = result["checks"]
    assert checks["invalid_donor"] == {"passed": True, "code": "missing_donor_id"}
    assert checks["missing_job"]["passed"] is True
    assert checks["missing_job"]["code"] == "job_not_found"
    assert checks["idempotency_replay"]["passed"] is True
    assert checks["idempotency_replay"]["idempotent_replay"] is True
    assert checks["idempotency_conflict"] == {"passed": True, "code": "request_id_reused_with_different_payload"}

    # New checks
    assert checks["structured_error_contract"]["passed"] is True
    assert checks["structured_error_contract"]["code"] == "unsupported_donor"
    assert checks["structured_error_contract"]["retryable"] is False

    assert checks["hitl_flow"]["passed"] is True
    assert checks["hitl_flow"]["job_id"] == "job_hitl"
    assert checks["hitl_flow"]["checkpoint_id"] == "cp_hitl_1"

    assert checks["ingest_text"]["passed"] is True
    assert checks["ingest_text"]["chunks_ingested"] == 3
    assert checks["ingest_text"]["namespace"] == "usaid_ads201"

    paths = [r.url.path for r in requests]
    # Sandbox happy path
    assert paths[0] == "/agents/onboarding"
    assert paths[1] == "/agents/register"
    assert paths[2] == "/generate/preflight"
    assert paths[3] == "/generate"
    assert paths[4] == "/status/job_123"
    assert paths[5] == "/status/job_123/quality"
    assert paths[6] == "/status/job_123/events"
    # Idempotency seed
    assert paths[7] == "/generate"
    # Conformance checks in declaration order
    assert paths[8] == "/generate/preflight"  # invalid_donor
    assert paths[9].startswith("/status/__conformance_missing_")  # missing_job
    assert paths[10] == "/generate"  # idempotency_replay
    assert paths[11] == "/generate"  # idempotency_conflict
    assert paths[12] == "/generate/preflight"  # structured_error_contract
    assert paths[13] == "/generate"  # hitl_flow: start job
    assert paths[14] == "/status/job_hitl"  # hitl_flow: poll → pending_hitl
    assert paths[15] == "/hitl/approve"  # hitl_flow: approve
    assert paths[16] == "/resume/job_hitl"  # hitl_flow: resume after approval
    assert paths[17] == "/status/job_hitl"  # hitl_flow: poll → done
    assert paths[18] == "/ingest/text"  # ingest_text
