# app/modules/registry.py
"""
Module registry - AUTO-DISCOVERED.

All modules are automatically discovered from app/modules/*/definition.py.
No manual imports needed - just create a module directory with definition.py.

The module system uses a three-tier classification:

1. CORE MODULES - Always enabled, cannot be disabled (is_core=True)
   - core: Dashboard, settings, profile
   - tenancy: Platform, merchant, store, admin user management
   - cms: Content pages, media library, themes
   - customers: Customer database, profiles, segmentation
   - billing: Platform subscriptions, store invoices (requires: payments)
   - messaging: Messages, notifications, email delivery
   - payments: Payment gateway integrations (Stripe, PayPal, etc.)
   - contracts: Cross-module protocols and interfaces

2. OPTIONAL MODULES - Can be enabled/disabled per platform (default)
   - inventory: Stock management, locations
   - catalog: Product catalog, translations, media
   - orders: Order management, customer checkout (requires: payments)
   - marketplace: Letzshop integration (requires: inventory, catalog)
   - analytics: Reports, dashboards
   - cart: Shopping cart (session-based)
   - checkout: Checkout flow (requires: cart, orders)
   - loyalty: Loyalty programs, stamps, points, digital wallets

3. INTERNAL MODULES - Admin-only tools, not customer-facing (is_internal=True)
   - dev_tools: Component library, icons, code quality
   - monitoring: Logs, background tasks, Flower link, Grafana dashboards

To add a new module:
1. Create app/modules/<code>/ directory
2. Add definition.py with ModuleDefinition instance
3. Set is_core=True or is_internal=True for non-optional modules
4. That's it! Module will be auto-discovered.
"""

import logging
from functools import lru_cache

from app.modules.base import ModuleDefinition
from app.modules.discovery import discover_modules, discover_modules_by_tier
from app.modules.enums import FrontendType

logger = logging.getLogger(__name__)


# =============================================================================
# Auto-Discovered Module Registry
# =============================================================================

@lru_cache(maxsize=1)
def _get_all_modules() -> dict[str, ModuleDefinition]:
    """Get all modules (cached)."""
    return discover_modules()


@lru_cache(maxsize=1)
def _get_modules_by_tier() -> dict[str, dict[str, ModuleDefinition]]:
    """Get modules organized by tier (cached)."""
    return discover_modules_by_tier()


# Expose as module-level variables via lazy loading to avoid circular imports
# These are computed on first access using Python's module __getattr__
def __getattr__(name: str):
    """Lazy module-level attribute access (avoids circular imports at import time)."""
    if name == "MODULES":
        return _get_all_modules()
    if name == "CORE_MODULES":
        return _get_modules_by_tier()["core"]
    if name == "OPTIONAL_MODULES":
        return _get_modules_by_tier()["optional"]
    if name == "INTERNAL_MODULES":
        return _get_modules_by_tier()["internal"]
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


# =============================================================================
# Helper Functions
# =============================================================================


def get_module(code: str) -> ModuleDefinition | None:
    """Get a module definition by code."""
    return _get_all_modules().get(code)


def get_core_modules() -> list[ModuleDefinition]:
    """Get all core modules (cannot be disabled)."""
    return list(_get_modules_by_tier()["core"].values())


def get_core_module_codes() -> set[str]:
    """Get codes of all core modules."""
    return set(_get_modules_by_tier()["core"].keys())


def get_optional_modules() -> list[ModuleDefinition]:
    """Get all optional modules (can be enabled/disabled)."""
    return list(_get_modules_by_tier()["optional"].values())


def get_optional_module_codes() -> set[str]:
    """Get codes of all optional modules."""
    return set(_get_modules_by_tier()["optional"].keys())


def get_internal_modules() -> list[ModuleDefinition]:
    """Get all internal modules (admin-only tools)."""
    return list(_get_modules_by_tier()["internal"].values())


def get_internal_module_codes() -> set[str]:
    """Get codes of all internal modules."""
    return set(_get_modules_by_tier()["internal"].keys())


def get_all_module_codes() -> set[str]:
    """Get all module codes."""
    return set(_get_all_modules().keys())


def is_core_module(code: str) -> bool:
    """Check if a module is a core module."""
    return code in _get_modules_by_tier()["core"]


def is_internal_module(code: str) -> bool:
    """Check if a module is an internal module."""
    return code in _get_modules_by_tier()["internal"]


def get_menu_item_module(menu_item_id: str, frontend_type: FrontendType) -> str | None:
    """
    Find which module provides a specific menu item.

    Args:
        menu_item_id: The menu item ID to find
        frontend_type: The frontend type to search in

    Returns:
        Module code if found, None otherwise
    """
    for module in _get_all_modules().values():
        if menu_item_id in module.get_menu_items(frontend_type):
            return module.code
    return None


def get_feature_module(feature_code: str) -> str | None:
    """
    Find which module provides a specific feature.

    Args:
        feature_code: The feature code to find

    Returns:
        Module code if found, None otherwise
    """
    for module in _get_all_modules().values():
        if module.has_feature(feature_code):
            return module.code
    return None


def validate_module_dependencies() -> list[str]:
    """
    Validate that all module dependencies are valid.

    Returns:
        List of error messages for invalid dependencies
    """
    errors = []
    all_codes = get_all_module_codes()
    core_codes = get_core_module_codes()

    for module in _get_all_modules().values():
        for required in module.requires:
            if required not in all_codes:
                errors.append(
                    f"Module '{module.code}' requires unknown module '{required}'"
                )
            # Core modules should not depend on optional modules
            if module.is_core and required not in core_codes:
                errors.append(
                    f"Core module '{module.code}' depends on optional module '{required}'"
                )

    return errors


def get_modules_by_tier() -> dict[str, list[ModuleDefinition]]:
    """
    Get modules organized by tier.

    Returns:
        Dict with keys 'core', 'optional', 'internal' mapping to module lists
    """
    by_tier = _get_modules_by_tier()
    return {
        "core": list(by_tier["core"].values()),
        "optional": list(by_tier["optional"].values()),
        "internal": list(by_tier["internal"].values()),
    }


def get_module_tier(code: str) -> str | None:
    """
    Get the tier classification of a module.

    Args:
        code: Module code

    Returns:
        'core', 'optional', 'internal', or None if not found
    """
    by_tier = _get_modules_by_tier()
    if code in by_tier["core"]:
        return "core"
    if code in by_tier["optional"]:
        return "optional"
    if code in by_tier["internal"]:
        return "internal"
    return None


def clear_registry_cache():
    """Clear the module registry cache. Useful for testing."""
    _get_all_modules.cache_clear()
    _get_modules_by_tier.cache_clear()


# =============================================================================
# Validation on Import (Development Check)
# =============================================================================

def _run_validation():
    """Run validation checks on module registry."""
    _validation_errors = validate_module_dependencies()
    if _validation_errors:
        import warnings
        for error in _validation_errors:
            warnings.warn(f"Module registry validation: {error}", stacklevel=2)


# Run validation on import (can be disabled in production)
_run_validation()


__all__ = [
    # Module dictionaries (lazy-loaded)
    "MODULES",
    "CORE_MODULES",
    "OPTIONAL_MODULES",
    "INTERNAL_MODULES",
    # Module retrieval
    "get_module",
    "get_core_modules",
    "get_core_module_codes",
    "get_optional_modules",
    "get_optional_module_codes",
    "get_internal_modules",
    "get_internal_module_codes",
    "get_all_module_codes",
    # Module classification
    "is_core_module",
    "is_internal_module",
    "get_modules_by_tier",
    "get_module_tier",
    # Menu and feature lookup
    "get_menu_item_module",
    "get_feature_module",
    # Validation
    "validate_module_dependencies",
    # Cache management
    "clear_registry_cache",
]
