"""Main migration orchestration.

This module provides the Migrator class which coordinates the migration
of containers between runtimes.
"""

from __future__ import annotations

import logging
from datetime import datetime
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Callable

from gaol.migration.exceptions import (
    MigrationError,
    MigrationExportError,
    MigrationImportError,
    MigrationRollbackError,
    MigrationValidationError,
)
from gaol.migration.models import (
    MigrationPlan,
    MigrationState,
    MigrationStatus,
)
from gaol.migration.translator import FeatureTranslator
from gaol.models.container import ContainerConfig, ContainerStatus
from gaol.runtimes import get_runtime
from gaol.store import ContainerStore, get_container_store

logger = logging.getLogger(__name__)


class Migrator:
    """Orchestrates container migration between runtimes.

    The Migrator handles the complete migration workflow:
    1. Validation - Check source container and target runtime
    2. Planning - Analyze features and generate warnings
    3. Export - Export filesystem from source runtime
    4. Import - Create container on target runtime
    5. Update - Update container store
    6. Cleanup - Optionally remove source container

    Example:
        >>> migrator = Migrator()
        >>> plan = migrator.create_plan("my-container", "nspawn")
        >>> if not plan.has_critical_warnings:
        ...     state = migrator.execute(plan)
    """

    def __init__(
        self,
        store: ContainerStore | None = None,
        translator: FeatureTranslator | None = None,
    ):
        """Initialize migrator.

        Args:
            store: Container store for configuration persistence.
            translator: Feature translator for config conversion.
        """
        self.store = store or get_container_store()
        self.translator = translator or FeatureTranslator()

    def create_plan(
        self,
        container_name: str,
        target_runtime: str,
        preserve_source: bool = True,
    ) -> MigrationPlan:
        """Create a migration plan without executing it.

        Analyzes the source container and target runtime to determine
        what changes will be made and what warnings should be shown.

        Args:
            container_name: Name of the container to migrate.
            target_runtime: Target runtime to migrate to.
            preserve_source: Whether to keep the source container.

        Returns:
            MigrationPlan with analysis results.

        Raises:
            MigrationValidationError: If validation fails.
        """
        # Get stored container
        stored = self.store.get(container_name)
        if not stored:
            raise MigrationValidationError(f"Container not found: {container_name}")

        source_runtime = stored.runtime

        # Check if migration is supported
        supported, reason = self.translator.is_migration_supported(
            source_runtime, target_runtime
        )
        if not supported:
            raise MigrationValidationError(reason or "Migration not supported")

        # Check target runtime is available
        try:
            target_rt = get_runtime(target_runtime)
            if not target_rt.is_available():
                raise MigrationValidationError(
                    f"Target runtime '{target_runtime}' is not available"
                )
        except Exception as e:
            raise MigrationValidationError(
                f"Cannot access target runtime: {e}"
            ) from e

        # Analyze feature compatibility
        translated_config, warnings = self.translator.analyze_migration(
            stored.config,
            source_runtime,
            target_runtime,
        )

        # Try to estimate filesystem size
        estimated_size = None
        try:
            source_rt = get_runtime(source_runtime)
            container_id = stored.container_id or container_name
            estimated_size = source_rt.get_filesystem_size(container_id)
        except (NotImplementedError, Exception):
            # Size estimation is optional
            pass

        return MigrationPlan(
            container_name=container_name,
            source_runtime=source_runtime,
            target_runtime=target_runtime,
            preserve_source=preserve_source,
            estimated_size_bytes=estimated_size,
            feature_warnings=warnings,
            translated_config=translated_config.model_dump(),
        )

    def execute(
        self,
        plan: MigrationPlan,
        progress_callback: Callable[[MigrationStatus, str], None] | None = None,
    ) -> MigrationState:
        """Execute a migration plan.

        Performs the actual migration, exporting the container filesystem
        and importing it to the target runtime.

        Args:
            plan: The migration plan to execute.
            progress_callback: Optional callback for progress updates.

        Returns:
            MigrationState with the final status.

        Raises:
            MigrationError: If migration fails and rollback fails.
        """
        state = MigrationState(plan=plan)
        state.mark_started()

        if plan.dry_run:
            state.status = MigrationStatus.COMPLETED
            return state

        source_rt = get_runtime(plan.source_runtime)
        target_rt = get_runtime(plan.target_runtime)
        stored = self.store.get(plan.container_name)

        if not stored:
            state.mark_failed("Container not found in store")
            return state

        source_id = stored.container_id or plan.container_name
        state.source_container_id = source_id

        # Check if source is running and stop it
        try:
            status = source_rt.status(source_id)
            state.source_was_running = status.status == ContainerStatus.RUNNING
            if state.source_was_running:
                self._update_progress(
                    state, MigrationStatus.VALIDATING,
                    "Stopping source container...", progress_callback
                )
                source_rt.stop(source_id)
        except Exception as e:
            logger.warning(f"Could not check source container status: {e}")

        try:
            with TemporaryDirectory(prefix="gaol-migrate-") as tmpdir:
                tmppath = Path(tmpdir)

                # Phase 1: Export filesystem
                self._update_progress(
                    state, MigrationStatus.EXPORTING,
                    "Exporting filesystem...", progress_callback
                )
                try:
                    export_path = source_rt.export_filesystem(source_id, tmppath)
                    state.export_path = export_path
                    logger.info(f"Exported filesystem to {export_path}")
                except NotImplementedError:
                    raise MigrationExportError(
                        f"Runtime '{plan.source_runtime}' does not support filesystem export"
                    )
                except Exception as e:
                    raise MigrationExportError(f"Export failed: {e}") from e

                # Phase 2: Import to target runtime
                self._update_progress(
                    state, MigrationStatus.IMPORTING,
                    "Importing to target runtime...", progress_callback
                )
                try:
                    # Build config for target
                    target_config = ContainerConfig(**plan.translated_config)

                    target_id = target_rt.import_filesystem(target_config, export_path)
                    state.target_container_id = target_id
                    logger.info(f"Imported to target container: {target_id}")
                except NotImplementedError:
                    raise MigrationImportError(
                        f"Runtime '{plan.target_runtime}' does not support filesystem import"
                    )
                except Exception as e:
                    raise MigrationImportError(f"Import failed: {e}") from e

                # Phase 3: Update store
                self._update_progress(
                    state, MigrationStatus.UPDATING_STORE,
                    "Updating configuration store...", progress_callback
                )
                try:
                    self.store.save(
                        target_config,
                        container_id=target_id,
                        runtime=plan.target_runtime,
                    )
                except Exception as e:
                    # Try to rollback the import
                    self._rollback_import(target_rt, target_id, state)
                    raise MigrationError(f"Store update failed: {e}") from e

                # Phase 4: Optional cleanup of source
                if not plan.preserve_source:
                    self._update_progress(
                        state, MigrationStatus.CLEANING_UP,
                        "Removing source container...", progress_callback
                    )
                    try:
                        source_rt.destroy(source_id, force=True)
                    except Exception as e:
                        # Log but don't fail - migration was successful
                        msg = f"Could not remove source container: {e}"
                        logger.warning(msg)
                        state.rollback_actions.append(f"Manual cleanup needed: {msg}")

            state.mark_completed()
            logger.info(f"Migration completed: {plan.container_name} -> {plan.target_runtime}")

        except MigrationError:
            raise
        except Exception as e:
            state.mark_failed(str(e), state.status)

            # Attempt rollback if we created a target container
            if state.target_container_id and state.can_rollback:
                try:
                    self._rollback_import(target_rt, state.target_container_id, state)
                    state.mark_rolled_back()
                except Exception as rollback_error:
                    state.can_rollback = False
                    raise MigrationRollbackError(e, rollback_error) from e

            raise MigrationError(f"Migration failed: {e}") from e

        return state

    def _update_progress(
        self,
        state: MigrationState,
        status: MigrationStatus,
        message: str,
        callback: Callable[[MigrationStatus, str], None] | None,
    ) -> None:
        """Update migration state and notify callback.

        Args:
            state: Current migration state.
            status: New status.
            message: Progress message.
            callback: Optional callback to notify.
        """
        state.status = status
        logger.debug(f"Migration progress: {status.value} - {message}")
        if callback:
            callback(status, message)

    def _rollback_import(
        self,
        runtime,
        container_id: str,
        state: MigrationState,
    ) -> None:
        """Attempt to rollback an imported container.

        Args:
            runtime: Target runtime.
            container_id: Container to destroy.
            state: Migration state to update.
        """
        try:
            runtime.destroy(container_id, force=True)
            state.rollback_actions.append(f"Destroyed target container: {container_id}")
        except Exception as e:
            state.rollback_actions.append(f"Failed to destroy target: {e}")
            raise


# Module-level singleton
_migrator: Migrator | None = None


def get_migrator() -> Migrator:
    """Get the global Migrator instance.

    Returns:
        Shared Migrator instance.
    """
    global _migrator
    if _migrator is None:
        _migrator = Migrator()
    return _migrator
