# app/modules/core/services/audit_aggregator.py
"""
Audit aggregator service for collecting audit providers from all modules.

This service lives in core because audit logging is infrastructure functionality
that should be available across all modules. It discovers AuditProviders from
enabled modules, providing a unified interface for audit logging.

Benefits:
- Audit logging always works (aggregator is in core)
- Each module can provide its own audit backend
- Optional modules are truly optional (monitoring can be removed without breaking app)
- Easy to add new audit backends (just implement AuditProviderProtocol in your module)

Usage:
    from app.modules.core.services.audit_aggregator import audit_aggregator
    from app.modules.contracts.audit import AuditEvent

    # Log an admin action
    audit_aggregator.log_action(
        db=db,
        event=AuditEvent(
            admin_user_id=123,
            action="create_setting",
            target_type="setting",
            target_id="max_stores",
            details={"category": "system"},
        )
    )

    # Or use the convenience method with individual parameters
    audit_aggregator.log(
        db=db,
        admin_user_id=123,
        action="create_setting",
        target_type="setting",
        target_id="max_stores",
        details={"category": "system"},
    )
"""

import logging
from typing import TYPE_CHECKING, Any

from sqlalchemy.orm import Session

from app.modules.contracts.audit import (
    AuditEvent,
    AuditProviderProtocol,
)

if TYPE_CHECKING:
    from app.modules.base import ModuleDefinition

logger = logging.getLogger(__name__)


class AuditAggregatorService:
    """
    Aggregates audit providers from all modules.

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

    def _get_enabled_providers(
        self, db: Session
    ) -> list[tuple["ModuleDefinition", AuditProviderProtocol]]:
        """
        Get audit providers from enabled modules.

        Note: Unlike metrics/widget providers, audit logging doesn't filter
        by platform_id - it's a system-wide concern. We still need db to
        check if optional modules are enabled.

        Args:
            db: Database session

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

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

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

            # Core and internal modules are always enabled
            # For optional modules, we don't strictly check enablement for audit
            # because audit logging is system infrastructure

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

        return providers

    def log_action(self, db: Session, event: AuditEvent) -> bool:
        """
        Log an audit event using all available providers.

        Called by services/routes to record admin actions.
        Uses all available audit providers - if multiple are configured,
        all will receive the event (e.g., database + external service).

        Args:
            db: Database session
            event: The audit event to log

        Returns:
            True if at least one provider logged successfully, False otherwise
        """
        providers = self._get_enabled_providers(db)

        if not providers:
            # No audit providers available - this is acceptable
            logger.debug(
                f"No audit providers available for action {event.action} "
                f"on {event.target_type}:{event.target_id}"
            )
            return False

        any_success = False
        for module, provider in providers:
            try:
                success = provider.log_action(db, event)
                if success:
                    any_success = True
                    logger.debug(
                        f"Audit logged via {module.code}: {event.action} "
                        f"on {event.target_type}:{event.target_id}"
                    )
                else:
                    logger.warning(
                        f"Audit provider {module.code} failed to log {event.action}"
                    )
            except Exception as e:
                logger.warning(
                    f"Failed to log audit via {module.code}: {e}"
                )
                # Continue with other providers - graceful degradation

        return any_success

    def log(
        self,
        db: Session,
        admin_user_id: int,
        action: str,
        target_type: str,
        target_id: str,
        details: dict[str, Any] | None = None,
        ip_address: str | None = None,
        user_agent: str | None = None,
        request_id: str | None = None,
    ) -> bool:
        """
        Convenience method to log an audit event with individual parameters.

        This is a shorthand for creating an AuditEvent and calling log_action.
        Useful for simple logging calls without explicitly constructing the event.

        Args:
            db: Database session
            admin_user_id: ID of the admin performing the action
            action: Action performed (e.g., "create_store", "update_setting")
            target_type: Type of target (e.g., "store", "user", "setting")
            target_id: ID of the target entity (as string)
            details: Additional context about the action
            ip_address: IP address of the admin (optional)
            user_agent: User agent string (optional)
            request_id: Request ID for correlation (optional)

        Returns:
            True if at least one provider logged successfully, False otherwise
        """
        event = AuditEvent(
            admin_user_id=admin_user_id,
            action=action,
            target_type=target_type,
            target_id=str(target_id),
            details=details,
            ip_address=ip_address,
            user_agent=user_agent,
            request_id=request_id,
        )
        return self.log_action(db, event)

    def get_available_backends(self, db: Session) -> list[str]:
        """
        Get list of available audit backends.

        Args:
            db: Database session

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


# Singleton instance
audit_aggregator = AuditAggregatorService()

__all__ = ["AuditAggregatorService", "audit_aggregator"]
