"""FastAPI/Starlette ASGI middleware for x402 payment handling."""

from __future__ import annotations

import base64
import json
import os
from typing import Any, Awaitable, Callable, Optional

try:
    from starlette.middleware.base import BaseHTTPMiddleware
    from starlette.requests import Request
    from starlette.responses import JSONResponse, Response
    from starlette.types import ASGIApp
except ImportError as e:
    raise ImportError(
        "starlette is required for this module. Install with: pip install x402-stacks[fastapi]"
    ) from e

from ..types import NetworkType, TokenType, TokenContract
from ..types_v2 import (
    PaymentRequiredV2,
    PaymentRequirementsV2,
    PaymentPayloadV2,
    SettlementResponseV2,
    ResourceInfo,
    X402_HEADERS,
    X402_ERROR_CODES,
)
from ..verifier_v2 import X402PaymentVerifier, SettleOptions
from ..verifier import X402PaymentVerifierV1, SettleOptionsV1
from ..utils import network_to_caip2, asset_to_v2


# ── Configuration ────────────────────────────────────────────────────


class PaymentMiddlewareConfig:
    """Configuration for x402 payment middleware."""

    def __init__(
        self,
        *,
        network: str,
        amount: str | int,
        pay_to: str,
        scheme: str = "exact",
        asset: Optional[str] = None,
        token_type: Optional[TokenType] = None,
        token_contract: Optional[TokenContract] = None,
        max_timeout_seconds: int = 300,
        facilitator_url: Optional[str] = None,
        description: Optional[str] = None,
        mime_type: Optional[str] = None,
        payment_validator: Optional[Callable[[SettlementResponseV2], bool | Awaitable[bool]]] = None,
        extra: Optional[dict[str, Any]] = None,
    ) -> None:
        # Normalize network to CAIP-2
        self.network: str = network if ":" in network else network_to_caip2(network)  # type: ignore[arg-type]
        self.amount = str(amount)
        self.pay_to = pay_to
        self.scheme = scheme
        self.asset = asset or asset_to_v2(token_type or "STX", token_contract)
        self.max_timeout_seconds = max_timeout_seconds
        self.facilitator_url = facilitator_url or "http://localhost:8085"
        self.description = description
        self.mime_type = mime_type
        self.payment_validator = payment_validator
        self.extra = extra


# ── V2 Middleware (default) ──────────────────────────────────────────


class PaymentMiddleware(BaseHTTPMiddleware):
    """ASGI middleware for V2 x402 payment protocol."""

    def __init__(self, app: ASGIApp, config: PaymentMiddlewareConfig) -> None:
        super().__init__(app)
        self._config = config
        self._verifier = X402PaymentVerifier(config.facilitator_url)

    async def dispatch(self, request: Request, call_next: Any) -> Response:
        try:
            sig_header = request.headers.get(X402_HEADERS["PAYMENT_SIGNATURE"])

            if not sig_header:
                return self._send_payment_required(request)

            # Decode payment payload
            try:
                decoded = base64.b64decode(sig_header).decode("utf-8")
                payment_payload = PaymentPayloadV2.model_validate(json.loads(decoded))
            except Exception:
                return JSONResponse(
                    {"error": X402_ERROR_CODES["INVALID_PAYLOAD"],
                     "message": "Invalid payment-signature header"},
                    status_code=400,
                )

            if payment_payload.x402_version != 2:
                return JSONResponse(
                    {"error": X402_ERROR_CODES["INVALID_X402_VERSION"],
                     "message": "Only x402 v2 is supported"},
                    status_code=400,
                )

            # Build requirements
            requirements = PaymentRequirementsV2(
                scheme=self._config.scheme,
                network=self._config.network,
                amount=self._config.amount,
                asset=self._config.asset,
                pay_to=self._config.pay_to,
                max_timeout_seconds=self._config.max_timeout_seconds,
                extra=self._config.extra,
            )

            # Settle
            settlement = self._verifier.settle(
                payment_payload, SettleOptions(payment_requirements=requirements)
            )

            if not settlement.success:
                resp = self._send_payment_required(request)
                return resp

            # Custom validator
            if self._config.payment_validator:
                import asyncio
                import inspect
                result = self._config.payment_validator(settlement)
                if inspect.isawaitable(result):
                    result = await result
                if not result:
                    return JSONResponse(
                        {"error": "custom_validation_failed",
                         "message": "Custom validation rejected the payment"},
                        status_code=402,
                    )

            # Attach payment to request state
            request.state.payment = settlement

            # Add response header
            response = await call_next(request)
            pay_resp = {
                "success": settlement.success,
                "payer": settlement.payer,
                "transaction": settlement.transaction,
                "network": settlement.network,
            }
            response.headers[X402_HEADERS["PAYMENT_RESPONSE"]] = base64.b64encode(
                json.dumps(pay_resp).encode()
            ).decode()
            return response

        except Exception as exc:
            return JSONResponse(
                {"error": X402_ERROR_CODES["UNEXPECTED_SETTLE_ERROR"],
                 "message": str(exc)},
                status_code=500,
            )

    def _send_payment_required(self, request: Request) -> JSONResponse:
        resource = ResourceInfo(
            url=str(request.url),
            description=self._config.description,
            mime_type=self._config.mime_type,
        )
        requirements = PaymentRequirementsV2(
            scheme=self._config.scheme,
            network=self._config.network,
            amount=self._config.amount,
            asset=self._config.asset,
            pay_to=self._config.pay_to,
            max_timeout_seconds=self._config.max_timeout_seconds,
            extra=self._config.extra,
        )
        body = PaymentRequiredV2(
            x402_version=2,
            resource=resource,
            accepts=[requirements],
        )
        body_json = body.model_dump(by_alias=True)
        header_val = base64.b64encode(json.dumps(body_json).encode()).decode()
        return JSONResponse(
            body_json,
            status_code=402,
            headers={X402_HEADERS["PAYMENT_REQUIRED"]: header_val},
        )


# ── V1 Middleware ────────────────────────────────────────────────────


class PaymentMiddlewareV1(BaseHTTPMiddleware):
    """ASGI middleware for V1 x402 payment protocol."""

    def __init__(
        self,
        app: ASGIApp,
        *,
        amount: str | int,
        address: str,
        network: NetworkType,
        facilitator_url: str = "http://localhost:8085",
        resource: Optional[str] = None,
        expiration_seconds: int = 300,
        token_type: Optional[TokenType] = None,
        token_contract: Optional[TokenContract] = None,
    ) -> None:
        super().__init__(app)
        self._amount = str(amount)
        self._address = address
        self._network = network
        self._facilitator_url = facilitator_url
        self._resource = resource
        self._expiration_seconds = expiration_seconds
        self._token_type = token_type
        self._token_contract = token_contract
        self._verifier = X402PaymentVerifierV1(facilitator_url, network)

    async def dispatch(self, request: Request, call_next: Any) -> Response:
        try:
            signed_payment = request.headers.get("x-payment")
            if not signed_payment:
                return self._send_payment_required(request)

            header_token = (request.headers.get("x-payment-token-type") or "").lower()
            token_type: TokenType = self._token_type or "STX"
            if header_token == "sbtc":
                token_type = "sBTC"
            elif header_token == "usdcx":
                token_type = "USDCx"

            settle_opts = SettleOptionsV1(
                expected_recipient=self._address,
                min_amount=int(self._amount),
                token_type=token_type,
                resource=self._resource or request.url.path,
                method=request.method,
            )
            verification = self._verifier.settle_payment(signed_payment, settle_opts)

            if not verification.is_valid:
                return JSONResponse(
                    {"error": "Invalid payment", "details": verification.validation_error,
                     "paymentStatus": verification.status},
                    status_code=402,
                )

            if verification.status == "pending":
                return JSONResponse(
                    {"error": "Payment not yet confirmed",
                     "details": "Please wait for transaction confirmation",
                     "paymentStatus": "pending"},
                    status_code=402,
                )

            if verification.status == "failed":
                return JSONResponse(
                    {"error": "Payment failed",
                     "details": "Transaction failed on blockchain",
                     "paymentStatus": "failed"},
                    status_code=402,
                )

            request.state.payment = verification

            response = await call_next(request)
            pay_resp = {
                "txId": verification.tx_id,
                "status": verification.status,
                "blockHeight": verification.block_height,
            }
            response.headers["X-PAYMENT-RESPONSE"] = base64.b64encode(
                json.dumps(pay_resp).encode()
            ).decode()
            return response

        except Exception as exc:
            return JSONResponse(
                {"error": "Payment settlement error", "details": str(exc)},
                status_code=500,
            )

    def _send_payment_required(self, request: Request) -> JSONResponse:
        from datetime import datetime, timezone
        nonce = os.urandom(16).hex()
        expires_at = datetime.fromtimestamp(
            datetime.now(timezone.utc).timestamp() + self._expiration_seconds,
            tz=timezone.utc,
        ).isoformat().replace("+00:00", "Z")

        body = {
            "maxAmountRequired": self._amount,
            "resource": self._resource or request.url.path,
            "payTo": self._address,
            "network": self._network,
            "nonce": nonce,
            "expiresAt": expires_at,
        }
        if self._token_type:
            body["tokenType"] = self._token_type
        if self._token_contract:
            body["tokenContract"] = self._token_contract.model_dump()
        return JSONResponse(body, status_code=402)


# ── Route-level dependency helpers ───────────────────────────────────


def payment_required(config: PaymentMiddlewareConfig) -> Any:
    """FastAPI dependency that requires V2 payment."""
    try:
        from fastapi import Depends, HTTPException, Request as FastAPIRequest
    except ImportError as e:
        raise ImportError("fastapi is required") from e

    verifier = X402PaymentVerifier(config.facilitator_url)

    async def _dep(request: FastAPIRequest) -> SettlementResponseV2:
        sig = request.headers.get(X402_HEADERS["PAYMENT_SIGNATURE"])
        if not sig:
            raise HTTPException(status_code=402, detail="Payment required")

        try:
            decoded = base64.b64decode(sig).decode()
            payload = PaymentPayloadV2.model_validate(json.loads(decoded))
        except Exception:
            raise HTTPException(status_code=400, detail="Invalid payment signature")

        requirements = PaymentRequirementsV2(
            scheme=config.scheme,
            network=config.network,
            amount=config.amount,
            asset=config.asset,
            pay_to=config.pay_to,
            max_timeout_seconds=config.max_timeout_seconds,
            extra=config.extra,
        )
        result = verifier.settle(payload, SettleOptions(payment_requirements=requirements))
        if not result.success:
            raise HTTPException(status_code=402, detail=result.error_reason or "Payment failed")
        return result

    return Depends(_dep)


def payment_required_v1(
    amount: str | int,
    address: str,
    network: NetworkType,
    facilitator_url: str = "http://localhost:8085",
) -> Any:
    """FastAPI dependency that requires V1 payment."""
    try:
        from fastapi import Depends, HTTPException, Request as FastAPIRequest
    except ImportError as e:
        raise ImportError("fastapi is required") from e

    verifier = X402PaymentVerifierV1(facilitator_url, network)

    async def _dep(request: FastAPIRequest) -> Any:
        signed = request.headers.get("x-payment")
        if not signed:
            raise HTTPException(status_code=402, detail="Payment required")

        opts = SettleOptionsV1(
            expected_recipient=address,
            min_amount=int(amount),
            resource=request.url.path,
            method=request.method,
        )
        result = verifier.settle_payment(signed, opts)
        if not result.is_valid:
            raise HTTPException(status_code=402, detail=result.validation_error or "Invalid payment")
        return result

    return Depends(_dep)


def get_payment(request: Any) -> Optional[SettlementResponseV2]:
    """Get payment from request state."""
    return getattr(request.state, "payment", None)


# ── Helper patterns ──────────────────────────────────────────────────


def conditional_payment(
    condition: Callable[[Any], bool | Awaitable[bool]],
    config: PaymentMiddlewareConfig,
) -> Any:
    """FastAPI dependency: only require payment if condition is met."""
    try:
        from fastapi import Depends, Request as FastAPIRequest
    except ImportError as e:
        raise ImportError("fastapi is required") from e

    base_dep = payment_required(config)

    async def _dep(request: FastAPIRequest) -> Optional[SettlementResponseV2]:
        import inspect
        result = condition(request)
        if inspect.isawaitable(result):
            result = await result
        if result:
            return await base_dep.dependency(request)
        return None

    return Depends(_dep)


def tiered_payment(
    get_tier: Callable[[Any], dict[str, Any] | Awaitable[dict[str, Any]]],
    base_config: dict[str, Any],
) -> Any:
    """FastAPI dependency: different amounts based on request."""
    try:
        from fastapi import Depends, Request as FastAPIRequest
    except ImportError as e:
        raise ImportError("fastapi is required") from e

    async def _dep(request: FastAPIRequest) -> SettlementResponseV2:
        import inspect
        tier = get_tier(request)
        if inspect.isawaitable(tier):
            tier = await tier
        cfg = PaymentMiddlewareConfig(**{**base_config, **tier})
        dep = payment_required(cfg)
        return await dep.dependency(request)

    return Depends(_dep)
