# app/modules/core/services/stats_aggregator.py
"""
Stats aggregator service for collecting metrics from all modules.

This service lives in core because dashboards are core functionality that should
always be available. It discovers and aggregates MetricsProviders from all enabled
modules, providing a unified interface for dashboard statistics.

Benefits:
- Dashboards always work (aggregator is in core)
- Each module owns its metrics (no cross-module coupling)
- Optional modules are truly optional (can be removed without breaking app)
- Easy to add new metrics (just implement MetricsProviderProtocol in your module)

Usage:
    from app.modules.core.services.stats_aggregator import stats_aggregator

    # Get store dashboard stats
    stats = stats_aggregator.get_store_dashboard_stats(
        db=db, store_id=123, platform_id=1
    )

    # Get admin dashboard stats
    stats = stats_aggregator.get_admin_dashboard_stats(
        db=db, platform_id=1
    )
"""

import logging
from typing import TYPE_CHECKING, Any

from sqlalchemy.orm import Session

from app.modules.contracts.metrics import (
    MetricsContext,
    MetricsProviderProtocol,
    MetricValue,
)

if TYPE_CHECKING:
    from app.modules.base import ModuleDefinition

logger = logging.getLogger(__name__)


class StatsAggregatorService:
    """
    Aggregates metrics from all module providers.

    This service discovers MetricsProviders from enabled modules and provides
    a unified interface for dashboard statistics. It handles graceful degradation
    when modules are disabled or providers fail.
    """

    def _get_store_platform_ids(self, db: Session, store_id: int) -> set[int]:
        """Get platform IDs the store is actively subscribed to."""
        from app.modules.tenancy.models.store_platform import StorePlatform

        rows = (
            db.query(StorePlatform.platform_id)
            .filter(
                StorePlatform.store_id == store_id,
                StorePlatform.is_active.is_(True),
            )
            .all()
        )
        return {r.platform_id for r in rows}

    def _get_enabled_providers(
        self, db: Session, platform_id: int, store_id: int | None = None
    ) -> list[tuple["ModuleDefinition", MetricsProviderProtocol]]:
        """
        Get metrics providers from enabled modules.

        When store_id is provided, filters to modules enabled on the store's
        subscribed platforms only (prevents cross-platform content leakage).

        Args:
            db: Database session
            platform_id: Platform ID to check module enablement
            store_id: Optional store ID for subscription-aware filtering

        Returns:
            List of (module, provider) tuples for enabled modules with providers
        """
        from app.modules.registry import MODULES
        from app.modules.service import module_service

        # Determine which platform IDs to check
        if store_id:
            check_platform_ids = self._get_store_platform_ids(db, store_id)
            if not check_platform_ids:
                check_platform_ids = {platform_id}
        else:
            check_platform_ids = {platform_id}

        providers: list[tuple[ModuleDefinition, MetricsProviderProtocol]] = []

        for module in MODULES.values():
            # Skip modules without metrics providers
            if not module.has_metrics_provider():
                continue

            # Core modules are always enabled, check others
            if not module.is_core:
                enabled_on_any = False
                for pid in check_platform_ids:
                    try:
                        if module_service.is_module_enabled(db, pid, module.code):
                            enabled_on_any = True
                            break
                    except Exception as e:
                        logger.warning(
                            f"Failed to check if module {module.code} is enabled "
                            f"on platform {pid}: {e}"
                        )
                if not enabled_on_any:
                    continue

            # Get the provider instance
            try:
                provider = module.get_metrics_provider_instance()
                if provider is not None:
                    providers.append((module, provider))
            except Exception as e:
                logger.warning(
                    f"Failed to get metrics provider for module {module.code}: {e}"
                )

        return providers

    def get_store_dashboard_stats(
        self,
        db: Session,
        store_id: int,
        platform_id: int,
        context: MetricsContext | None = None,
    ) -> dict[str, list[MetricValue]]:
        """
        Get all metrics for a store, grouped by category.

        Called by the store dashboard to display store-scoped statistics.

        Args:
            db: Database session
            store_id: ID of the store to get metrics for
            platform_id: Platform ID (for module enablement check)
            context: Optional filtering/scoping context

        Returns:
            Dict mapping category name to list of MetricValue objects
        """
        providers = self._get_enabled_providers(db, platform_id, store_id=store_id)
        result: dict[str, list[MetricValue]] = {}

        for module, provider in providers:
            try:
                metrics = provider.get_store_metrics(db, store_id, context)
                if metrics:
                    result[provider.metrics_category] = metrics
            except Exception as e:
                logger.warning(
                    f"Failed to get store metrics from module {module.code}: {e}"
                )
                # Continue with other providers - graceful degradation

        return result

    def get_admin_dashboard_stats(
        self,
        db: Session,
        platform_id: int,
        context: MetricsContext | None = None,
    ) -> dict[str, list[MetricValue]]:
        """
        Get all metrics for a platform, grouped by category.

        Called by the admin dashboard to display platform-wide statistics.

        Args:
            db: Database session
            platform_id: ID of the platform to get metrics for
            context: Optional filtering/scoping context

        Returns:
            Dict mapping category name to list of MetricValue objects
        """
        providers = self._get_enabled_providers(db, platform_id)
        result: dict[str, list[MetricValue]] = {}

        for module, provider in providers:
            try:
                metrics = provider.get_platform_metrics(db, platform_id, context)
                if metrics:
                    result[provider.metrics_category] = metrics
            except Exception as e:
                logger.warning(
                    f"Failed to get platform metrics from module {module.code}: {e}"
                )
                # Continue with other providers - graceful degradation

        return result

    def get_store_stats_flat(
        self,
        db: Session,
        store_id: int,
        platform_id: int,
        context: MetricsContext | None = None,
    ) -> dict[str, Any]:
        """
        Get store metrics as a flat dictionary.

        This is a convenience method that flattens the category-grouped metrics
        into a single dictionary with metric keys as keys. Useful for backward
        compatibility with existing dashboard code.

        Args:
            db: Database session
            store_id: ID of the store to get metrics for
            platform_id: Platform ID (for module enablement check)
            context: Optional filtering/scoping context

        Returns:
            Flat dict mapping metric keys to values
        """
        categorized = self.get_store_dashboard_stats(db, store_id, platform_id, context)
        return self._flatten_metrics(categorized)

    def get_merchant_dashboard_stats(
        self,
        db: Session,
        merchant_id: int,
        platform_id: int,
        context: MetricsContext | None = None,
    ) -> dict[str, list[MetricValue]]:
        """
        Get all metrics for a merchant, grouped by category.

        Called by the merchant dashboard to display merchant-scoped statistics.

        Args:
            db: Database session
            merchant_id: ID of the merchant to get metrics for
            platform_id: Platform ID (for module enablement check)
            context: Optional filtering/scoping context

        Returns:
            Dict mapping category name to list of MetricValue objects
        """
        providers = self._get_enabled_providers(db, platform_id)
        result: dict[str, list[MetricValue]] = {}

        for module, provider in providers:
            if not hasattr(provider, "get_merchant_metrics"):
                continue
            try:
                metrics = provider.get_merchant_metrics(db, merchant_id, context)
                if metrics:
                    result[provider.metrics_category] = metrics
            except Exception as e:
                logger.warning(
                    f"Failed to get merchant metrics from module {module.code}: {e}"
                )

        return result

    def get_merchant_stats_flat(
        self,
        db: Session,
        merchant_id: int,
        platform_id: int,
        context: MetricsContext | None = None,
    ) -> dict[str, Any]:
        """
        Get merchant metrics as a flat dictionary.

        Args:
            db: Database session
            merchant_id: ID of the merchant to get metrics for
            platform_id: Platform ID (for module enablement check)
            context: Optional filtering/scoping context

        Returns:
            Flat dict mapping metric keys to values
        """
        categorized = self.get_merchant_dashboard_stats(
            db, merchant_id, platform_id, context
        )
        return self._flatten_metrics(categorized)

    def get_admin_stats_flat(
        self,
        db: Session,
        platform_id: int,
        context: MetricsContext | None = None,
    ) -> dict[str, Any]:
        """
        Get platform metrics as a flat dictionary.

        This is a convenience method that flattens the category-grouped metrics
        into a single dictionary with metric keys as keys. Useful for backward
        compatibility with existing dashboard code.

        Args:
            db: Database session
            platform_id: Platform ID
            context: Optional filtering/scoping context

        Returns:
            Flat dict mapping metric keys to values
        """
        categorized = self.get_admin_dashboard_stats(db, platform_id, context)
        return self._flatten_metrics(categorized)

    def _flatten_metrics(
        self, categorized: dict[str, list[MetricValue]]
    ) -> dict[str, Any]:
        """Flatten categorized metrics into a single dictionary."""
        flat: dict[str, Any] = {}
        for metrics in categorized.values():
            for metric in metrics:
                flat[metric.key] = metric.value
        return flat

    def get_available_categories(
        self, db: Session, platform_id: int
    ) -> list[str]:
        """
        Get list of available metric categories for a platform.

        Args:
            db: Database session
            platform_id: Platform ID (for module enablement check)

        Returns:
            List of category names from enabled providers
        """
        providers = self._get_enabled_providers(db, platform_id)
        return [provider.metrics_category for _, provider in providers]


# Singleton instance
stats_aggregator = StatsAggregatorService()

__all__ = ["StatsAggregatorService", "stats_aggregator"]
