"""
FastAPI / Starlette middleware — seals every HTTP request/response pair.

Usage:
    from fastapi import FastAPI
    from finault_seal.middleware import FastAPISealMiddleware

    app = FastAPI()
    app.add_middleware(FastAPISealMiddleware, agent_id="api-server")
"""

from __future__ import annotations

import time
from typing import Optional

from .client import SealClient
from .crypto import SealCrypto


class FastAPISealMiddleware:
    """ASGI middleware that creates a Seal for every request/response.

    Parameters
    ----------
    app : ASGI application
    agent_id : str
        Agent identity for all seals generated by this middleware.
    client : SealClient, optional
        Custom client.  Creates a default one if not provided.
    exclude_paths : list[str], optional
        Path prefixes to exclude (e.g. ["/health", "/metrics"]).
    """

    def __init__(
        self,
        app,
        agent_id: str = "api-server",
        client: Optional[SealClient] = None,
        exclude_paths: Optional[list] = None,
    ):
        self.app = app
        self.agent_id = agent_id
        self.client = client or SealClient()
        self.exclude_paths = exclude_paths or ["/health", "/metrics", "/favicon.ico"]

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        path = scope.get("path", "")
        if any(path.startswith(ex) for ex in self.exclude_paths):
            await self.app(scope, receive, send)
            return

        method = scope.get("method", "UNKNOWN")
        start = time.perf_counter()

        # Capture response status
        response_status = 0
        response_headers = {}
        seal_id = ""

        async def send_wrapper(message):
            nonlocal response_status, seal_id
            if message["type"] == "http.response.start":
                response_status = message.get("status", 0)
                # Inject X-Finault-Seal header
                headers = list(message.get("headers", []))
                # We'll generate the seal after we know the status
                elapsed_ms = (time.perf_counter() - start) * 1000
                try:
                    seal = self.client.seal(
                        agent_id=self.agent_id,
                        action=f"{method} {path}",
                        input_hash=SealCrypto.hash_content(
                            f"{method} {path} {scope.get('query_string', b'').decode()}"
                        ),
                        protocol="REST",
                        outcome={"status": response_status},
                        latency_ms=round(elapsed_ms, 2),
                        tags=["middleware", "http"],
                    )
                    seal_id = seal.seal_id
                    headers.append(
                        (b"x-finault-seal", seal.seal_id.encode("utf-8"))
                    )
                except Exception:
                    pass  # Never let sealing break the request
                message = {**message, "headers": headers}
            await send(message)

        await self.app(scope, receive, send_wrapper)
