"""
Internal request pipeline primitives.

The SDK models requests/responses independently of the underlying HTTP transport
so cross-cutting behavior can be implemented as middleware.
"""

from __future__ import annotations

from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping, Sequence
from dataclasses import dataclass, field
from typing import Any, Generic, Literal, Protocol, TypeAlias, TypedDict, TypeVar, cast

Header: TypeAlias = tuple[str, str]

R = TypeVar("R", bound="SDKBaseResponse")
S = TypeVar("S")


class RequestContext(TypedDict, total=False):
    cache_key: str
    cache_ttl: float
    # Stable per-operation correlation ID generated by RequestId middleware.
    client_request_id: str
    # Monotonic start time captured by HooksMiddleware (seconds).
    started_at: float
    # Total wall-clock budget for the whole operation (redirects + streaming iteration).
    deadline_seconds: float
    # Used by hooks middleware to allow inner middleware (redirect/stream wrappers) to emit events.
    emit_event: Callable[[Any], Any]
    # RequestInfo stored by HooksMiddleware for reuse by inner middleware.
    hook_request_info: Any
    external: bool
    ever_external: bool
    safe_follow: bool
    streaming: bool
    chunk_size: int
    on_progress: Any
    timeout: Any
    timeout_seconds: float
    tenant_hash: str


class ResponseContext(TypedDict, total=False):
    cache_hit: bool
    client_request_id: str
    external: bool
    ever_external: bool
    http_version: str
    request_id: str
    elapsed_seconds: float
    retry_count: int


class SDKBaseResponse(Protocol):
    status_code: int
    headers: list[Header]
    context: ResponseContext


@dataclass(slots=True)
class SDKRequest:
    method: str
    url: str
    headers: list[Header] = field(default_factory=list)
    params: Sequence[tuple[str, str]] | None = None
    json: Any | None = None
    files: Mapping[str, Any] | None = None
    data: Mapping[str, Any] | None = None
    api_version: Literal["v1", "v2"] = "v2"
    write_intent: bool = False
    context: RequestContext = field(default_factory=lambda: cast(RequestContext, {}))


@dataclass(slots=True)
class SDKResponse:
    status_code: int
    headers: list[Header]
    content: bytes
    json: Any | None = None
    context: ResponseContext = field(default_factory=lambda: cast(ResponseContext, {}))


@dataclass(slots=True)
class SDKRawResponse:
    status_code: int
    headers: list[Header]
    content: bytes
    context: ResponseContext = field(default_factory=lambda: cast(ResponseContext, {}))


class SDKRawStream(Protocol):
    def __enter__(self) -> SDKRawStream: ...

    def __exit__(self, exc_type: object, exc: object, tb: object) -> None: ...

    def iter_bytes(self, *, chunk_size: int) -> Iterator[bytes]: ...

    def close(self) -> None: ...


class SDKAsyncRawStream(Protocol):
    async def __aenter__(self) -> SDKAsyncRawStream: ...

    async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None: ...

    def aiter_bytes(self, *, chunk_size: int) -> AsyncIterator[bytes]: ...

    async def aclose(self) -> None: ...


@dataclass(slots=True)
class SDKRawStreamResponse(Generic[S]):
    status_code: int
    headers: list[Header]
    stream: S
    context: ResponseContext = field(default_factory=lambda: cast(ResponseContext, {}))


Pipeline: TypeAlias = Callable[[SDKRequest], R]
AsyncPipeline: TypeAlias = Callable[[SDKRequest], Awaitable[R]]


class Middleware(Protocol[R]):
    def __call__(self, req: SDKRequest, next: Pipeline[R]) -> R: ...


class AsyncMiddleware(Protocol[R]):
    async def __call__(self, req: SDKRequest, next: AsyncPipeline[R]) -> R: ...


def compose(middlewares: Sequence[Middleware[R]], terminal: Pipeline[R]) -> Pipeline[R]:
    pipeline: Pipeline[R] = terminal
    for middleware in reversed(middlewares):
        next_pipeline = pipeline

        def _wrapped(
            req: SDKRequest,
            *,
            _mw: Middleware[R] = middleware,
            _n: Pipeline[R] = next_pipeline,
        ) -> R:
            return _mw(req, _n)

        pipeline = _wrapped
    return pipeline


def compose_async(
    middlewares: Sequence[AsyncMiddleware[R]], terminal: AsyncPipeline[R]
) -> AsyncPipeline[R]:
    pipeline: AsyncPipeline[R] = terminal
    for middleware in reversed(middlewares):
        next_pipeline = pipeline

        async def _wrapped(
            req: SDKRequest,
            *,
            _mw: AsyncMiddleware[R] = middleware,
            _n: AsyncPipeline[R] = next_pipeline,
        ) -> R:
            return await _mw(req, _n)

        pipeline = _wrapped
    return pipeline
