# app/modules/billing/dependencies/feature_gate.py
"""
Feature gating decorator and dependencies for tier-based access control.

Resolves store → merchant → subscription → tier → TierFeatureLimit.

Provides:
- @require_feature decorator for endpoints
- RequireFeature dependency for flexible usage
- RequireWithinLimit dependency for quantitative checks
- FeatureNotAvailableError exception with upgrade info

Usage:
    # As decorator (simple)
    @router.get("/analytics")
    @require_feature("analytics_dashboard")
    def get_analytics(...):
        ...

    # As dependency (more control)
    @router.get("/analytics")
    def get_analytics(
        _: None = Depends(RequireFeature("analytics_dashboard")),
        ...
    ):
        ...

    # Quantitative limit check
    @router.post("/products")
    def create_product(
        _: None = Depends(RequireWithinLimit("products_limit")),
        ...
    ):
        ...
"""

import asyncio
import functools
import logging
from collections.abc import Callable

from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session

from app.api.deps import get_current_store_api
from app.core.database import get_db
from app.modules.billing.services.feature_service import feature_service
from app.modules.tenancy.models import User

logger = logging.getLogger(__name__)


class FeatureNotAvailableError(HTTPException):
    """
    Exception raised when a feature is not available for the merchant's tier.

    Includes upgrade information for the frontend to display.
    """

    def __init__(
        self,
        feature_code: str,
        feature_name: str | None = None,
        required_tier_code: str | None = None,
        required_tier_name: str | None = None,
        required_tier_price_cents: int | None = None,
    ):
        detail = {
            "error": "feature_not_available",
            "message": "This feature requires an upgrade to access.",
            "feature_code": feature_code,
            "feature_name": feature_name,
            "upgrade": {
                "tier_code": required_tier_code,
                "tier_name": required_tier_name,
                "price_monthly_cents": required_tier_price_cents,
            }
            if required_tier_code
            else None,
        }
        super().__init__(status_code=403, detail=detail)


class RequireFeature:
    """
    Dependency class that checks if store's merchant has access to a feature.

    Resolves store → merchant → subscription → tier → TierFeatureLimit.

    Args:
        *feature_codes: One or more feature codes. Access granted if ANY is available.
    """

    def __init__(self, *feature_codes: str):
        if not feature_codes:
            raise ValueError("At least one feature code is required")
        self.feature_codes = feature_codes

    def __call__(
        self,
        current_user: User = Depends(get_current_store_api),
        db: Session = Depends(get_db),
    ) -> None:
        """Check if store's merchant has access to any of the required features."""
        store_id = current_user.token_store_id
        platform_id = current_user.token_platform_id

        for feature_code in self.feature_codes:
            if feature_service.has_feature_for_store(
                db, store_id, feature_code, platform_id=platform_id
            ):
                return

        # None of the features are available
        feature_code = self.feature_codes[0]
        raise FeatureNotAvailableError(feature_code=feature_code)


class RequireWithinLimit:
    """
    Dependency that checks a quantitative resource limit.

    Resolves store → merchant → subscription → tier → TierFeatureLimit,
    then checks current usage against the limit.

    Args:
        feature_code: The quantitative feature to check (e.g., "products_limit")
    """

    def __init__(self, feature_code: str):
        self.feature_code = feature_code

    def __call__(
        self,
        current_user: User = Depends(get_current_store_api),
        db: Session = Depends(get_db),
    ) -> None:
        """Check if the resource limit allows adding more items."""
        store_id = current_user.token_store_id

        allowed, message = feature_service.check_resource_limit(
            db, self.feature_code, store_id=store_id,
            platform_id=current_user.token_platform_id,
        )

        if not allowed:
            raise HTTPException(
                status_code=403,
                detail={
                    "error": "limit_exceeded",
                    "message": message,
                    "feature_code": self.feature_code,
                },
            )


def require_feature(*feature_codes: str) -> Callable:
    """
    Decorator to require one or more features for an endpoint.

    The decorated endpoint will return 403 if the store's merchant
    doesn't have access to ANY of the specified features.

    Args:
        *feature_codes: One or more feature codes. Access granted if ANY is available.
    """
    if not feature_codes:
        raise ValueError("At least one feature code is required")

    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        async def async_wrapper(*args, **kwargs):
            db = kwargs.get("db")
            current_user = kwargs.get("current_user")

            if not db or not current_user:
                raise HTTPException(
                    status_code=500,
                    detail="Feature check failed: missing db or current_user dependency",
                )

            store_id = current_user.token_store_id
            platform_id = current_user.token_platform_id

            for feature_code in feature_codes:
                if feature_service.has_feature_for_store(
                    db, store_id, feature_code, platform_id=platform_id
                ):
                    return await func(*args, **kwargs)

            raise FeatureNotAvailableError(feature_code=feature_codes[0])

        @functools.wraps(func)
        def sync_wrapper(*args, **kwargs):
            db = kwargs.get("db")
            current_user = kwargs.get("current_user")

            if not db or not current_user:
                raise HTTPException(
                    status_code=500,
                    detail="Feature check failed: missing db or current_user dependency",
                )

            store_id = current_user.token_store_id
            platform_id = current_user.token_platform_id

            for feature_code in feature_codes:
                if feature_service.has_feature_for_store(
                    db, store_id, feature_code, platform_id=platform_id
                ):
                    return func(*args, **kwargs)

            raise FeatureNotAvailableError(feature_code=feature_codes[0])

        if asyncio.iscoroutinefunction(func):
            return async_wrapper
        return sync_wrapper

    return decorator


__all__ = [
    "require_feature",
    "RequireFeature",
    "RequireWithinLimit",
    "FeatureNotAvailableError",
]
