# app/modules/migrations.py
"""
Module migration discovery utility.

Provides utilities for discovering and managing module-specific migrations.
Each self-contained module can have its own migrations directory that will
be included in Alembic's version locations.

Module Migration Structure:
    app/modules/<code>/
    └── migrations/
        └── versions/
            ├── <module>_001_initial.py
            ├── <module>_002_add_field.py
            └── ...

Migration Naming Convention:
    {module_code}_{sequence}_{description}.py
    Example: cms_001_create_content_pages.py

This ensures no collision between modules and makes it clear which
module owns each migration.

Usage:
    # Get all migration paths for Alembic
    from app.modules.migrations import get_all_migration_paths

    paths = get_all_migration_paths()
    # Returns: [Path("alembic/versions"), Path("app/modules/cms/migrations/versions"), ...]

    # In alembic/env.py
    context.configure(
        version_locations=[str(p) for p in get_all_migration_paths()],
        ...
    )
"""

import logging
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from app.modules.base import ModuleDefinition

logger = logging.getLogger(__name__)


def get_core_migrations_path() -> Path:
    """
    Get the path to the core (non-module) migrations directory.

    Returns:
        Path to alembic/versions/
    """
    # Navigate from app/modules/migrations.py to project root
    project_root = Path(__file__).parent.parent.parent
    return project_root / "alembic" / "versions"


def get_module_migrations_path(module: "ModuleDefinition") -> Path | None:
    """
    Get the migrations path for a specific module.

    Args:
        module: Module definition to get migrations path for

    Returns:
        Path to module's migrations/versions/ directory, or None if not configured
    """
    migrations_dir = module.get_migrations_dir()
    if migrations_dir is None:
        return None

    versions_dir = migrations_dir / "versions"
    return versions_dir


def discover_module_migrations() -> list[Path]:
    """
    Discover all module migration directories.

    Scans all registered modules for those with migrations_path configured
    and returns paths to their versions directories.

    Returns:
        List of paths to module migration version directories
    """
    # Import here to avoid circular imports
    from app.modules.registry import MODULES

    paths: list[Path] = []

    for module in MODULES.values():
        if not module.migrations_path:
            continue

        versions_path = get_module_migrations_path(module)
        if versions_path is None:
            continue

        if versions_path.exists():
            paths.append(versions_path)
            logger.debug(f"Found migrations for module {module.code}: {versions_path}")
        else:
            logger.debug(
                f"Module {module.code} has migrations_path but no versions directory"
            )

    return sorted(paths)  # Sort for deterministic ordering


def get_all_migration_paths() -> list[Path]:
    """
    Get all migration paths including core and module migrations.

    Returns:
        List of paths starting with core migrations, followed by module migrations
        in alphabetical order by module code.

    Example:
        [
            Path("alembic/versions"),
            Path("app/modules/billing/migrations/versions"),
            Path("app/modules/cms/migrations/versions"),
        ]
    """
    paths = [get_core_migrations_path()]
    paths.extend(discover_module_migrations())
    return paths


def get_migration_order() -> list[str]:
    """
    Get the order in which module migrations should be applied.

    Returns migrations in dependency order - modules with no dependencies first,
    then modules that depend on them, etc.

    Returns:
        List of module codes in migration order
    """
    # Import here to avoid circular imports
    from app.modules.registry import MODULES

    # Build dependency graph
    modules_with_migrations = [
        m for m in MODULES.values() if m.migrations_path
    ]

    if not modules_with_migrations:
        return []

    # Topological sort based on dependencies
    ordered: list[str] = []
    visited: set[str] = set()
    temp_visited: set[str] = set()

    def visit(code: str) -> None:
        if code in visited:
            return
        if code in temp_visited:
            raise ValueError(f"Circular dependency detected involving {code}")

        module = MODULES.get(code)
        if module is None:
            return

        temp_visited.add(code)

        # Visit dependencies first
        for dep in module.requires:
            if dep in {m.code for m in modules_with_migrations}:
                visit(dep)

        temp_visited.remove(code)
        visited.add(code)

        if module.migrations_path:
            ordered.append(code)

    for module in modules_with_migrations:
        visit(module.code)

    return ordered


def validate_migration_names() -> list[str]:
    """
    Validate that all module migrations follow the naming convention.

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

    for path in discover_module_migrations():
        # Extract module code from path (e.g., app/modules/cms/migrations/versions -> cms)
        module_dir = path.parent.parent  # migrations/versions -> migrations -> module
        module_code = module_dir.name.replace("_", "-")  # cms or dev_tools -> dev-tools

        for migration_file in path.glob("*.py"):
            name = migration_file.stem
            if name == "__pycache__":
                continue

            # Check prefix matches module code
            expected_prefix = module_code.replace("-", "_")
            if not name.startswith(f"{expected_prefix}_"):
                errors.append(
                    f"Migration {migration_file} should start with '{expected_prefix}_'"
                )

    return errors


def create_module_migrations_dir(module: "ModuleDefinition") -> Path:
    """
    Create the migrations directory structure for a module.

    Args:
        module: Module to create migrations for

    Returns:
        Path to the created versions directory
    """
    module_dir = module.get_module_dir()
    migrations_dir = module_dir / "migrations"
    versions_dir = migrations_dir / "versions"

    versions_dir.mkdir(parents=True, exist_ok=True)

    # Create __init__.py files
    init_files = [
        migrations_dir / "__init__.py",
        versions_dir / "__init__.py",
    ]
    for init_file in init_files:
        if not init_file.exists():
            init_file.write_text('"""Module migrations."""\n')

    logger.info(f"Created migrations directory for module {module.code}")
    return versions_dir


__all__ = [
    "get_core_migrations_path",
    "get_module_migrations_path",
    "discover_module_migrations",
    "get_all_migration_paths",
    "get_migration_order",
    "validate_migration_names",
    "create_module_migrations_dir",
]
