from __future__ import annotations

import json
import logging
from dataclasses import replace
from typing import Any, Awaitable, Callable, Mapping, Sequence

from aiohttp import web
from aiohttp.client_exceptions import ClientConnectionResetError

from ember._internal.exceptions import ProviderAPIError
from keiro.gateway.infra.lossiness import get_lossiness_detector
from keiro.gateway.infra.middleware.context import RequestContext, get_request_context
from keiro.gateway.infra.models_list import list_models
from keiro.gateway.infra.providers.capabilities import list_capabilities
from keiro.gateway.infra.response.sessions import (
    SamplingConfig,
    continue_with_tool_outputs,
    drop_session,
    get_session,
    save_session,
)
from ember.models.providers.anthropic import AnthropicProvider
from ember.models.providers.utils import (
    ResponsesMessage,
    ResponsesRequest,
    parse_responses_request,
)
from ..pipeline.container import Services
from ..pipeline.pipeline import ChatCompletionPipeline, Context
from ..pipeline.steps import PIPELINE_STEPS
from ..domain.errors import RateLimitedError
from ..domain.model import (
    ChatResponse,
    GatewayRequest,
    Message,
    Policy,
    StreamingResponse,
    Usage,
)
from .state import CANCELLED_KEY, SERVICES_KEY
from .serialization import (
    _allow_debug_payload,
    _build_anthropic_params_for_stream,
    _build_stream_done_payload,
    _build_stream_header_payload,
    _extract_tool_calls,
    _messages_payload_from_response,
    _provider_model_name,
    _responses_payload_from_response,
    _rewrite_responses_model,
    _serialize_response,
    _serialize_stream_rbc,
    _tool_use_blocks,
)
from .telemetry import (
    _emit_request_telemetry,
    _extract_rbc_id,
    _extract_usage_delta,
    _finalize_stream_usage,
    _persist_sur,
    _sse_event,
)
from .validation import (
    _ensure_str_list,
    _optional_int,
    _optional_str,
    _require_mapping,
    _require_non_empty_sequence,
    _require_non_empty_str,
    _safe_int,
    _to_bool,
    _to_float,
    _to_int,
)

LOG = logging.getLogger(__name__)
STREAM_LOG = logging.getLogger("ember.gateway.stream")

_VALIDATION_LOG = logging.getLogger("ember.gateway.validation")


def register_routes(app: web.Application) -> None:
    # Primary versioned routes.
    app.router.add_post("/v1/chat/completions", post_chat_completions)
    app.router.add_post("/v1/responses", post_responses)
    app.router.add_post("/v1/messages", post_messages)
    app.router.add_post(
        "/v1/responses/{response_id}/submit_tool_outputs",
        post_responses_submit_tool_outputs,
    )
    app.router.add_get("/v1/streams/{request_id}/resume", get_stream_resume)
    app.router.add_post("/v1/requests/{request_id}/cancel", cancel_request)
    app.router.add_get("/v1/models", get_models)
    app.router.add_get("/v1/capabilities", get_capabilities)

    # Back-compatibility aliases that match the production Takumi gateway,
    # where the base URL omits the version prefix.
    app.router.add_post("/chat/completions", post_chat_completions)
    app.router.add_post("/responses", post_responses)
    app.router.add_post("/messages", post_messages)
    app.router.add_post(
        "/responses/{response_id}/submit_tool_outputs",
        post_responses_submit_tool_outputs,
    )
    app.router.add_get("/streams/{request_id}/resume", get_stream_resume)
    app.router.add_post("/requests/{request_id}/cancel", cancel_request)
    app.router.add_get("/models", get_models)
    app.router.add_get("/capabilities", get_capabilities)
    app.router.add_route("*", "/{tail:.*}", _log_not_found)


async def get_models(request: web.Request) -> web.Response:
    provider = request.query.get("provider")
    status = request.query.get("status")
    data = list_models(provider=provider, status=status)
    return web.json_response(data)


async def get_capabilities(request: web.Request) -> web.Response:
    # gateway1 exposes adapter metadata; gateway2 reuses the same helper
    # which defaults to the configured adapters when called without args.
    payload = list_capabilities()
    return web.json_response(payload)


async def _log_not_found(request: web.Request) -> web.StreamResponse:
    LOG.warning(
        "unhandled_route method=%s path=%s query=%s",
        request.method,
        request.rel_url.path,
        request.rel_url.query_string,
    )
    raise web.HTTPNotFound(text="404: Not Found")


async def _handle_completion_request(
    request: web.Request,
    *,
    endpoint: str,
    convert_fn: Callable[[Mapping[str, Any]], ResponsesRequest],
    serialize_fn: Callable[..., dict[str, Any]],
    stream_fn: Callable[..., Awaitable[web.StreamResponse]],
) -> web.StreamResponse | web.Response:
    """Shared handler for chat/completions, responses, and messages endpoints."""
    services = request.app[SERVICES_KEY]
    request_ctx = get_request_context(request)
    body = request.get("json_body") or await request.json()
    gateway_req = _parse_endpoint_request(
        request, body, endpoint=endpoint, convert_fn=convert_fn,
    )
    pipeline_ctx = _build_context(services, gateway_req, request, request_ctx)
    pipeline = ChatCompletionPipeline(services, PIPELINE_STEPS)
    log_extra: dict[str, Any] = {
        "request_id": pipeline_ctx.request_id,
        "org_id": gateway_req.org_id,
        "model": gateway_req.responses.model,
        "stream": gateway_req.responses.stream,
    }
    if endpoint == "messages":
        log_extra["endpoint"] = endpoint
    LOG.info("pipeline_start", extra=log_extra)
    response = pipeline.run(pipeline_ctx)
    LOG.info(
        "pipeline_complete",
        extra={
            k: v for k, v in {
                "request_id": pipeline_ctx.request_id,
                "provider": pipeline_ctx.chosen_provider,
                "streaming": isinstance(response, StreamingResponse),
                "endpoint": endpoint if endpoint == "messages" else None,
            }.items() if v is not None
        },
    )
    if isinstance(response, StreamingResponse):
        return await stream_fn(request, services, response)
    LOG.info(
        f"{endpoint}_completion_complete",
        extra={
            "request_id": response.id,
            "provider": response.provider,
            "prompt_tokens": response.usage.prompt_tokens,
            "completion_tokens": response.usage.completion_tokens,
        },
    )
    _emit_request_telemetry(pipeline_ctx, response)
    include_debug = _allow_debug_payload(request_ctx)
    payload = serialize_fn(response, include_debug=include_debug)
    _persist_sur(
        services,
        request_id=response.id,
        org_id=gateway_req.org_id,
        price_book_version=str(
            response.raw.get("pricing", {}).get("price_book_version", "p0-0")
        ),
        rbc_id=_extract_rbc_id(response.raw.get("rbc")),
        provider_prompt_tokens=response.usage.prompt_tokens,
        provider_completion_tokens=response.usage.completion_tokens,
        canonical_prompt_tokens=response.usage.prompt_tokens,
        canonical_completion_tokens=response.usage.completion_tokens,
    )
    return web.json_response(payload)


async def _stream_chat_adapter(
    request: web.Request,
    services: Services,  # noqa: ARG001 — matches stream_fn protocol
    response: StreamingResponse,
) -> web.StreamResponse:
    """Adapter: ``_stream_chat_response`` reads services from the request."""
    return await _stream_chat_response(request, response)


async def post_chat_completions(request: web.Request) -> web.StreamResponse | web.Response:
    return await _handle_completion_request(
        request,
        endpoint="chat",
        convert_fn=_chat_payload_to_responses,
        serialize_fn=_serialize_response,
        stream_fn=_stream_chat_adapter,
    )


async def post_responses(request: web.Request) -> web.StreamResponse | web.Response:
    return await _handle_completion_request(
        request,
        endpoint="responses",
        convert_fn=parse_responses_request,
        serialize_fn=_responses_payload_from_response,
        stream_fn=_stream_responses_response,
    )


async def post_messages(request: web.Request) -> web.StreamResponse | web.Response:
    """Handle Anthropic Messages API format requests."""
    return await _handle_completion_request(
        request,
        endpoint="messages",
        convert_fn=_messages_payload_to_responses,
        serialize_fn=_messages_payload_from_response,
        stream_fn=_stream_messages_response,
    )


async def post_responses_submit_tool_outputs(request: web.Request) -> web.StreamResponse:
    services = request.app[SERVICES_KEY]
    response_id = request.match_info.get("response_id")
    if not response_id:
        raise web.HTTPBadRequest(text="missing response_id")
    body = request.get("json_body") or await request.json()
    tool_outputs = body.get("tool_outputs")
    if not isinstance(tool_outputs, list) or not tool_outputs:
        raise web.HTTPBadRequest(text="tool_outputs must be a non-empty array")

    try:
        session = get_session(response_id)
    except ProviderAPIError as exc:
        raise web.HTTPNotFound(text=str(exc)) from exc

    if session.provider != "anthropic":
        raise web.HTTPBadRequest(text="provider does not support submit_tool_outputs")

    api_key_override = session.api_key_override
    provider = AnthropicProvider(api_key_override) if api_key_override else AnthropicProvider()

    try:
        stream = continue_with_tool_outputs(provider, response_id, tool_outputs)
    except ProviderAPIError as exc:
        drop_session(response_id)
        raise web.HTTPBadRequest(text=str(exc)) from exc

    base_response = session.streaming_response
    if base_response is None:
        raise web.HTTPInternalServerError(text="missing streaming session state")

    resumed_response = replace(base_response, stream=stream, stream_source=stream)
    session.streaming_response = resumed_response

    return await _stream_responses_response(request, services, resumed_response)


async def get_stream_resume(request: web.Request) -> web.StreamResponse:
    services = request.app[SERVICES_KEY]
    request_id = request.match_info.get("request_id")
    if not request_id:
        raise web.HTTPBadRequest(text="missing request_id")
    after_param = request.query.get("after", "0")
    try:
        after = int(after_param)
    except (TypeError, ValueError) as exc:
        raise web.HTTPBadRequest(text="invalid 'after' parameter") from exc
    if after < 0:
        raise web.HTTPBadRequest(text="'after' must be non-negative")

    events = list(services.replay.replay_from(request_id, after=after))
    if after > 0 and not events:
        fallback = list(services.replay.replay_from(request_id, after=max(0, after - 1)))
        last_good = max(
            (int(event.get("event_id", 0)) for event in fallback), default=max(0, after - 1)
        )
        return web.json_response(
            {
                "error": "resume_required",
                "resume_required": True,
                "last_good_event_id": last_good,
            },
            status=409,
        )

    stream_resp = web.StreamResponse(status=200, headers={"Content-Type": "text/event-stream"})
    await stream_resp.prepare(request)
    for event in events:
        event_id = int(event.get("event_id", 0))
        prompt_tokens = _safe_int(event.get("prompt_tokens"))
        completion_tokens = _safe_int(event.get("completion_tokens"))
        payload = {
            "delta": event.get("data", ""),
            "usage_delta": {
                "prompt_tokens": prompt_tokens,
                "completion_tokens": completion_tokens,
                "total_tokens": prompt_tokens + completion_tokens,
            },
        }
        body = f"id: {event_id}\ndata: {json.dumps(payload)}\n\n"
        if not await _safe_stream_write(
            stream_resp,
            body.encode("utf-8"),
            request_id=request_id,
            event="resume",
        ):
            return stream_resp
    await _safe_stream_eof(stream_resp, request_id=request_id)
    return stream_resp


def _build_context(
    services: Services,
    request_model: GatewayRequest,
    request: web.Request,
    request_ctx: RequestContext,
) -> Context:
    rid = request.get("request_id") or services.ids.new_request_id()
    return Context(
        req=request_model,
        request_id=rid,
        canonical_model=request_model.model,
        plan={},
        price_book_version="p0-0",
        prompt_messages=list(request_model.messages_for_estimate),
        temperature_hint=_temperature_hint(request_model.responses),
        api_key_id=request_ctx.api_key_id,
    )


def _warn_lossy_fields(
    payload: Mapping[str, Any], endpoint: str, *, strict: bool = False
) -> None:
    """Log a WARNING if the request contains unsupported fields.

    When *strict* is ``True``, raises ``HTTPConflict`` (409) for lossy
    payloads instead of just logging.
    """
    report = get_lossiness_detector().check_request(payload, endpoint)
    if report.is_lossy:
        _VALIDATION_LOG.warning(
            "lossy_request endpoint=%s unsupported_fields=%s"
            " unsupported_features=%s recommendation=%s",
            endpoint,
            report.unsupported_fields,
            report.unsupported_features,
            report.recommendation,
        )
        if strict:
            raise web.HTTPConflict(
                text=json.dumps(report.to_dict()),
                content_type="application/json",
            )


def _parse_endpoint_request(
    request: web.Request,
    body: Mapping[str, Any],
    *,
    endpoint: str,
    convert_fn: Any,
) -> GatewayRequest:
    """Parse an endpoint request body into a GatewayRequest.

    Args:
        request: The aiohttp request.
        body: Raw JSON body.
        endpoint: Endpoint name for lossy-field warnings (``"chat"``,
            ``"responses"``, ``"messages"``).
        convert_fn: Callable that turns the validated payload mapping into a
            ``ResponsesRequest``.
    """
    payload = _require_mapping("body", body)
    strict = request.app[SERVICES_KEY].settings.strict_validation
    _warn_lossy_fields(payload, endpoint, strict=strict)
    responses_req = convert_fn(payload)
    org_id = _resolve_org_id(payload, request)
    policy = _parse_policy(payload.get("policy"))
    session_id_override = _optional_str(payload.get("session_id"), "session_id")
    idem_override = _optional_str(payload.get("idempotency_key"), "idempotency_key")
    return _build_gateway_request(
        org_id=org_id,
        responses_req=responses_req,
        policy=policy,
        session_id=session_id_override,
        idempotency_key=idem_override,
    )


def _messages_payload_to_responses(body: Mapping[str, Any]) -> ResponsesRequest:
    """Convert Anthropic Messages API payload to ResponsesRequest.

    Key differences from OpenAI chat format:
    - system prompt is a top-level field, not a message
    - max_tokens is required (Anthropic) vs max_output_tokens (OpenAI)
    - content blocks use different type names
    """
    model = _require_non_empty_str("model", body.get("model"))
    raw_messages = _require_non_empty_sequence("messages", body.get("messages"))

    # Build messages array, prepending system as a system message if present
    responses_messages: list[dict[str, object]] = []

    # Anthropic's system is a top-level field - convert to a system message
    # We preserve the original format (type: "text") for lossless passthrough
    system_value = body.get("system")
    if system_value is not None:
        if isinstance(system_value, str) and system_value.strip():
            responses_messages.append(
                {
                    "role": "system",
                    "content": [{"type": "text", "text": system_value.strip()}],
                }
            )
        elif isinstance(system_value, Sequence) and not isinstance(system_value, str):
            # System can be an array of content blocks in Anthropic format
            system_content = _normalize_messages_content(system_value, "system")
            if system_content:
                responses_messages.append({"role": "system", "content": system_content})

    # Process user/assistant messages
    for index, entry in enumerate(raw_messages):
        responses_messages.append(_normalize_messages_message(entry, index))

    # Build the payload for parse_responses_request
    converted: dict[str, object] = {"model": model, "messages": responses_messages}

    # Handle max_tokens (Anthropic) -> max_output_tokens (internal)
    max_tokens = _optional_int(body.get("max_tokens"), "max_tokens")
    if max_tokens is not None:
        converted["max_output_tokens"] = max_tokens

    # Pass through common parameters
    for key in (
        "temperature",
        "top_p",
        "stop_sequences",
        "tools",
        "tool_choice",
        "metadata",
        "stream",
    ):
        if key in body:
            converted[key] = body[key]

    # Handle Anthropic's stop_sequences -> stop (OpenAI naming)
    if "stop_sequences" in body and "stop" not in converted:
        converted["stop"] = body["stop_sequences"]

    converted["stream"] = bool(body.get("stream"))

    return parse_responses_request(converted)


def _normalize_messages_message(entry: object, index: int) -> dict[str, object]:
    """Normalize a single Anthropic message to internal format."""
    message = _require_mapping(f"messages[{index}]", entry)
    role_raw = message.get("role")
    role = role_raw.strip() if isinstance(role_raw, str) else ""
    if not role:
        raise web.HTTPBadRequest(text=f"messages[{index}].role is required")
    content = _normalize_messages_content(message.get("content"), f"messages[{index}]")
    return {"role": role, "content": content}


def _normalize_messages_content(raw: object, path: str) -> list[dict[str, object]]:
    """Normalize Anthropic content format - preserving original block types.

    This function preserves Anthropic's native content block types for lossless
    passthrough when routing to Anthropic models. The gateway's internal format
    accepts both Anthropic-style blocks (type: "text") and OpenAI-style blocks
    (type: "input_text").

    Anthropic block types preserved:
    - {"type": "text", "text": "..."} - text content
    - {"type": "image", "source": {...}} - images
    - {"type": "tool_use", ...} - tool invocations
    - {"type": "tool_result", ...} - tool results
    """
    if raw is None:
        return []
    if isinstance(raw, str):
        # Plain strings become text blocks (Anthropic format)
        return [{"type": "text", "text": raw}]
    if isinstance(raw, Mapping):
        # Single block - pass through preserving type
        return [dict(raw)]
    if isinstance(raw, Sequence) and not isinstance(raw, (str, bytes, bytearray)):
        parts: list[dict[str, object]] = []
        for idx, item in enumerate(raw):
            if isinstance(item, str):
                parts.append({"type": "text", "text": item})
            elif isinstance(item, Mapping):
                # Pass through all block types exactly as received
                parts.append(dict(item))
            else:
                raise web.HTTPBadRequest(text=f"{path}[{idx}] must be a string or object")
        return parts
    raise web.HTTPBadRequest(text=f"{path}.content must be a string, object, or array")


def _build_gateway_request(
    *,
    org_id: str,
    responses_req: ResponsesRequest,
    policy: Policy | None,
    session_id: str | None,
    idempotency_key: str | None,
) -> GatewayRequest:
    return GatewayRequest(
        org_id=str(org_id),
        responses=responses_req,
        messages_for_estimate=_messages_for_estimate(responses_req),
        session_id=session_id or responses_req.session_id,
        idempotency_key=idempotency_key or responses_req.idempotency_key,
        policy=policy,
    )


def _chat_payload_to_responses(body: Mapping[str, Any]) -> ResponsesRequest:
    model = _require_non_empty_str("model", body.get("model"))
    raw_messages = _require_non_empty_sequence("messages", body.get("messages"))
    responses_messages: list[dict[str, object]] = [
        _normalize_chat_message(entry, index) for index, entry in enumerate(raw_messages)
    ]
    payload: dict[str, object] = {
        key: value for key, value in body.items() if key not in {"messages", "max_tokens"}
    }
    max_tokens = _optional_int(body.get("max_tokens"), "max_tokens")
    if max_tokens is not None and "max_output_tokens" not in payload:
        payload["max_output_tokens"] = max_tokens
    payload["model"] = model
    payload["messages"] = responses_messages
    payload["stream"] = bool(body.get("stream"))
    return parse_responses_request(payload)


def _normalize_chat_message(entry: object, index: int) -> dict[str, object]:
    message = _require_mapping(f"messages[{index}]", entry)
    role_raw = message.get("role")
    role = role_raw.strip() if isinstance(role_raw, str) else ""
    if not role:
        raise web.HTTPBadRequest(text=f"messages[{index}].role is required")
    content = _normalize_message_content(message.get("content"), index)
    result: dict[str, object] = {"role": role, "content": content}
    if "tool_calls" in message:
        result["tool_calls"] = message["tool_calls"]
    if "tool_call_id" in message:
        result["tool_call_id"] = message["tool_call_id"]
    if "name" in message:
        result["name"] = message["name"]
    return result


def _normalize_message_content(raw: object, message_index: int) -> list[dict[str, object]]:
    if raw is None:
        return []
    if isinstance(raw, str):
        return [{"type": "input_text", "text": raw}]
    if isinstance(raw, Mapping):
        return [_mapping_to_part(raw, f"messages[{message_index}].content")]
    if isinstance(raw, Sequence) and not isinstance(raw, (str, bytes, bytearray)):
        parts: list[dict[str, object]] = []
        for idx, item in enumerate(raw):
            if isinstance(item, str):
                parts.append({"type": "input_text", "text": item})
                continue
            if isinstance(item, Mapping):
                parts.append(_mapping_to_part(item, f"messages[{message_index}].content[{idx}]"))
                continue
            raise web.HTTPBadRequest(
                text=f"messages[{message_index}].content[{idx}] must be a string or object"
            )
        return parts
    raise web.HTTPBadRequest(
        text=f"messages[{message_index}].content must be a string, object, or array"
    )


def _mapping_to_part(payload: Mapping[str, object], field: str) -> dict[str, object]:
    part_type = _optional_str(payload.get("type"), f"{field}.type") or "input_text"
    part: dict[str, object] = {"type": part_type}
    for key, value in payload.items():
        if key == "type":
            continue
        if not isinstance(key, str):
            raise web.HTTPBadRequest(text=f"{field} keys must be strings")
        part[key] = value
    return part


def _messages_for_estimate(responses_req: ResponsesRequest) -> list[Message]:
    messages: list[Message] = []
    if responses_req.instructions:
        messages.append(Message(role="system", content=str(responses_req.instructions)))
    input_section = responses_req.input
    if isinstance(input_section, str):
        messages.append(Message(role="user", content=input_section))
    elif isinstance(input_section, Sequence):
        for entry in input_section:
            if isinstance(entry, ResponsesMessage):
                rendered = _render_content_parts(entry.content)
                if rendered:
                    messages.append(Message(role=entry.role, content=rendered))
    for msg in responses_req.messages:
        rendered = _render_content_parts(msg.content)
        if rendered:
            messages.append(Message(role=msg.role, content=rendered))
    return messages


def _temperature_hint(responses_req: ResponsesRequest) -> float | None:
    raw = responses_req.original_payload.get("temperature")
    if raw is None:
        return None
    if isinstance(raw, (int, float)):
        return float(raw)
    if isinstance(raw, str):
        try:
            return float(raw)
        except ValueError as exc:  # pragma: no cover
            raise web.HTTPBadRequest(text="invalid temperature parameter") from exc
    raise web.HTTPBadRequest(text="invalid temperature parameter")


def _render_content_parts(parts: Sequence[Any]) -> str:
    fragments: list[str] = []
    for part in parts:
        part_type = getattr(part, "type", None)
        fields = getattr(part, "fields", {})
        text_value = None
        if isinstance(fields, Mapping):
            candidate = fields.get("text")
            if isinstance(candidate, str):
                text_value = candidate
        if text_value is not None:
            fragments.append(text_value)
            continue
        payload: dict[str, Any] = {}
        if part_type:
            payload["type"] = part_type
        if isinstance(fields, Mapping):
            payload.update(fields)
        fragments.append(json.dumps(payload, ensure_ascii=False))
    return "".join(fragments)


def _resolve_org_id(body: Mapping[str, Any], request: web.Request) -> str:
    candidate = body.get("org_id")
    if candidate is not None:
        normalized = _optional_str(candidate, "org_id")
        if normalized:
            return normalized
    ctx = get_request_context(request)
    if not ctx.org_id:
        raise web.HTTPBadRequest(text="missing org_id")
    return str(ctx.org_id)


async def _stream_chat_response(
    request: web.Request, response: StreamingResponse
) -> web.StreamResponse:
    services = request.app[SERVICES_KEY]
    ctx = get_request_context(request)
    org_id = ctx.org_id
    include_debug = _allow_debug_payload(ctx)
    STREAM_LOG.info(
        "stream_start %s",
        json.dumps(
            {
                "org_id": org_id,
                "request_id": response.id,
                "canonical_model": response.model,
                "provider": response.provider,
                "provider_model": response.provider_model,
                "stream_variant": "chat_completions",
                "completion_budget": response.completion_budget,
                "plan": response.plan,
            },
            ensure_ascii=False,
            default=str,
        ),
    )
    LOG.info(
        "stream_chat_start",
        extra={
            "request_id": response.id,
            "provider": response.provider,
            "provider_model": response.provider_model,
        },
    )
    stream_resp = web.StreamResponse(
        status=202,
        headers={
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        },
    )
    await stream_resp.prepare(request)

    metadata = _build_stream_header_payload(response, include_debug=include_debug)
    event_seq = 0
    if not await _safe_stream_write(
        stream_resp,
        _sse_event("header", metadata, event_seq),
        request_id=response.id,
        event="header",
    ):
        return stream_resp

    collected_text: list[str] = []
    recorded_prompt_tokens = 0
    recorded_completion_tokens = 0
    recorded_cost = 0.0
    cancelled = request.app[CANCELLED_KEY]
    final_usage: Usage | None = None
    early_return = False

    def _cancelled() -> bool:
        return response.id in cancelled

    try:
        if _cancelled():
            if not await _safe_stream_write(
                stream_resp,
                _sse_event(
                    "error",
                    {
                        "code": "cancelled",
                        "message": "Request cancelled by client",
                        "request_id": response.id,
                    },
                    event_seq + 1,
                ),
                request_id=response.id,
                event="cancelled",
            ):
                early_return = True
                return stream_resp
            await _safe_stream_eof(stream_resp, request_id=response.id)
            early_return = True
            return stream_resp
        for chunk in response.stream:
            if _cancelled():
                event_seq += 1
                if not await _safe_stream_write(
                    stream_resp,
                    _sse_event(
                        "error",
                        {
                            "code": "cancelled",
                            "message": "Request cancelled by client",
                            "request_id": response.id,
                        },
                        event_seq,
                    ),
                    request_id=response.id,
                    event="cancelled",
                ):
                    early_return = True
                    return stream_resp
                await _safe_stream_eof(stream_resp, request_id=response.id)
                early_return = True
                return stream_resp
            event_seq += 1
            text = str(chunk)
            collected_text.append(text)
            if not await _safe_stream_write(
                stream_resp,
                _sse_event("delta", {"text": text}, event_seq),
                request_id=response.id,
                event="delta",
            ):
                early_return = True
                return stream_resp

            usage_delta = _extract_usage_delta(text)
            if usage_delta is not None:
                prompt_delta, completion_delta = usage_delta
                cost_delta = float(
                    services.pricing.calculate(
                        model=response.provider_model,
                        prompt_tokens=prompt_delta,
                        completion_tokens=completion_delta,
                        price_book_version=response.price_book_version,
                        billing_mode=response.billing_mode,
                    )
                )
                if completion_delta and not services.tpm.consume_output(
                    response.org_id, completion_delta
                ):
                    event_seq += 1
                    if not await _safe_stream_write(
                        stream_resp,
                        _sse_event(
                            "error",
                            {
                                "code": "rate_limit",
                                "scope": "tpm",
                                "phase": "stream_chunk",
                                "message": "TPM limit exceeded (stream chunk)",
                            },
                            event_seq,
                        ),
                        request_id=response.id,
                        event="rate_limit",
                    ):
                        early_return = True
                        return stream_resp
                    await _safe_stream_eof(stream_resp, request_id=response.id)
                    early_return = True
                    return stream_resp

                services.billing.record_usage_event(
                    response.fbr_id,
                    event_seq,
                    prompt_delta,
                    completion_delta,
                    cost_delta,
                )
                services.replay.append(
                    response.id,
                    event_id=event_seq,
                    data=text,
                    prompt_tokens=prompt_delta,
                    completion_tokens=completion_delta,
                    ts=services.clock.now_s(),
                )
                recorded_prompt_tokens += prompt_delta
                recorded_completion_tokens += completion_delta
                recorded_cost += cost_delta
    except Exception as exc:
        STREAM_LOG.error(
            "stream_error %s",
            json.dumps(
                {
                    "org_id": org_id,
                    "request_id": response.id,
                    "provider": response.provider,
                    "provider_model": response.provider_model,
                    "reason": str(exc),
                },
                ensure_ascii=False,
                default=str,
            ),
        )
        event_seq += 1
        if await _safe_stream_write(
            stream_resp,
            _sse_event(
                "error",
                {"code": "provider_error", "message": str(exc)},
                event_seq,
            ),
            request_id=response.id,
            event="provider_error",
        ):
            await _safe_stream_eof(stream_resp, request_id=response.id)
        early_return = True
        raise
    finally:
        try:
            final_usage = _finalize_stream_usage(
                services,
                response,
                recorded_prompt_tokens=recorded_prompt_tokens,
                recorded_completion_tokens=recorded_completion_tokens,
                recorded_cost=recorded_cost,
            )
        except RateLimitedError as err:
            event_seq += 1
            if await _safe_stream_write(
                stream_resp,
                _sse_event(
                    "error",
                    {
                        "code": "rate_limit",
                        "scope": err.scope,
                        "phase": err.phase,
                        "message": str(err),
                    },
                    event_seq,
                ),
                request_id=response.id,
                event="rate_limit",
            ):
                await _safe_stream_eof(stream_resp, request_id=response.id)
            early_return = True
        if final_usage is not None and not early_return:
            event_seq += 1
            done_payload = _build_stream_done_payload(
                response,
                final_usage,
                text="".join(collected_text),
                include_debug=include_debug,
            )
            if not await _safe_stream_write(
                stream_resp,
                _sse_event("done", done_payload, event_seq),
                request_id=response.id,
                event="done",
            ):
                early_return = True
        if not early_return:
            await _safe_stream_eof(stream_resp, request_id=response.id)
        cancelled.discard(response.id)
    if not early_return:
        STREAM_LOG.info(
            "stream_completed %s",
            json.dumps(
                {
                    "org_id": org_id,
                    "request_id": response.id,
                    "provider": response.provider,
                    "provider_model": response.provider_model,
                    "stream_variant": "chat_completions",
                    "usage": None
                    if final_usage is None
                    else {
                        "prompt_tokens": final_usage.prompt_tokens,
                        "completion_tokens": final_usage.completion_tokens,
                        "total_tokens": final_usage.total_tokens,
                        "cost_usd": final_usage.cost_usd,
                    },
                    "text_preview": "".join(collected_text)[:120],
                },
                ensure_ascii=False,
                default=str,
            ),
        )

    return stream_resp


_STREAM_DISCONNECT_ERRORS = (
    ConnectionResetError,
    ConnectionAbortedError,
    BrokenPipeError,
    ClientConnectionResetError,
)


async def _safe_stream_write(
    stream_resp: web.StreamResponse,
    payload: bytes,
    *,
    request_id: str,
    event: str,
) -> bool:
    try:
        await stream_resp.write(payload)
        return True
    except _STREAM_DISCONNECT_ERRORS as exc:
        LOG.info(
            "stream_client_disconnected",
            extra={"request_id": request_id, "event": event, "error": type(exc).__name__},
        )
        return False


async def _safe_stream_eof(
    stream_resp: web.StreamResponse,
    *,
    request_id: str,
    event: str = "eof",
) -> bool:
    try:
        await stream_resp.write_eof()
        return True
    except _STREAM_DISCONNECT_ERRORS as exc:
        LOG.info(
            "stream_client_disconnected",
            extra={"request_id": request_id, "event": event, "error": type(exc).__name__},
        )
        return False


async def _stream_messages_response(
    request: web.Request,
    services: Services,
    response: StreamingResponse,
) -> web.StreamResponse:
    """Stream response in Anthropic Messages API SSE format.

    Anthropic streaming events:
        event: message_start
        data: {"type": "message_start", "message": {...}}

        event: content_block_start
        data: {"type": "content_block_start", "index": 0, "content_block": {...}}

        event: content_block_delta
        data: {"type": "content_block_delta", "index": 0, "delta": {...}}

        event: content_block_stop
        data: {"type": "content_block_stop", "index": 0}

        event: message_delta
        data: {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {...}}

        event: message_stop
        data: {"type": "message_stop"}
    """
    stream_resp = web.StreamResponse(
        status=200,
        headers={
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        },
    )
    await stream_resp.prepare(request)

    LOG.info(
        "stream_messages_start",
        extra={
            "request_id": response.id,
            "provider": response.provider,
            "provider_model": response.provider_model,
        },
    )

    # Emit message_start event
    message_start = {
        "type": "message_start",
        "message": {
            "id": response.id,
            "type": "message",
            "role": "assistant",
            "content": [],
            "model": response.model,
            "stop_reason": None,
            "stop_sequence": None,
            "usage": {"input_tokens": response.prompt_estimate, "output_tokens": 0},
        },
    }
    await _write_messages_event(stream_resp, "message_start", message_start, response.id)

    # Emit content_block_start for the text block
    block_start = {
        "type": "content_block_start",
        "index": 0,
        "content_block": {"type": "text", "text": ""},
    }
    await _write_messages_event(stream_resp, "content_block_start", block_start, response.id)

    collected_text: list[str] = []
    recorded_prompt_tokens = 0
    recorded_completion_tokens = 0
    recorded_cost = 0.0
    final_usage: Usage | None = None

    cancelled = request.app[CANCELLED_KEY]
    skip_terminal_events = False

    def _cancelled() -> bool:
        return response.id in cancelled

    try:
        if _cancelled():
            error_event = {
                "type": "error",
                "error": {"type": "cancelled", "message": "Request cancelled by client"},
            }
            await _write_messages_event(stream_resp, "error", error_event, response.id)
            skip_terminal_events = True
            return stream_resp

        for chunk in response.stream:
            if _cancelled():
                skip_terminal_events = True
                break

            if isinstance(chunk, str) and chunk:
                collected_text.append(chunk)
                delta_event = {
                    "type": "content_block_delta",
                    "index": 0,
                    "delta": {"type": "text_delta", "text": chunk},
                }
                await _write_messages_event(
                    stream_resp, "content_block_delta", delta_event, response.id
                )

        # Emit content_block_stop
        block_stop = {"type": "content_block_stop", "index": 0}
        await _write_messages_event(stream_resp, "content_block_stop", block_stop, response.id)

    except _STREAM_DISCONNECT_ERRORS:
        LOG.info(
            "stream_client_disconnected",
            extra={"request_id": response.id, "event": "messages_stream"},
        )
        skip_terminal_events = True
    except Exception as exc:
        LOG.exception(
            "stream_error",
            extra={"request_id": response.id, "error": str(exc)},
        )
        error_event = {
            "type": "error",
            "error": {"type": "server_error", "message": str(exc)},
        }
        try:
            await _write_messages_event(stream_resp, "error", error_event, response.id)
        except _STREAM_DISCONNECT_ERRORS:
            pass
        skip_terminal_events = True
    finally:
        try:
            final_usage = _finalize_stream_usage(
                services,
                response,
                recorded_prompt_tokens=recorded_prompt_tokens,
                recorded_completion_tokens=recorded_completion_tokens,
                recorded_cost=recorded_cost,
            )
        except RateLimitedError as err:
            if not skip_terminal_events:
                rate_event = {
                    "type": "error",
                    "error": {
                        "type": "rate_limit",
                        "message": str(err),
                        "scope": err.scope,
                        "phase": err.phase,
                    },
                }
                try:
                    await _write_messages_event(stream_resp, "error", rate_event, response.id)
                except _STREAM_DISCONNECT_ERRORS:
                    pass
            skip_terminal_events = True

        if not skip_terminal_events:
            if final_usage is not None:
                recorded_completion_tokens = final_usage.completion_tokens

            # Emit message_delta with stop_reason and usage
            message_delta = {
                "type": "message_delta",
                "delta": {"stop_reason": "end_turn", "stop_sequence": None},
                "usage": {"output_tokens": recorded_completion_tokens},
            }
            await _write_messages_event(stream_resp, "message_delta", message_delta, response.id)

            # Emit message_stop
            message_stop_evt = {"type": "message_stop"}
            await _write_messages_event(stream_resp, "message_stop", message_stop_evt, response.id)

        await _safe_stream_eof(stream_resp, request_id=response.id, event="messages_stream_eof")
        cancelled.discard(response.id)
    return stream_resp


async def _write_messages_event(
    stream_resp: web.StreamResponse,
    event_type: str,
    data: dict[str, Any],
    request_id: str,
) -> bool:
    """Write an SSE event in Anthropic Messages API format."""
    payload = f"event: {event_type}\ndata: {json.dumps(data)}\n\n"
    try:
        await stream_resp.write(payload.encode("utf-8"))
        return True
    except _STREAM_DISCONNECT_ERRORS as exc:
        LOG.info(
            "stream_client_disconnected",
            extra={"request_id": request_id, "event": event_type, "error": type(exc).__name__},
        )
        return False


async def _stream_responses_response(
    request: web.Request,
    services: Services,
    response: StreamingResponse,
) -> web.StreamResponse:
    ctx = get_request_context(request)
    org_id = ctx.org_id
    include_debug = _allow_debug_payload(ctx)
    stream_resp = web.StreamResponse(
        status=202,
        headers={
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        },
    )
    await stream_resp.prepare(request)
    LOG.info(
        "stream_responses_start",
        extra={
            "request_id": response.id,
            "provider": response.provider,
            "provider_model": response.provider_model,
        },
    )
    STREAM_LOG.info(
        "stream_start %s",
        json.dumps(
            {
                "org_id": org_id,
                "request_id": response.id,
                "canonical_model": response.model,
                "provider": response.provider,
                "provider_model": response.provider_model,
                "stream_variant": "responses",
                "completion_budget": response.completion_budget,
                "plan": response.plan,
            },
            ensure_ascii=False,
            default=str,
        ),
    )

    is_anthropic_provider = response.provider == "anthropic"
    responses_req = response.responses_request
    provider_model_name = _provider_model_name(response.provider_model)
    anthropic_session_saved = False

    collected_text: list[str] = []
    recorded_prompt_tokens = 0
    recorded_completion_tokens = 0
    recorded_cost = 0.0
    responses_completed_emitted = False
    event_seq = 0

    def _extract_output_text(response_payload: Any) -> str | None:
        if not isinstance(response_payload, Mapping):
            return None
        text_value = response_payload.get("output_text")
        if isinstance(text_value, str):
            stripped = text_value.strip()
            if stripped:
                return stripped
        output_items = response_payload.get("output")
        if isinstance(output_items, Sequence):
            for item in output_items:
                if not isinstance(item, Mapping):
                    continue
                content = item.get("content")
                if not isinstance(content, Sequence):
                    continue
                for part in content:
                    if not isinstance(part, Mapping):
                        continue
                    if part.get("type") != "output_text":
                        continue
                    text_val = part.get("text")
                    if isinstance(text_val, str):
                        stripped = text_val.strip()
                        if stripped:
                            return stripped
        return None

    cancelled = request.app[CANCELLED_KEY]

    def _cancelled() -> bool:
        return response.id in cancelled

    try:
        if _cancelled():
            cancel_payload = {
                "type": "response.error",
                "error": {"code": "cancelled", "message": "Request cancelled by client"},
            }
            if not await _safe_stream_write(
                stream_resp,
                f"data: {json.dumps(cancel_payload, ensure_ascii=False)}\n\n".encode("utf-8"),
                request_id=response.id,
                event="cancelled",
            ):
                return stream_resp
            if is_anthropic_provider:
                drop_session(response.id)
            await _safe_stream_eof(stream_resp, request_id=response.id)
            return stream_resp
        injected_text = False
        for event_seq, chunk in enumerate(response.stream, start=1):
            if _cancelled():
                cancel_payload = {
                    "type": "response.error",
                    "error": {"code": "cancelled", "message": "Request cancelled by client"},
                }
                if not await _safe_stream_write(
                    stream_resp,
                    f"data: {json.dumps(cancel_payload, ensure_ascii=False)}\n\n".encode("utf-8"),
                    request_id=response.id,
                    event="cancelled",
                ):
                    return stream_resp
                if is_anthropic_provider:
                    drop_session(response.id)
                await _safe_stream_eof(stream_resp, request_id=response.id)
                return stream_resp
            payload_text = chunk.decode("utf-8") if isinstance(chunk, bytes) else str(chunk)
            try:
                parsed_payload = json.loads(payload_text)
            except json.JSONDecodeError:
                STREAM_LOG.warning(
                    "Upstream SSE event is not valid JSON (dropped): %.200s",
                    payload_text,
                )
                parsed_payload = None
            if not isinstance(parsed_payload, dict):
                parsed_payload = None

            if parsed_payload is not None:
                event_type = str(parsed_payload.get("type") or "")
                if event_type == "response.error":
                    message = "provider error"
                    error_obj = parsed_payload.get("error")
                    if isinstance(error_obj, Mapping):
                        message = str(error_obj.get("message") or message)
                    elif isinstance(error_obj, str):
                        message = error_obj
                    raise RuntimeError(message)
                if event_type == "response.completed":
                    responses_completed_emitted = True
                    if not collected_text and not injected_text:
                        completion_text = _extract_output_text(parsed_payload.get("response"))
                        if completion_text:
                            synthetic_delta = json.dumps(
                                {"type": "response.output_text.delta", "delta": completion_text},
                                ensure_ascii=False,
                            )
                            if not await _safe_stream_write(
                                stream_resp,
                                f"data: {synthetic_delta}\n\n".encode("utf-8"),
                                request_id=response.id,
                                event="output_text.delta",
                            ):
                                return stream_resp
                            collected_text.append(completion_text)
                            injected_text = True
                            if not await _safe_stream_write(
                                stream_resp,
                                b'data: {"type": "response.output_text.done"}\n\n',
                                request_id=response.id,
                                event="output_text.done",
                            ):
                                return stream_resp
                if event_type == "response.required_action" and is_anthropic_provider:
                    if responses_req is None:
                        raise RuntimeError("Responses payload unavailable for Anthropic stream")
                    tool_calls = _extract_tool_calls(parsed_payload)
                    if not tool_calls:
                        raise RuntimeError("Anthropic required_action missing tool calls")
                    params = _build_anthropic_params_for_stream(responses_req, provider_model_name)
                    if params is None:
                        raise RuntimeError("Failed to rebuild Anthropic request for tool hand-off")
                    messages = list(params.messages)
                    messages.append({"role": "assistant", "content": _tool_use_blocks(tool_calls)})
                    sampling_cfg = SamplingConfig.from_params(params)
                    save_session(
                        response.id,
                        provider=response.provider,
                        model=provider_model_name,
                        messages=messages,
                        tools=list(params.tools),
                        system=list(params.system or ()),
                        max_tokens=params.max_tokens,
                        sampling=sampling_cfg,
                        api_key_override=response.api_key_override,
                        responses_request=responses_req,
                        streaming_response=response,
                    )
                    anthropic_session_saved = True

            if parsed_payload is not None:
                if _rewrite_responses_model(parsed_payload, response.model):
                    payload_text = json.dumps(
                        parsed_payload, ensure_ascii=False
                    )

            if not await _safe_stream_write(
                stream_resp,
                f"data: {payload_text}\n\n".encode("utf-8"),
                request_id=response.id,
                event="chunk",
            ):
                return stream_resp

            if parsed_payload is None:
                continue

            delta_val = parsed_payload.get("delta")
            if isinstance(delta_val, str):
                collected_text.append(delta_val)

            usage_info = parsed_payload.get("usage_delta")
            if not isinstance(usage_info, Mapping):
                data_section = parsed_payload.get("data")
                if isinstance(data_section, Mapping):
                    usage_info = data_section.get("usage_delta")  # type: ignore[assignment]
            if isinstance(usage_info, Mapping):
                prompt_delta = _safe_int(
                    usage_info.get("prompt_tokens") or usage_info.get("input_tokens")
                )
                completion_delta = _safe_int(
                    usage_info.get("completion_tokens") or usage_info.get("output_tokens")
                )
                if completion_delta and not services.tpm.consume_output(
                    response.org_id, completion_delta
                ):
                    rate_error = {
                        "type": "response.error",
                        "error": {
                            "code": "rate_limit",
                            "message": "TPM limit exceeded (stream chunk)",
                        },
                    }
                    if not await _safe_stream_write(
                        stream_resp,
                        f"data: {json.dumps(rate_error, ensure_ascii=False)}\n\n".encode("utf-8"),
                        request_id=response.id,
                        event="rate_limit",
                    ):
                        return stream_resp
                    await _safe_stream_eof(stream_resp, request_id=response.id)
                    return stream_resp

                if prompt_delta or completion_delta:
                    cost_delta = float(
                        services.pricing.calculate(
                            model=response.provider_model,
                            prompt_tokens=prompt_delta,
                            completion_tokens=completion_delta,
                            price_book_version=response.price_book_version,
                            billing_mode=response.billing_mode,
                        )
                    )
                    recorded_prompt_tokens += prompt_delta
                    recorded_completion_tokens += completion_delta
                    recorded_cost += cost_delta
                    services.billing.record_usage_event(
                        response.fbr_id,
                        event_seq,
                        prompt_delta,
                        completion_delta,
                        cost_delta,
                    )
                    services.replay.append(
                        response.id,
                        event_id=event_seq,
                        data=payload_text,
                        prompt_tokens=prompt_delta,
                        completion_tokens=completion_delta,
                        ts=services.clock.now_s(),
                    )
    except Exception as exc:
        STREAM_LOG.error(
            "stream_error %s",
            json.dumps(
                {
                    "org_id": org_id,
                    "request_id": response.id,
                    "provider": response.provider,
                    "provider_model": response.provider_model,
                    "reason": str(exc),
                },
                ensure_ascii=False,
                default=str,
            ),
        )
        error_payload = {
            "type": "response.error",
            "error": {"message": str(exc)},
        }
        if await _safe_stream_write(
            stream_resp,
            f"data: {json.dumps(error_payload, ensure_ascii=False)}\n\n".encode("utf-8"),
            request_id=response.id,
            event="provider_error",
        ):
            await _safe_stream_eof(stream_resp, request_id=response.id)
        if is_anthropic_provider:
            drop_session(response.id)
        raise
    finally:
        cancelled.discard(response.id)
    try:
        final_response = None
        if hasattr(response.stream_source, "final_response"):
            final_response = response.stream_source.final_response  # type: ignore[attr-defined]
        elif hasattr(response.stream, "final_response"):
            final_response = response.stream.final_response  # type: ignore[attr-defined]

        try:
            final_usage = _finalize_stream_usage(
                services,
                response,
                recorded_prompt_tokens=recorded_prompt_tokens,
                recorded_completion_tokens=recorded_completion_tokens,
                recorded_cost=recorded_cost,
            )
        except RateLimitedError as err:
            rate_error = {
                "type": "response.error",
                "error": {
                    "code": "rate_limit",
                    "scope": err.scope,
                    "phase": err.phase,
                    "message": str(err),
                },
            }
            if await _safe_stream_write(
                stream_resp,
                f"data: {json.dumps(rate_error, ensure_ascii=False)}\n\n".encode("utf-8"),
                request_id=response.id,
                event="rate_limit",
            ):
                await _safe_stream_eof(stream_resp, request_id=response.id)
            return stream_resp

        if not responses_completed_emitted:
            response_obj: dict[str, Any] = {}
            if isinstance(final_response, Mapping):
                response_obj = dict(final_response)
            elif hasattr(response.stream_source, "final_response") and isinstance(  # type: ignore[union-attr]
                response.stream_source.final_response,
                Mapping,  # type: ignore[union-attr]
            ):
                response_obj = dict(response.stream_source.final_response)  # type: ignore[union-attr]
            response_obj.setdefault("id", f"resp_{response.id}")
            response_obj["model"] = response.model
            response_obj.setdefault("status", "completed")

            if final_usage is not None:
                usage_dict: dict[str, object] = {
                    "prompt_tokens": final_usage.prompt_tokens,
                    "completion_tokens": final_usage.completion_tokens,
                    "total_tokens": final_usage.total_tokens,
                }
                if include_debug:
                    usage_dict["cost_usd"] = final_usage.cost_usd
                response_obj.setdefault("usage", usage_dict)

            if collected_text and not response_obj.get("output"):
                response_obj["output"] = [
                    {
                        "role": "assistant",
                        "content": [
                            {
                                "type": "output_text",
                                "text": "".join(collected_text),
                            }
                        ],
                        "status": "completed",
                    }
                ]

            completion_payload: dict[str, Any] = {
                "type": "response.completed",
                "response": response_obj,
            }
            if not await _safe_stream_write(
                stream_resp,
                f"data: {json.dumps(completion_payload, ensure_ascii=False)}\n\n".encode("utf-8"),
                request_id=response.id,
                event="response.completed",
            ):
                return stream_resp

        if await _safe_stream_write(
            stream_resp,
            b"data: [DONE]\n\n",
            request_id=response.id,
            event="done",
        ):
            await _safe_stream_eof(stream_resp, request_id=response.id)
        STREAM_LOG.info(
            "stream_completed %s",
            json.dumps(
                {
                    "org_id": org_id,
                    "request_id": response.id,
                    "provider": response.provider,
                    "provider_model": response.provider_model,
                    "stream_variant": "responses",
                    "usage": None
                    if final_usage is None
                    else {
                        "prompt_tokens": final_usage.prompt_tokens,
                        "completion_tokens": final_usage.completion_tokens,
                        "total_tokens": final_usage.total_tokens,
                        "cost_usd": final_usage.cost_usd,
                    },
                    "text_preview": "".join(collected_text)[:120],
                },
                ensure_ascii=False,
                default=str,
            ),
        )

        if is_anthropic_provider and not anthropic_session_saved:
            try:
                get_session(response.id)
            except ProviderAPIError:
                LOG.warning(
                    "Session lookup failed during stream cleanup "
                    "(possible orphaned session): response_id=%s",
                    response.id,
                )
            else:
                drop_session(response.id)
        return stream_resp
    finally:
        cancelled.discard(response.id)


async def cancel_request(request: web.Request) -> web.Response:
    req_id = request.match_info.get("request_id")
    cancelled = request.app[CANCELLED_KEY]
    if req_id:
        cancelled.add(req_id)
    return web.json_response({"cancelled": True, "request_id": req_id})


def _parse_policy(raw: Any) -> Policy | None:
    if raw is None:
        return None
    if not isinstance(raw, Mapping):
        raise web.HTTPBadRequest(text="policy must be an object")
    allowed_providers = _ensure_str_list("policy.allowed_providers", raw.get("allowed_providers"))
    allowed_models = _ensure_str_list("policy.allowed_models", raw.get("allowed_models"))
    region_raw = raw.get("region")
    if region_raw is None:
        region = None
    elif isinstance(region_raw, str) and region_raw.strip():
        region = region_raw.strip()
    else:
        raise web.HTTPBadRequest(text="policy.region must be a non-empty string")
    max_cost = _to_float("policy.max_cost_usd", raw.get("max_cost_usd"))
    json_strict = _to_bool("policy.json_strict", raw.get("json_strict"))
    retention = _to_int("policy.retention_ttl", raw.get("retention_ttl"))
    training_opt_out = _to_bool("policy.training_opt_out", raw.get("training_opt_out"))
    return Policy(
        allowed_providers=allowed_providers,
        allowed_models=allowed_models,
        region=region,
        max_cost_usd=max_cost,
        json_strict=json_strict,
        retention_ttl=retention,
        training_opt_out=training_opt_out,
    )


__all__ = [
    "register_routes",
    "post_chat_completions",
    "post_responses",
    "post_messages",
    "get_stream_resume",
    "cancel_request",
    "CANCELLED_KEY",
]
