"""
FastAPI middleware / dependency for L402 discourse tolls.

Usage:
    from discourse_toll import create_toll, TollConfig, MockWallet

    mock = MockWallet()
    toll = create_toll(TollConfig(secret="mysecret", wallet=mock))

    @app.post("/comments")
    async def post_comment(
        body: CommentBody,
        paid: bool = Depends(toll(context_from="body.thread_id")),
    ):
        ...
"""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from typing import Any, Callable, Optional

from .pricing import PricingConfig, PricingEngine
from .trust import TrustResolver, static_resolver
from .wallet import WalletProvider, WalletBackend
from .macaroon import (
    create_macaroon,
    verify_macaroon,
    encode_macaroon,
    decode_macaroon,
    verify_preimage,
)


@dataclass
class TollConfig:
    """Configuration for discourse toll middleware."""

    secret: str
    wallet: Any  # WalletBackend
    pricing: PricingConfig = field(default_factory=PricingConfig)
    trust: Optional[TrustResolver] = None
    invoice_ttl_secs: int = 600
    description: str = "Discourse toll"


class TollResponse:
    """402 Payment Required response data."""

    def __init__(self, status: int, data: dict):
        self.status = status
        self.data = data


def create_toll(config: TollConfig):
    """
    Create a discourse toll factory.

    Returns a callable that produces FastAPI dependencies.

    Usage:
        toll = create_toll(config)

        @app.post("/comments")
        async def handler(paid=Depends(toll(context_from="body.thread_id"))):
            ...
    """
    pricing = PricingEngine(config.pricing)
    trust = config.trust or TrustResolver()
    wallet = WalletProvider(config.wallet)

    def toll(
        *,
        context_from: Optional[str] = None,
        agent_from: Optional[str] = None,
        description: Optional[str] = None,
    ):
        """
        Create a FastAPI dependency for a specific route.

        Args:
            context_from: Dot-path to extract context ID (e.g. 'body.thread_id')
            agent_from: Dot-path to extract agent ID (e.g. 'headers.x-agent-id')
            description: Invoice description
        """

        async def dependency(request) -> bool:
            """FastAPI dependency that enforces L402 toll."""
            from fastapi import HTTPException
            from fastapi.responses import JSONResponse

            # --- Check for L402 auth ---
            auth_header = request.headers.get("authorization", "")
            if auth_header.lower().startswith("l402 "):
                result = _verify_l402(
                    auth_header, request, context_from, agent_from
                )
                if result["valid"]:
                    return True
                raise HTTPException(
                    status_code=401,
                    detail={
                        "error": "Invalid L402 credentials",
                        "detail": result["error"],
                    },
                )

            # --- Calculate price ---
            agent_id = _extract(request, agent_from) or request.headers.get(
                "x-agent-id", "anonymous"
            )
            context_id = _extract(request, context_from) or "default"

            # Fetch trust score
            trust_score = None
            try:
                trust_score = await trust.get_score(agent_id)
            except Exception:
                pass

            price = pricing.calculate(
                agent_id=agent_id,
                context_id=context_id,
                trust_score=trust_score,
                dry_run=True,
            )

            # Free pass
            if price.sats == 0:
                return True

            # Create invoice
            desc = description or f"{config.description}: {context_id}"
            inv = await wallet.create_invoice(price.sats, desc)

            # Create macaroon
            expires_at = int(time.time()) + config.invoice_ttl_secs
            mac = create_macaroon(
                config.secret,
                inv["payment_hash"],
                expires_at=expires_at,
                endpoint=str(request.url.path),
                method=request.method,
                context_id=context_id,
                agent_id=agent_id,
            )
            encoded_mac = encode_macaroon(mac)

            # Return 402
            raise HTTPException(
                status_code=402,
                detail={
                    "status": 402,
                    "message": "Payment Required",
                    "protocol": "L402",
                    "payment_hash": inv["payment_hash"],
                    "invoice": inv["invoice"],
                    "macaroon": encoded_mac,
                    "amount_sats": price.sats,
                    "description": desc,
                    "pricing": price.breakdown,
                    "instructions": {
                        "step1": "Pay the Lightning invoice",
                        "step2": "Get the preimage from the payment receipt",
                        "step3": "Retry with header: Authorization: L402 <macaroon>:<preimage>",
                    },
                },
            )

        return dependency

    def _verify_l402(
        auth_header: str,
        request: Any,
        context_from: Optional[str],
        agent_from: Optional[str],
    ) -> dict:
        """Verify an L402 authorization header."""
        token = auth_header.split(None, 1)[1] if " " in auth_header else ""
        parts = token.split(":", 1)
        if len(parts) != 2:
            return {"valid": False, "error": "Invalid L402 format"}

        encoded_mac, preimage = parts
        mac = decode_macaroon(encoded_mac)
        if mac is None:
            return {"valid": False, "error": "Invalid macaroon encoding"}

        if not verify_preimage(preimage, mac["id"]):
            return {"valid": False, "error": "Preimage does not match payment hash"}

        agent_id = _extract(request, agent_from) or request.headers.get(
            "x-agent-id", "anonymous"
        )
        context_id = _extract(request, context_from) or "default"

        valid, error = verify_macaroon(
            config.secret,
            mac,
            endpoint=str(request.url.path),
            method=request.method,
            context_id=context_id,
            agent_id=agent_id,
        )

        if not valid:
            return {"valid": False, "error": error}

        # Record action for progressive pricing
        pricing.calculate(agent_id=agent_id, context_id=context_id, dry_run=False)
        return {"valid": True}

    def _extract(request: Any, path: Optional[str]) -> Optional[str]:
        """Extract a value from request using dot-path."""
        if not path:
            return None
        parts = path.split(".")
        obj: Any = request
        for part in parts:
            if isinstance(obj, dict):
                obj = obj.get(part)
            elif hasattr(obj, part):
                obj = getattr(obj, part)
            else:
                return None
        return str(obj) if obj is not None else None

    # Expose internals
    toll.pricing = pricing  # type: ignore
    toll.trust = trust  # type: ignore
    toll.wallet = wallet  # type: ignore
    toll.stats = lambda: {  # type: ignore
        "pricing": pricing.stats(),
        "trust": trust.stats(),
        "wallet": wallet.stats(),
    }
    toll.cleanup = pricing.cleanup  # type: ignore

    return toll
