"""High-level API for container update management."""

from __future__ import annotations

import logging
from datetime import datetime
from typing import TYPE_CHECKING

from gaol.distro import get_distro_info
from gaol.store import get_container_store
from gaol.update.exceptions import (
    ContainerNotFoundError,
    ContainerNotRunningError,
    PolicyNotFoundError,
    SnapshotError,
)
from gaol.update.executor import UpdateExecutor
from gaol.update.models import (
    RollbackStrategy,
    UpdateCheck,
    UpdateHistory,
    UpdatePolicy,
    UpdateResult,
    UpdateSchedule,
    UpdateScheduleType,
    UpdateStatus,
)
from gaol.update.scheduler import UpdateScheduler, get_update_scheduler
from gaol.update.store import UpdateStore, get_update_store

if TYPE_CHECKING:
    from gaol.runtimes.base import ContainerRuntime
    from gaol.store import ContainerStore, StoredContainer


logger = logging.getLogger(__name__)


class UpdateManager:
    """High-level API for container update management.

    Provides a unified interface for:
    - Checking for available updates
    - Applying updates with automatic rollback
    - Managing update policies and schedules
    - Viewing update history

    Example:
        >>> manager = get_update_manager()
        >>> # Check for updates
        >>> check = manager.check("mycontainer")
        >>> if check.updates_available:
        ...     result = manager.apply("mycontainer")
        >>> # Set up automatic updates
        >>> manager.set_schedule("mycontainer", UpdateScheduleType.DAILY, "03:00")
    """

    def __init__(
        self,
        container_store: ContainerStore | None = None,
        update_store: UpdateStore | None = None,
        runtime_factory: callable | None = None,
    ) -> None:
        """Initialize the update manager.

        Args:
            container_store: Container store (defaults to global instance)
            update_store: Update store (defaults to global instance)
            runtime_factory: Factory function to create runtime for a container
        """
        self._container_store = container_store or get_container_store()
        self._update_store = update_store or get_update_store()
        self._runtime_factory = runtime_factory or self._default_runtime_factory
        self._scheduler: UpdateScheduler | None = None

    def _default_runtime_factory(self, stored: StoredContainer) -> ContainerRuntime:
        """Create runtime for a container."""
        from gaol.runtimes import get_runtime

        return get_runtime(stored.config.runtime)

    def _get_container(self, name: str) -> StoredContainer:
        """Get a container by name or raise error."""
        stored = self._container_store.get(name)
        if stored is None:
            raise ContainerNotFoundError(name)
        return stored

    def _get_runtime_and_id(
        self, stored: StoredContainer
    ) -> tuple[ContainerRuntime, str]:
        """Get runtime and container ID, ensuring container is running."""
        runtime = self._runtime_factory(stored)
        container_id = stored.runtime_id
        if container_id is None:
            raise ContainerNotRunningError(stored.config.name)

        # Verify container is running
        try:
            info = runtime.inspect(container_id)
            if info.state.lower() != "running":
                raise ContainerNotRunningError(stored.config.name)
        except RuntimeError:
            raise ContainerNotRunningError(stored.config.name)

        return runtime, container_id

    # -------------------------------------------------------------------------
    # Update Operations
    # -------------------------------------------------------------------------

    def check(self, container_name: str) -> UpdateCheck:
        """Check for available updates in a container.

        Args:
            container_name: Name of the container

        Returns:
            UpdateCheck with available updates

        Raises:
            ContainerNotFoundError: If container doesn't exist
            ContainerNotRunningError: If container is not running
        """
        stored = self._get_container(container_name)
        runtime, container_id = self._get_runtime_and_id(stored)

        executor = UpdateExecutor(runtime)
        check = executor.check_updates(
            container_id,
            container_name,
            stored.config.distro,
        )

        # Update last check time in history
        history = self._update_store.get_history(container_name)
        history.last_check = datetime.now()
        self._update_store.save_history(history)

        return check

    def apply(
        self,
        container_name: str,
        force: bool = False,
        auto_restart: bool | None = None,
    ) -> UpdateResult:
        """Apply updates to a container.

        Args:
            container_name: Name of the container
            force: Apply even if no updates available
            auto_restart: Override policy restart setting

        Returns:
            UpdateResult with operation outcome

        Raises:
            ContainerNotFoundError: If container doesn't exist
            ContainerNotRunningError: If container is not running
        """
        stored = self._get_container(container_name)
        runtime, container_id = self._get_runtime_and_id(stored)

        # Get or create policy
        policy = self._update_store.get_policy(container_name)
        if policy is None:
            policy = UpdatePolicy(container_name=container_name)

        executor = UpdateExecutor(runtime)

        # Create callback functions
        def snapshot_callback(name: str) -> None:
            try:
                runtime.snapshot_create(container_id, name)
            except Exception as e:
                raise SnapshotError("create", str(e))

        def health_check_callback(cid: str) -> bool:
            """Simple health check - verify container is running."""
            try:
                info = runtime.inspect(cid)
                return info.state.lower() == "running"
            except Exception:
                return False

        def rollback_callback(snapshot_name: str) -> None:
            try:
                runtime.snapshot_restore(container_id, snapshot_name)
            except Exception as e:
                raise SnapshotError("restore", str(e))

        # Apply updates
        result = executor.apply_updates(
            container_id,
            stored,
            policy,
            snapshot_callback=snapshot_callback if policy.pre_update_snapshot else None,
            health_check_callback=(
                health_check_callback if policy.health_check_after else None
            ),
            rollback_callback=(
                rollback_callback
                if policy.rollback_strategy == RollbackStrategy.AUTO
                else None
            ),
        )

        # Save result to history
        self._update_store.append_result(result)

        # Handle restart if needed
        restart = auto_restart if auto_restart is not None else policy.auto_restart
        if restart and result.status == UpdateStatus.COMPLETED:
            try:
                runtime.stop(container_id)
                runtime.start(container_id)
                logger.info(f"Restarted container {container_name} after update")
            except Exception as e:
                logger.warning(f"Failed to restart container: {e}")

        return result

    # -------------------------------------------------------------------------
    # Policy Management
    # -------------------------------------------------------------------------

    def get_policy(self, container_name: str) -> UpdatePolicy | None:
        """Get update policy for a container.

        Args:
            container_name: Container name

        Returns:
            UpdatePolicy or None if not set
        """
        return self._update_store.get_policy(container_name)

    def set_policy(self, policy: UpdatePolicy) -> None:
        """Set update policy for a container.

        Args:
            policy: Update policy to save
        """
        # Verify container exists
        self._get_container(policy.container_name)
        self._update_store.save_policy(policy)

    def delete_policy(self, container_name: str) -> bool:
        """Delete update policy for a container.

        Args:
            container_name: Container name

        Returns:
            True if policy was deleted
        """
        return self._update_store.delete_policy(container_name)

    def set_schedule(
        self,
        container_name: str,
        schedule_type: UpdateScheduleType,
        time_of_day: str = "03:00",
        day_of_week: int = 0,
        day_of_month: int = 1,
    ) -> UpdatePolicy:
        """Set or update the update schedule for a container.

        Args:
            container_name: Container name
            schedule_type: Schedule type (daily, weekly, monthly, manual)
            time_of_day: Time of day for updates (HH:MM)
            day_of_week: Day of week for weekly updates (0=Monday)
            day_of_month: Day of month for monthly updates

        Returns:
            Updated UpdatePolicy
        """
        # Verify container exists
        self._get_container(container_name)

        # Get existing policy or create new
        policy = self._update_store.get_policy(container_name)

        schedule = UpdateSchedule(
            schedule_type=schedule_type,
            time_of_day=time_of_day,
            day_of_week=day_of_week,
            day_of_month=day_of_month,
            enabled=schedule_type != UpdateScheduleType.MANUAL,
        )

        if policy:
            # Create new policy with updated schedule (models are frozen)
            policy = UpdatePolicy(
                container_name=container_name,
                schedule=schedule,
                update_packages=policy.update_packages,
                update_base_image=policy.update_base_image,
                rollback_strategy=policy.rollback_strategy,
                pre_update_snapshot=policy.pre_update_snapshot,
                snapshot_retention=policy.snapshot_retention,
                health_check_after=policy.health_check_after,
                health_check_timeout=policy.health_check_timeout,
                auto_restart=policy.auto_restart,
                packages_to_skip=policy.packages_to_skip,
            )
        else:
            policy = UpdatePolicy(container_name=container_name, schedule=schedule)

        self._update_store.save_policy(policy)
        return policy

    def enable_schedule(self, container_name: str) -> None:
        """Enable scheduled updates for a container.

        Args:
            container_name: Container name

        Raises:
            PolicyNotFoundError: If no policy exists
        """
        policy = self._update_store.get_policy(container_name)
        if policy is None:
            raise PolicyNotFoundError(container_name)

        # Create new schedule with enabled=True
        new_schedule = UpdateSchedule(
            schedule_type=policy.schedule.schedule_type,
            time_of_day=policy.schedule.time_of_day,
            day_of_week=policy.schedule.day_of_week,
            day_of_month=policy.schedule.day_of_month,
            enabled=True,
        )

        new_policy = UpdatePolicy(
            container_name=container_name,
            schedule=new_schedule,
            update_packages=policy.update_packages,
            update_base_image=policy.update_base_image,
            rollback_strategy=policy.rollback_strategy,
            pre_update_snapshot=policy.pre_update_snapshot,
            snapshot_retention=policy.snapshot_retention,
            health_check_after=policy.health_check_after,
            health_check_timeout=policy.health_check_timeout,
            auto_restart=policy.auto_restart,
            packages_to_skip=policy.packages_to_skip,
        )
        self._update_store.save_policy(new_policy)

    def disable_schedule(self, container_name: str) -> None:
        """Disable scheduled updates for a container.

        Args:
            container_name: Container name

        Raises:
            PolicyNotFoundError: If no policy exists
        """
        policy = self._update_store.get_policy(container_name)
        if policy is None:
            raise PolicyNotFoundError(container_name)

        # Create new schedule with enabled=False
        new_schedule = UpdateSchedule(
            schedule_type=policy.schedule.schedule_type,
            time_of_day=policy.schedule.time_of_day,
            day_of_week=policy.schedule.day_of_week,
            day_of_month=policy.schedule.day_of_month,
            enabled=False,
        )

        new_policy = UpdatePolicy(
            container_name=container_name,
            schedule=new_schedule,
            update_packages=policy.update_packages,
            update_base_image=policy.update_base_image,
            rollback_strategy=policy.rollback_strategy,
            pre_update_snapshot=policy.pre_update_snapshot,
            snapshot_retention=policy.snapshot_retention,
            health_check_after=policy.health_check_after,
            health_check_timeout=policy.health_check_timeout,
            auto_restart=policy.auto_restart,
            packages_to_skip=policy.packages_to_skip,
        )
        self._update_store.save_policy(new_policy)

    # -------------------------------------------------------------------------
    # History
    # -------------------------------------------------------------------------

    def history(self, container_name: str, limit: int | None = None) -> UpdateHistory:
        """Get update history for a container.

        Args:
            container_name: Container name
            limit: Maximum number of results to return

        Returns:
            UpdateHistory with results
        """
        history = self._update_store.get_history(container_name)
        if limit and len(history.results) > limit:
            # Return a copy with limited results
            return UpdateHistory(
                container_name=history.container_name,
                results=history.results[-limit:],
                last_update=history.last_update,
                last_check=history.last_check,
                total_updates=history.total_updates,
                successful_updates=history.successful_updates,
                failed_updates=history.failed_updates,
                rollbacks=history.rollbacks,
            )
        return history

    def clear_history(self, container_name: str) -> bool:
        """Clear update history for a container.

        Args:
            container_name: Container name

        Returns:
            True if history was deleted
        """
        return self._update_store.delete_history(container_name)

    # -------------------------------------------------------------------------
    # Listing
    # -------------------------------------------------------------------------

    def list_policies(self) -> list[UpdatePolicy]:
        """List all update policies.

        Returns:
            List of all update policies
        """
        return self._update_store.list_policies()

    def list_scheduled(self) -> list[tuple[str, UpdatePolicy]]:
        """List all containers with scheduled updates.

        Returns:
            List of (container_name, policy) tuples for enabled schedules
        """
        policies = self._update_store.list_policies()
        return [
            (p.container_name, p)
            for p in policies
            if p.schedule.enabled and p.schedule.schedule_type != UpdateScheduleType.MANUAL
        ]

    # -------------------------------------------------------------------------
    # Scheduler Management
    # -------------------------------------------------------------------------

    def start_scheduler(self) -> None:
        """Start the background update scheduler."""
        if self._scheduler is None:
            self._scheduler = get_update_scheduler(
                self._update_store,
                on_update=self.apply,
            )
        self._scheduler.start()

    def stop_scheduler(self) -> None:
        """Stop the background update scheduler."""
        if self._scheduler:
            self._scheduler.stop()

    def get_next_update(self, container_name: str) -> datetime | None:
        """Get next scheduled update time for a container.

        Args:
            container_name: Container name

        Returns:
            Next scheduled time or None
        """
        if self._scheduler:
            return self._scheduler.get_next_update(container_name)
        return None


# Global manager instance
_manager: UpdateManager | None = None


def get_update_manager() -> UpdateManager:
    """Get the global update manager instance.

    Returns:
        Global UpdateManager instance
    """
    global _manager
    if _manager is None:
        _manager = UpdateManager()
    return _manager
