"""Background scheduler for database backups.

Runs backups in a thread pool to avoid blocking the login flow.
"""

import atexit
import threading
from concurrent.futures import ThreadPoolExecutor
from typing import Optional

from loguru import logger

from .backup_service import BackupResult, BackupService


class BackupExecutor:
    """Singleton scheduler for running database backups in background threads.

    Uses ThreadPoolExecutor to run backups asynchronously without blocking
    the login flow.
    """

    _instance: Optional["BackupExecutor"] = None
    _lock = threading.Lock()

    def __new__(cls) -> "BackupExecutor":
        """Ensure singleton instance."""
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self) -> None:
        """Initialize the scheduler (only runs once due to singleton)."""
        if hasattr(self, "_initialized"):
            return

        # Thread pool for running backups
        # Max 2 workers to limit concurrent backup operations
        self._executor = ThreadPoolExecutor(
            max_workers=2,
            thread_name_prefix="backup_worker",
        )

        # Track pending backups to avoid duplicates
        self._pending_backups: set[str] = set()
        self._pending_lock = threading.Lock()

        self._initialized = True

        # Register atexit handler to ensure clean shutdown
        atexit.register(self.shutdown)
        logger.info("Backup executor initialized")

    def submit_backup(
        self,
        username: str,
        password: str,
        max_backups: int = 1,
        max_age_days: int = 7,
    ) -> bool:
        """Schedule a background backup for a user.

        This method returns immediately without waiting for the backup
        to complete.

        Args:
            username: User's username
            password: User's password (for encryption)
            max_backups: Maximum number of backups to keep
            max_age_days: Delete backups older than this many days

        Returns:
            True if backup was scheduled, False if already pending
        """
        with self._pending_lock:
            if username in self._pending_backups:
                logger.debug("Backup already pending for user, skipping")
                return False
            self._pending_backups.add(username)

        # Submit backup to thread pool
        future = self._executor.submit(
            self._run_backup,
            username,
            password,
            max_backups,
            max_age_days,
        )

        # Add callback to remove from pending set when done
        future.add_done_callback(lambda f: self._backup_completed(username, f))

        logger.debug("Background backup scheduled for user")
        return True

    def _run_backup(
        self,
        username: str,
        password: str,
        max_backups: int,
        max_age_days: int,
    ) -> BackupResult:
        """Run the actual backup operation.

        Args:
            username: User's username
            password: User's password
            max_backups: Maximum number of backups to keep
            max_age_days: Delete backups older than this many days

        Returns:
            BackupResult from the backup operation
        """
        try:
            service = BackupService(
                username=username,
                password=password,
                max_backups=max_backups,
                max_age_days=max_age_days,
            )
            result = service.create_backup()

            if result.success:
                logger.info(
                    f"Background backup completed: {result.backup_path.name if result.backup_path else 'unknown'}"
                )
            else:
                logger.warning(f"Background backup failed: {result.error}")

            return result

        except Exception as e:
            logger.exception("Background backup error")
            return BackupResult(success=False, error=str(e))

    def _backup_completed(self, username: str, future) -> None:
        """Callback when a backup completes.

        Args:
            username: User whose backup completed
            future: The completed future
        """
        with self._pending_lock:
            self._pending_backups.discard(username)

        # Log any exceptions that weren't caught
        try:
            future.result()
        except Exception:
            logger.exception("Unhandled backup exception")

    def shutdown(self, wait: bool = True) -> None:
        """Shutdown the scheduler.

        Args:
            wait: If True, wait for pending backups to complete
        """
        logger.info("Shutting down backup executor")
        self._executor.shutdown(wait=wait)

    def get_pending_count(self) -> int:
        """Get number of pending backups.

        Returns:
            Number of backups currently in progress
        """
        with self._pending_lock:
            return len(self._pending_backups)


def get_backup_executor() -> BackupExecutor:
    """Get the singleton backup scheduler instance.

    Returns:
        The BackupExecutor singleton (thread-safe via __new__)
    """
    return BackupExecutor()
