import json
import logging
import os

import redis
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp


class IdempotencyMiddleware(BaseHTTPMiddleware):
    def __init__(self, app: ASGIApp, redis_url: str = None):
        super().__init__(app)
        self.redis_url = redis_url or os.getenv("REDIS_URL", "redis://localhost:6379/0")
        try:
            self.redis = redis.from_url(self.redis_url, decode_responses=True)
        except Exception as exc:  # pragma: no cover - defensive
            logging.getLogger(__name__).warning(
                "Failed to initialise Redis for idempotency cache: %s", exc
            )
            self.redis = None
        self.ttl = 86400  # 24 hours

    async def dispatch(self, request: Request, call_next):
        if request.method not in ("POST", "PUT", "PATCH", "DELETE"):
            return await call_next(request)

        idempotency_key = request.headers.get("Idempotency-Key")
        if not idempotency_key:
            return await call_next(request)

        key = f"idempotency:{idempotency_key}"

        # Check cache
        if self.redis:
            try:
                cached = self.redis.get(key)
                if cached:
                    data = json.loads(cached)
                    return Response(
                        content=data["body"],
                        status_code=data["status_code"],
                        headers=data["headers"],
                        media_type=data["media_type"],
                    )
            except Exception as exc:  # pragma: no cover - best-effort cache lookup
                logging.getLogger(__name__).warning(
                    "Failed to read idempotency cache for key %s: %s", key, exc
                )

        response = await call_next(request)

        if 200 <= response.status_code < 500 and self.redis:
            try:
                # Read body
                body_chunks = []
                async for chunk in response.body_iterator:
                    body_chunks.append(chunk)
                body = b"".join(body_chunks)

                # Reconstruct response
                response = Response(
                    content=body,
                    status_code=response.status_code,
                    headers=dict(response.headers),
                    media_type=response.media_type,
                )

                # Cache
                data = {
                    "body": body.decode("utf-8"),
                    "status_code": response.status_code,
                    "headers": dict(response.headers),
                    "media_type": response.media_type,
                }
                self.redis.setex(key, self.ttl, json.dumps(data))
            except Exception as exc:  # pragma: no cover - best-effort cache write
                logging.getLogger(__name__).warning(
                    "Failed to persist idempotency cache for key %s: %s", key, exc
                )

        return response
