import json
import uuid
import logging
from typing import Optional, List, Callable
from .client import authorize

logger = logging.getLogger("payfence")


class PayFenceMiddleware:
    """ASGI middleware for PayFence authorization."""

    def __init__(
        self,
        app,
        api_key: str,
        base_url: str = "https://api.payfence.io",
        exclude_paths: Optional[List[str]] = None,
        on_deny: Optional[Callable] = None,
    ):
        self.app = app
        self.api_key = api_key
        self.base_url = base_url
        self.exclude_paths = exclude_paths or []
        self.on_deny = on_deny

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

        path = scope.get("path", "/")

        # Skip excluded paths
        if any(path.startswith(p) for p in self.exclude_paths):
            logger.debug("Path excluded: %s", path)
            return await self.app(scope, receive, send)

        # Extract bearer token from headers
        headers = dict(scope.get("headers", []))
        auth = headers.get(b"authorization", b"").decode()
        token = auth[7:] if auth.startswith("Bearer ") else ""

        if not token:
            logger.info("Deny: missing Bearer token for %s", path)
            return await self._send_deny(send, {"decision": "deny", "reason": "payment_required", "message": "Missing Bearer token"})

        try:
            request_id = headers.get(b"x-request-id", b"").decode() or str(uuid.uuid4())
            method = scope.get("method", "GET")

            decision = authorize(
                api_key=self.api_key,
                token=token,
                method=method,
                path=path,
                request_id=request_id,
                base_url=self.base_url,
            )

            if decision.get("decision") == "allow":
                logger.debug("Allow: %s %s", method, path)
                scope["payfence"] = decision
                return await self.app(scope, receive, send)

            # Deny
            logger.info("Deny: %s %s — reason=%s", method, path, decision.get("reason"))
            if self.on_deny:
                return await self.on_deny(scope, receive, send, decision)
            return await self._send_deny(send, decision)

        except Exception as e:
            logger.warning("PayFence authorize failed, failing open: %s", e)
            return await self.app(scope, receive, send)

    async def _send_deny(self, send, decision):
        body = json.dumps(decision).encode()
        await send({
            "type": "http.response.start",
            "status": 402,
            "headers": [[b"content-type", b"application/json"]],
        })
        await send({
            "type": "http.response.body",
            "body": body,
        })
