# app/modules/core/services/menu_discovery_service.py
"""
Menu Discovery Service - Discovers and aggregates menu items from all modules.

This service implements the module-driven menu system where each module
defines its own menu items through MenuSectionDefinition and MenuItemDefinition
in its definition.py file.

Key Features:
- Discovers menu definitions from all loaded modules
- Filters by module enablement (disabled modules = hidden menus)
- Respects user/platform visibility preferences (AdminMenuConfig)
- Supports permission-based filtering
- Enforces mandatory item visibility

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

    # Get complete menu for admin frontend
    menu = menu_discovery_service.get_menu_for_frontend(
        db,
        FrontendType.ADMIN,
        platform_id=1,
        user=current_user
    )

    # Get flat list of all menu items for configuration UI
    items = menu_discovery_service.get_all_menu_items(FrontendType.ADMIN)
"""

import logging
from copy import deepcopy
from dataclasses import dataclass, field

from sqlalchemy.orm import Session

from app.modules.base import MenuSectionDefinition
from app.modules.enums import FrontendType
from app.modules.service import module_service

logger = logging.getLogger(__name__)


@dataclass
class DiscoveredMenuItem:
    """
    A menu item discovered from a module, enriched with runtime info.

    Extends MenuItemDefinition with runtime context like visibility status,
    module enablement, and resolved route.
    """

    id: str
    label_key: str
    icon: str
    route: str
    order: int
    is_mandatory: bool
    requires_permission: str | None
    badge_source: str | None
    is_super_admin_only: bool

    # Runtime enrichment
    module_code: str
    section_id: str
    section_label_key: str | None
    section_order: int
    is_visible: bool = True
    is_module_enabled: bool = True
    header_template: str | None = None


@dataclass
class DiscoveredMenuSection:
    """
    A menu section discovered from modules, with aggregated items.

    Multiple modules may contribute items to the same section.
    """

    id: str
    label_key: str | None
    icon: str | None
    order: int
    is_super_admin_only: bool
    is_collapsible: bool
    items: list[DiscoveredMenuItem] = field(default_factory=list)


class MenuDiscoveryService:
    """
    Service to discover and aggregate menu items from all enabled modules.

    This service:
    1. Collects menu definitions from all module definition.py files
    2. Filters by module enablement for the platform
    3. Applies user/platform visibility preferences
    4. Supports permission-based filtering
    5. Returns sorted, renderable menu structures
    """

    def discover_all_menus(self) -> dict[FrontendType, list[MenuSectionDefinition]]:
        """
        Discover all menu definitions from all loaded modules.

        Returns:
            Dict mapping FrontendType to list of MenuSectionDefinition
            from all modules (not filtered by enablement).
        """
        from app.modules.registry import MODULES

        all_menus: dict[FrontendType, list[MenuSectionDefinition]] = {
            ft: [] for ft in FrontendType
        }

        for _module_code, module_def in MODULES.items():
            for frontend_type, sections in module_def.menus.items():
                all_menus[frontend_type].extend(deepcopy(sections))

        return all_menus

    def get_menu_sections_for_frontend(
        self,
        db: Session,
        frontend_type: FrontendType,
        platform_id: int | None = None,
        enabled_module_codes: set[str] | None = None,
    ) -> list[DiscoveredMenuSection]:
        """
        Get aggregated menu sections for a frontend type.

        Filters by module enablement if platform_id is provided.
        Does NOT apply user visibility preferences (use get_menu_for_frontend for that).

        Args:
            db: Database session
            frontend_type: Frontend type to get menus for
            platform_id: Platform ID for module enablement filtering
            enabled_module_codes: If provided, overrides single-platform lookup.
                A module is considered enabled if its code is in this set.
                Useful for merchant portal where a merchant may span multiple platforms.

        Returns:
            List of DiscoveredMenuSection sorted by order
        """
        from app.modules.registry import MODULES

        # Track sections by ID for aggregation
        sections_map: dict[str, DiscoveredMenuSection] = {}

        for module_code, module_def in MODULES.items():
            # Check if module is enabled
            if enabled_module_codes is not None:
                is_module_enabled = module_code in enabled_module_codes
            elif platform_id:
                is_module_enabled = module_service.is_module_enabled(
                    db, platform_id, module_code
                )
            else:
                is_module_enabled = True

            # Get menu sections for this frontend type
            module_sections = module_def.menus.get(frontend_type, [])

            for section in module_sections:
                # Get or create section entry
                if section.id not in sections_map:
                    sections_map[section.id] = DiscoveredMenuSection(
                        id=section.id,
                        label_key=section.label_key,
                        icon=section.icon,
                        order=section.order,
                        is_super_admin_only=section.is_super_admin_only,
                        is_collapsible=section.is_collapsible,
                        items=[],
                    )

                # Add items from this module to the section
                for item in section.items:
                    discovered_item = DiscoveredMenuItem(
                        id=item.id,
                        label_key=item.label_key,
                        icon=item.icon,
                        route=item.route,
                        order=item.order,
                        is_mandatory=item.is_mandatory,
                        requires_permission=item.requires_permission,
                        badge_source=item.badge_source,
                        is_super_admin_only=item.is_super_admin_only,
                        module_code=module_code,
                        section_id=section.id,
                        section_label_key=section.label_key,
                        section_order=section.order,
                        is_module_enabled=is_module_enabled,
                        header_template=item.header_template,
                    )
                    sections_map[section.id].items.append(discovered_item)

        # Sort sections by order
        sorted_sections = sorted(sections_map.values(), key=lambda s: s.order)

        # Sort items within each section
        for section in sorted_sections:
            section.items.sort(key=lambda i: i.order)

        return sorted_sections

    def get_menu_for_frontend(
        self,
        db: Session,
        frontend_type: FrontendType,
        platform_id: int | None = None,
        user_id: int | None = None,
        is_super_admin: bool = False,
        store_code: str | None = None,
        enabled_module_codes: set[str] | None = None,
        user_permissions: list[str] | None = None,
    ) -> list[DiscoveredMenuSection]:
        """
        Get filtered menu structure for frontend rendering.

        Applies all filters:
        1. Module enablement (disabled modules = hidden items)
        2. Visibility configuration (AdminMenuConfig preferences)
        3. Super admin status (hides super_admin_only items for non-super-admins)
        4. Permission requirements (filter by user permissions)

        Args:
            db: Database session
            frontend_type: Frontend type (ADMIN, STORE, etc.)
            platform_id: Platform ID for module enablement and visibility
            user_id: User ID for user-specific visibility (super admins only)
            is_super_admin: Whether the user is a super admin
            store_code: Store code for route placeholder replacement
            enabled_module_codes: If provided, overrides single-platform lookup
                for module enablement. Passed through to get_menu_sections_for_frontend.
            user_permissions: List of permission IDs the user has. If provided,
                items with requires_permission set will be hidden unless the
                permission is in this list. None = no permission filtering.

        Returns:
            List of DiscoveredMenuSection with filtered and sorted items
        """
        # Get all sections with module enablement filtering
        sections = self.get_menu_sections_for_frontend(
            db, frontend_type, platform_id, enabled_module_codes=enabled_module_codes
        )

        # Get visibility configuration
        visible_item_ids = self._get_visible_item_ids(
            db, frontend_type, platform_id, user_id
        )

        # Filter sections and items
        filtered_sections = []
        for section in sections:
            # Skip super_admin_only sections for non-super-admins
            if section.is_super_admin_only and not is_super_admin:
                continue

            # Filter items
            filtered_items = []
            for item in section.items:
                # Skip if module is disabled
                if not item.is_module_enabled:
                    continue

                # Skip super_admin_only items for non-super-admins
                if item.is_super_admin_only and not is_super_admin:
                    continue

                # Apply visibility (mandatory items always visible)
                if visible_item_ids is not None and not item.is_mandatory:
                    if item.id not in visible_item_ids:
                        continue

                # Apply permission filtering
                if (
                    user_permissions is not None
                    and item.requires_permission
                    and item.requires_permission not in user_permissions
                ):
                    continue

                # Resolve route placeholders
                if store_code and "{store_code}" in item.route:
                    item.route = item.route.replace("{store_code}", store_code)

                item.is_visible = True
                filtered_items.append(item)

            # Only include section if it has visible items
            if filtered_items:
                section.items = filtered_items
                filtered_sections.append(section)

        return filtered_sections

    def _get_visible_item_ids(
        self,
        db: Session,
        frontend_type: FrontendType,
        platform_id: int | None = None,
        user_id: int | None = None,
    ) -> set[str] | None:
        """
        Get set of visible menu item IDs from AdminMenuConfig.

        Returns:
            Set of visible item IDs, or None if no config exists (default all visible)
        """
        from app.modules.core.models import AdminMenuConfig

        if not platform_id and not user_id:
            return None

        query = db.query(AdminMenuConfig).filter(
            AdminMenuConfig.frontend_type == frontend_type,
        )

        if platform_id:
            query = query.filter(AdminMenuConfig.platform_id == platform_id)
        elif user_id:
            query = query.filter(AdminMenuConfig.user_id == user_id)

        configs = query.all()
        if not configs:
            return None  # No config = all visible by default

        return {c.menu_item_id for c in configs if c.is_visible}

    def get_all_menu_items(
        self,
        frontend_type: FrontendType,
    ) -> list[DiscoveredMenuItem]:
        """
        Get flat list of all menu items for a frontend type.

        Useful for configuration UI where you need to show all possible items.
        Does NOT filter by module enablement or visibility.

        Args:
            frontend_type: Frontend type to get items for

        Returns:
            Flat list of DiscoveredMenuItem from all modules
        """
        from app.modules.registry import MODULES

        items = []

        for module_code, module_def in MODULES.items():
            for section in module_def.menus.get(frontend_type, []):
                for item in section.items:
                    discovered_item = DiscoveredMenuItem(
                        id=item.id,
                        label_key=item.label_key,
                        icon=item.icon,
                        route=item.route,
                        order=item.order,
                        is_mandatory=item.is_mandatory,
                        requires_permission=item.requires_permission,
                        badge_source=item.badge_source,
                        is_super_admin_only=item.is_super_admin_only,
                        module_code=module_code,
                        section_id=section.id,
                        section_label_key=section.label_key,
                        section_order=section.order,
                    )
                    items.append(discovered_item)

        return sorted(items, key=lambda i: (i.section_order, i.order))

    def get_mandatory_item_ids(
        self,
        frontend_type: FrontendType,
    ) -> set[str]:
        """
        Get all mandatory menu item IDs for a frontend type.

        Mandatory items cannot be hidden by users.

        Args:
            frontend_type: Frontend type to get mandatory items for

        Returns:
            Set of menu item IDs marked as is_mandatory=True
        """
        from app.modules.registry import MODULES

        mandatory_ids = set()

        for module_def in MODULES.values():
            for section in module_def.menus.get(frontend_type, []):
                for item in section.items:
                    if item.is_mandatory:
                        mandatory_ids.add(item.id)

        return mandatory_ids

    def get_all_menu_item_ids(
        self,
        frontend_type: FrontendType,
    ) -> set[str]:
        """
        Get set of all menu item IDs for a frontend type.

        Args:
            frontend_type: Frontend type to get item IDs for

        Returns:
            Set of all menu item IDs from all modules
        """
        from app.modules.registry import MODULES

        item_ids = set()

        for module_def in MODULES.values():
            for section in module_def.menus.get(frontend_type, []):
                for item in section.items:
                    item_ids.add(item.id)

        return item_ids

    def get_menu_item(
        self,
        frontend_type: FrontendType,
        menu_item_id: str,
    ) -> DiscoveredMenuItem | None:
        """
        Get a specific menu item by ID.

        Args:
            frontend_type: Frontend type to search in
            menu_item_id: Menu item ID to look up

        Returns:
            DiscoveredMenuItem if found, None otherwise
        """
        from app.modules.registry import MODULES

        for module_code, module_def in MODULES.items():
            for section in module_def.menus.get(frontend_type, []):
                for item in section.items:
                    if item.id == menu_item_id:
                        return DiscoveredMenuItem(
                            id=item.id,
                            label_key=item.label_key,
                            icon=item.icon,
                            route=item.route,
                            order=item.order,
                            is_mandatory=item.is_mandatory,
                            requires_permission=item.requires_permission,
                            badge_source=item.badge_source,
                            is_super_admin_only=item.is_super_admin_only,
                            module_code=module_code,
                            section_id=section.id,
                            section_label_key=section.label_key,
                            section_order=section.order,
                        )

        return None

    def is_super_admin_only_item(
        self,
        menu_item_id: str,
        frontend_type: FrontendType = FrontendType.ADMIN,
    ) -> bool:
        """
        Check if a menu item is super-admin only.

        An item is super-admin only if either:
        - The item itself has is_super_admin_only=True
        - The section containing the item has is_super_admin_only=True

        Args:
            menu_item_id: Menu item ID to check
            frontend_type: Frontend type to search in

        Returns:
            True if the item is super-admin only
        """
        from app.modules.registry import MODULES

        for module_def in MODULES.values():
            for section in module_def.menus.get(frontend_type, []):
                # Check if section is super-admin only
                section_is_super_admin = section.is_super_admin_only

                for item in section.items:
                    if item.id == menu_item_id:
                        return section_is_super_admin or item.is_super_admin_only

        return False

    def get_menu_item_module(
        self,
        menu_item_id: str,
        frontend_type: FrontendType,
    ) -> str | None:
        """
        Get the module code that provides a specific menu item.

        Args:
            menu_item_id: Menu item ID to look up
            frontend_type: Frontend type to search in

        Returns:
            Module code, or None if not found
        """
        from app.modules.registry import MODULES

        for module_code, module_def in MODULES.items():
            for section in module_def.menus.get(frontend_type, []):
                for item in section.items:
                    if item.id == menu_item_id:
                        return module_code

        return None


# Singleton instance
menu_discovery_service = MenuDiscoveryService()


__all__ = [
    "menu_discovery_service",
    "MenuDiscoveryService",
    "DiscoveredMenuItem",
    "DiscoveredMenuSection",
]
