# app/modules/contracts/features.py
"""
Feature provider protocol for cross-module feature declaration and usage tracking.

This module defines the protocol that modules implement to declare their billable
features. The billing module's FeatureAggregator discovers and aggregates all providers.

Benefits:
- Each module owns its feature declarations (no cross-module coupling)
- Billing is feature-agnostic (discovers features dynamically)
- Adding features only requires changes in the owning module
- Scope-aware usage tracking (per-store or per-merchant)

Usage:
    # 1. Implement the protocol in your module
    class CatalogFeatureProvider:
        @property
        def feature_category(self) -> str:
            return "catalog"

        def get_feature_declarations(self) -> list[FeatureDeclaration]:
            return [
                FeatureDeclaration(
                    code="products_limit",
                    name_key="catalog.features.products_limit.name",
                    description_key="catalog.features.products_limit.description",
                    category="catalog",
                    feature_type=FeatureType.QUANTITATIVE,
                    scope=FeatureScope.STORE,
                    default_limit=200,
                    unit_key="catalog.features.products_limit.unit",
                )
            ]

        def get_store_usage(self, db, store_id) -> list[FeatureUsage]:
            count = db.query(Product).filter(Product.store_id == store_id).count()
            return [FeatureUsage(feature_code="products_limit", current_count=count, label="Products")]

        def get_merchant_usage(self, db, merchant_id, platform_id) -> list[FeatureUsage]:
            count = db.query(Product).join(Store).filter(
                Store.merchant_id == merchant_id
            ).count()
            return [FeatureUsage(feature_code="products_limit", current_count=count, label="Products")]

    # 2. Register in module definition
    def _get_feature_provider():
        from app.modules.catalog.services.catalog_features import catalog_feature_provider
        return catalog_feature_provider

    catalog_module = ModuleDefinition(
        code="catalog",
        feature_provider=_get_feature_provider,
        # ...
    )

    # 3. Features appear automatically in billing when module is enabled
"""

import enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, Protocol, runtime_checkable

if TYPE_CHECKING:
    from sqlalchemy.orm import Session


class FeatureType(str, enum.Enum):
    """Type of feature for billing purposes."""

    BINARY = "binary"  # On/off feature (e.g., "analytics_dashboard")
    QUANTITATIVE = "quantitative"  # Countable resource with limits (e.g., "products_limit")


class FeatureScope(str, enum.Enum):
    """Scope at which quantitative limits apply."""

    STORE = "store"  # Limit applies per-store (e.g., 200 products per store)
    MERCHANT = "merchant"  # Limit applies across all merchant's stores (e.g., 3 team members total)


@dataclass
class FeatureDeclaration:
    """
    Static declaration of a billable feature.

    Returned by feature providers at startup. Contains no database state,
    only metadata about what a feature is and how it should be billed.

    Attributes:
        code: Unique identifier (e.g., "products_limit", "analytics_dashboard")
        name_key: i18n key for display name (e.g., "catalog.features.products_limit.name")
        description_key: i18n key for description
        category: Grouping category (matches module domain, e.g., "catalog")
        feature_type: BINARY or QUANTITATIVE
        scope: STORE or MERCHANT (for QUANTITATIVE; BINARY always merchant-level)
        default_limit: Sensible default for QUANTITATIVE (None = unlimited)
        unit_key: i18n key for unit label (e.g., "catalog.features.products_limit.unit")
        is_per_period: Whether the limit resets each billing period (e.g., orders/month)
        ui_icon: Lucide icon name for display
        display_order: Sort order within category (lower = higher priority)

    Example:
        FeatureDeclaration(
            code="products_limit",
            name_key="catalog.features.products_limit.name",
            description_key="catalog.features.products_limit.description",
            category="catalog",
            feature_type=FeatureType.QUANTITATIVE,
            scope=FeatureScope.STORE,
            default_limit=200,
            unit_key="catalog.features.products_limit.unit",
        )
    """

    code: str
    name_key: str
    description_key: str
    category: str
    feature_type: FeatureType
    scope: FeatureScope = FeatureScope.MERCHANT
    default_limit: int | None = None
    unit_key: str | None = None
    is_per_period: bool = False
    ui_icon: str | None = None
    display_order: int = 0


@dataclass
class FeatureUsage:
    """
    Current usage count for a quantitative feature.

    Returned by feature providers when asked about current resource consumption.

    Attributes:
        feature_code: Which feature this usage relates to
        current_count: Current count of the resource
        label: Human-readable label for display
    """

    feature_code: str
    current_count: int
    label: str


@runtime_checkable
class FeatureProviderProtocol(Protocol):
    """
    Protocol for modules that declare billable features.

    Each module implements this to declare what features it provides
    and how to measure current usage. The billing module's FeatureAggregator
    discovers all providers and uses them for feature gating.

    Implementation Notes:
    - get_feature_declarations() must be static (no DB needed)
    - Usage methods should use efficient aggregation queries
    - Return empty list if no usage data available (don't raise)

    Example Implementation:
        class CatalogFeatureProvider:
            @property
            def feature_category(self) -> str:
                return "catalog"

            def get_feature_declarations(self) -> list[FeatureDeclaration]:
                return [
                    FeatureDeclaration(
                        code="products_limit",
                        name_key="catalog.features.products_limit.name",
                        description_key="catalog.features.products_limit.description",
                        category="catalog",
                        feature_type=FeatureType.QUANTITATIVE,
                        scope=FeatureScope.STORE,
                        default_limit=200,
                        unit_key="catalog.features.products_limit.unit",
                    ),
                ]

            def get_store_usage(self, db, store_id) -> list[FeatureUsage]:
                count = db.query(Product).filter(Product.store_id == store_id).count()
                return [FeatureUsage(feature_code="products_limit", current_count=count, label="Products")]

            def get_merchant_usage(self, db, merchant_id, platform_id) -> list[FeatureUsage]:
                count = db.query(Product).join(Store).filter(Store.merchant_id == merchant_id).count()
                return [FeatureUsage(feature_code="products_limit", current_count=count, label="Products")]
    """

    @property
    def feature_category(self) -> str:
        """
        Category name for this provider's features.

        Should match the module's domain (e.g., "catalog", "orders", "tenancy").

        Returns:
            Category string used for grouping features
        """
        ...

    def get_feature_declarations(self) -> list[FeatureDeclaration]:
        """
        Get static feature declarations for this module.

        Called at startup to build the feature catalog. No database access needed.
        These declarations describe what features exist and how they're billed.

        Returns:
            List of FeatureDeclaration objects for this module
        """
        ...

    def get_store_usage(
        self,
        db: "Session",
        store_id: int,
    ) -> list[FeatureUsage]:
        """
        Get current usage counts for a specific store.

        Called when checking quantitative limits scoped to STORE.
        Should only count resources belonging to the specified store.

        Args:
            db: Database session for queries
            store_id: ID of the store to get usage for

        Returns:
            List of FeatureUsage objects with current counts
        """
        ...

    def get_merchant_usage(
        self,
        db: "Session",
        merchant_id: int,
        platform_id: int,
    ) -> list[FeatureUsage]:
        """
        Get current usage counts aggregated across all merchant's stores.

        Called when checking quantitative limits scoped to MERCHANT.
        Should aggregate usage across all stores owned by the merchant
        on the specified platform.

        Args:
            db: Database session for queries
            merchant_id: ID of the merchant
            platform_id: ID of the platform

        Returns:
            List of FeatureUsage objects with aggregated counts
        """
        ...


__all__ = [
    "FeatureType",
    "FeatureScope",
    "FeatureDeclaration",
    "FeatureUsage",
    "FeatureProviderProtocol",
]
