from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal, Protocol, Sequence, TypeVar


@dataclass(frozen=True, kw_only=True)
class BaseExecRequest:
    """Backend-agnostic execution envelope for middleware."""

    # Discriminator (helps both runtime dispatch and typing/narrowing).
    kind: str  # "sql" | "http" (extended over time)
    operation: str  # e.g. "list_reasoners"
    meta: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True, kw_only=True)
class SqlRequest(BaseExecRequest):
    """SQL execution envelope."""

    kind: Literal["sql"] = "sql"
    statement: str
    params: list[Any] | None = None


@dataclass(frozen=True, kw_only=True)
class HttpRequest(BaseExecRequest):
    """HTTP execution envelope (Direct Access)."""

    kind: Literal["http"] = "http"
    method: str
    url: str
    json: dict[str, Any] | None = None
    headers: dict[str, str] | None = None


ExecRequest = SqlRequest | HttpRequest

# Executor consumes requests (input-only), so its type parameter should be contravariant.
TReq_contra = TypeVar("TReq_contra", bound=BaseExecRequest, contravariant=True)
# Middleware both consumes and produces executors of the same request type, so invariant.
TReq = TypeVar("TReq", bound=BaseExecRequest)
# Executor output is produced-only, so covariant.
TOut_co = TypeVar("TOut_co", covariant=True)
TOut = TypeVar("TOut")


class Executor(Protocol[TReq_contra, TOut_co]):
    """Execution protocol for middleware pipelines.

    The output type is fully generic:
    - sync executors return concrete values (e.g. `list[Row]`, `requests.Response`)
    - async executors return awaitables (e.g. `Awaitable[dict[str, Any] | None]`)
    """

    def execute(self, req: TReq_contra) -> TOut_co: ...


class Middleware(Protocol[TReq, TOut]):
    def wrap(self, nxt: Executor[TReq, TOut]) -> Executor[TReq, TOut]: ...


def chain(base: Executor[TReq, TOut], middlewares: Sequence[Middleware[TReq, TOut]]) -> Executor[TReq, TOut]:
    """Wrap an executor with middleware in order.

    The first middleware in the list becomes the outermost wrapper.
    """

    out = base
    for m in reversed(list(middlewares)):
        out = m.wrap(out)
    return out

