# app/modules/discovery.py
"""
Module auto-discovery system.

Provides automatic discovery of modules from app/modules/*/definition.py.
This enables true plug-and-play modules - just create a module directory
with a definition.py file and it will be automatically registered.

Module Discovery Rules:
1. Module must have app/modules/<code>/definition.py
2. definition.py must define a ModuleDefinition instance
3. The instance variable name should be <code>_module (e.g., cms_module)
4. Module tier is determined by is_core and is_internal flags

Usage:
    # Auto-discover all modules (replaces manual imports in registry.py)
    from app.modules.discovery import discover_modules

    MODULES = discover_modules()

Directory Structure for Auto-Discovery:
    app/modules/
    ├── cms/
    │   ├── __init__.py
    │   ├── definition.py      <- Must export ModuleDefinition
    │   ├── routes/
    │   │   ├── api/
    │   │   └── pages/
    │   ├── services/
    │   ├── models/
    │   ├── schemas/
    │   ├── templates/
    │   ├── static/
    │   ├── locales/
    │   └── tasks/
    └── analytics/
        └── ...
"""

import importlib
import logging
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from app.modules.base import ModuleDefinition

logger = logging.getLogger(__name__)

# Modules directory
MODULES_DIR = Path(__file__).parent


def discover_modules() -> dict[str, "ModuleDefinition"]:
    """
    Auto-discover all modules from app/modules/*/definition.py.

    Scans the modules directory for subdirectories containing definition.py,
    imports them, and extracts ModuleDefinition instances.

    Returns:
        Dictionary mapping module code to ModuleDefinition

    Example:
        >>> modules = discover_modules()
        >>> modules.keys()
        dict_keys(['cms', 'analytics', 'billing', ...])
    """
    from app.modules.base import ModuleDefinition

    modules: dict[str, ModuleDefinition] = {}

    if not MODULES_DIR.exists():
        logger.warning(f"Modules directory not found: {MODULES_DIR}")
        return modules

    for module_dir in sorted(MODULES_DIR.iterdir()):
        # Skip non-directories and special files
        if not module_dir.is_dir():
            continue
        if module_dir.name.startswith("_") or module_dir.name.startswith("."):
            continue

        definition_file = module_dir / "definition.py"
        if not definition_file.exists():
            continue

        try:
            # Import the definition module
            dir_name = module_dir.name
            import_path = f"app.modules.{dir_name}.definition"
            definition_module = importlib.import_module(import_path)

            # Find ModuleDefinition instances in the module
            for attr_name in dir(definition_module):
                if attr_name.startswith("_"):
                    continue

                attr = getattr(definition_module, attr_name)
                if isinstance(attr, ModuleDefinition):
                    modules[attr.code] = attr
                    logger.debug(f"Discovered module: {attr.code} from {import_path}")
                    break
            else:
                logger.warning(f"No ModuleDefinition found in {import_path}")

        except ImportError as e:
            logger.error(f"Failed to import {import_path}: {e}")
        except Exception as e:
            logger.error(f"Error discovering module in {module_dir}: {e}")

    # Log summary by tier
    core = [c for c, m in modules.items() if m.is_core]
    internal = [c for c, m in modules.items() if m.is_internal]
    optional = [c for c, m in modules.items() if not m.is_core and not m.is_internal]

    logger.info(
        f"[MODULES] Auto-discovered {len(modules)} modules: "
        f"{len(core)} core ({', '.join(sorted(core))}), "
        f"{len(optional)} optional ({', '.join(sorted(optional))}), "
        f"{len(internal)} internal ({', '.join(sorted(internal))})"
    )
    return modules


def discover_modules_by_tier() -> dict[str, dict[str, "ModuleDefinition"]]:
    """
    Discover modules and organize by tier.

    Returns:
        Dict with keys 'core', 'optional', 'internal' mapping to module dicts

    Example:
        >>> by_tier = discover_modules_by_tier()
        >>> by_tier['core'].keys()
        dict_keys(['core', 'tenancy', 'cms', 'customers'])
    """
    modules = discover_modules()

    result = {
        "core": {},
        "optional": {},
        "internal": {},
    }

    for code, module in modules.items():
        tier = module.get_tier()
        result[tier][code] = module

    return result


def get_module_codes() -> set[str]:
    """Get all discovered module codes."""
    return set(discover_modules().keys())


def validate_module_discovery() -> list[str]:
    """
    Validate that all module directories have proper structure.

    Returns:
        List of validation errors (empty if all valid)
    """
    errors: list[str] = []

    if not MODULES_DIR.exists():
        return ["Modules directory not found"]

    for module_dir in sorted(MODULES_DIR.iterdir()):
        if not module_dir.is_dir():
            continue
        if module_dir.name.startswith("_") or module_dir.name.startswith("."):
            continue

        # Check for definition.py
        definition_file = module_dir / "definition.py"
        if not definition_file.exists():
            # Only warn if it looks like a module (has __init__.py)
            if (module_dir / "__init__.py").exists():
                errors.append(
                    f"Module directory '{module_dir.name}' has __init__.py but no definition.py"
                )
            continue

        # Check for __init__.py
        init_file = module_dir / "__init__.py"
        if not init_file.exists():
            errors.append(f"Module '{module_dir.name}' missing __init__.py")

    return errors


__all__ = [
    "discover_modules",
    "discover_modules_by_tier",
    "get_module_codes",
    "validate_module_discovery",
    "MODULES_DIR",
]
