"""Tests for container backup management."""

import tempfile
import threading
import time
from datetime import datetime, timedelta
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from gaol.backup.exceptions import (
    BackupError,
    BackupExecutionError,
    BackupNotFoundError,
    ContainerNotFoundError,
    IncrementalNotAvailableError,
    PolicyNotFoundError,
    RestoreError,
    RetentionError,
    SchedulerError,
    StorageError,
    StorageNotConfiguredError,
    VerificationError,
)
from gaol.backup.models import (
    BackupHistory,
    BackupMetadata,
    BackupPolicy,
    BackupResult,
    BackupSchedule,
    BackupScheduleType,
    BackupStatus,
    BackupType,
    GlobalBackupConfig,
    RestoreResult,
    RetentionPolicy,
    SchedulerState,
    StorageBackendType,
    StorageConfig,
)
from gaol.backup.retention import RetentionEnforcer
from gaol.backup.scheduler import BackupScheduler
from gaol.backup.storage import LocalStorageBackend, get_storage_backend
from gaol.backup.store import BackupStore


class TestBackupModels:
    """Tests for backup Pydantic models."""

    def test_backup_schedule_defaults(self) -> None:
        """Test BackupSchedule default values."""
        schedule = BackupSchedule()
        assert schedule.schedule_type == BackupScheduleType.MANUAL
        assert schedule.time_of_day == "02:00"
        assert schedule.day_of_week == 0
        assert schedule.day_of_month == 1
        assert schedule.enabled is True

    def test_backup_schedule_custom(self) -> None:
        """Test BackupSchedule with custom values."""
        schedule = BackupSchedule(
            schedule_type=BackupScheduleType.WEEKLY,
            time_of_day="04:30",
            day_of_week=5,
            enabled=False,
        )
        assert schedule.schedule_type == BackupScheduleType.WEEKLY
        assert schedule.time_of_day == "04:30"
        assert schedule.day_of_week == 5
        assert schedule.enabled is False

    def test_backup_schedule_time_validation(self) -> None:
        """Test time format validation."""
        # Valid times
        BackupSchedule(time_of_day="00:00")
        BackupSchedule(time_of_day="23:59")
        BackupSchedule(time_of_day="12:30")

        # Invalid times
        with pytest.raises(ValueError):
            BackupSchedule(time_of_day="25:00")
        with pytest.raises(ValueError):
            BackupSchedule(time_of_day="12:60")

    def test_retention_policy_defaults(self) -> None:
        """Test RetentionPolicy default values."""
        policy = RetentionPolicy()
        assert policy.keep_last == 7
        assert policy.keep_daily == 7
        assert policy.keep_weekly == 4
        assert policy.keep_monthly == 6
        assert policy.max_age_days == 90
        assert policy.max_total_size_mb is None

    def test_retention_policy_custom(self) -> None:
        """Test RetentionPolicy with custom values."""
        policy = RetentionPolicy(
            keep_last=10,
            keep_daily=14,
            keep_weekly=8,
            keep_monthly=12,
            max_age_days=180,
            max_total_size_mb=10240,
        )
        assert policy.keep_last == 10
        assert policy.keep_daily == 14
        assert policy.max_total_size_mb == 10240

    def test_storage_config_local(self) -> None:
        """Test StorageConfig for local storage."""
        config = StorageConfig(
            backend=StorageBackendType.LOCAL,
            path="/backups",
        )
        assert config.backend == StorageBackendType.LOCAL
        assert config.path == "/backups"
        assert config.bucket is None

    def test_storage_config_s3(self) -> None:
        """Test StorageConfig for S3 storage."""
        config = StorageConfig(
            backend=StorageBackendType.S3,
            bucket="my-backups",
            region="us-east-1",
            prefix="containers/",
        )
        assert config.backend == StorageBackendType.S3
        assert config.bucket == "my-backups"
        assert config.region == "us-east-1"
        assert config.prefix == "containers/"

    def test_backup_policy_defaults(self) -> None:
        """Test BackupPolicy default values."""
        policy = BackupPolicy(container_name="test")
        assert policy.container_name == "test"
        assert policy.schedule.schedule_type == BackupScheduleType.MANUAL
        assert policy.retention.keep_last == 7
        assert policy.backup_type == BackupType.FULL
        assert policy.prefer_incremental is True
        assert policy.compression == "gzip"
        assert policy.verify_after_backup is True
        assert policy.notify_on_failure is True

    def test_backup_policy_custom(self) -> None:
        """Test BackupPolicy with custom values."""
        schedule = BackupSchedule(
            schedule_type=BackupScheduleType.DAILY,
            time_of_day="03:00",
        )
        retention = RetentionPolicy(keep_last=14)
        policy = BackupPolicy(
            container_name="mycontainer",
            schedule=schedule,
            retention=retention,
            backup_type=BackupType.INCREMENTAL,
            compression="xz",
            pre_backup_commands=["sync"],
            post_backup_commands=["echo done"],
        )
        assert policy.container_name == "mycontainer"
        assert policy.schedule.schedule_type == BackupScheduleType.DAILY
        assert policy.retention.keep_last == 14
        assert policy.backup_type == BackupType.INCREMENTAL
        assert policy.compression == "xz"
        assert policy.pre_backup_commands == ["sync"]

    def test_backup_metadata(self) -> None:
        """Test BackupMetadata model."""
        now = datetime.now()
        meta = BackupMetadata(
            backup_id="abc-123",
            container_name="test",
            backup_name="daily-backup",
            backup_type=BackupType.FULL,
            created_at=now,
            size_bytes=1024 * 1024,
            storage_backend=StorageBackendType.LOCAL,
            storage_path="/backups/test/daily-backup.tar.gz",
            checksum="sha256:abc123",
            verified=False,
        )
        assert meta.backup_id == "abc-123"
        assert meta.backup_name == "daily-backup"
        assert meta.backup_type == BackupType.FULL
        assert meta.size_bytes == 1024 * 1024
        assert meta.verified is False

    def test_backup_result_defaults(self) -> None:
        """Test BackupResult default values."""
        result = BackupResult(
            container_name="test",
            backup_name="test-backup",
            backup_id="abc-123",
            backup_type=BackupType.FULL,
        )
        assert result.container_name == "test"
        assert result.status == BackupStatus.PENDING
        assert result.size_bytes == 0
        assert result.error_message is None

    def test_backup_result_success(self) -> None:
        """Test BackupResult success property."""
        result = BackupResult(
            container_name="test",
            backup_name="test-backup",
            backup_id="abc-123",
            backup_type=BackupType.FULL,
            status=BackupStatus.COMPLETED,
        )
        assert result.success is True

        result2 = BackupResult(
            container_name="test",
            backup_name="test-backup",
            backup_id="abc-123",
            backup_type=BackupType.FULL,
            status=BackupStatus.FAILED,
        )
        assert result2.success is False

    def test_backup_result_duration(self) -> None:
        """Test BackupResult duration calculation."""
        start = datetime.now()
        end = start + timedelta(seconds=60)
        result = BackupResult(
            container_name="test",
            backup_name="test-backup",
            backup_id="abc-123",
            backup_type=BackupType.FULL,
            started_at=start,
            completed_at=end,
        )
        assert result.duration_seconds == pytest.approx(60.0, abs=0.1)

    def test_restore_result(self) -> None:
        """Test RestoreResult model."""
        result = RestoreResult(
            container_name="test",
            backup_name="daily-backup",
            backup_id="abc-123",
            status=BackupStatus.COMPLETED,
            pre_restore_snapshot="pre-restore-20240113",
        )
        assert result.container_name == "test"
        assert result.backup_name == "daily-backup"
        assert result.status == BackupStatus.COMPLETED
        assert result.pre_restore_snapshot == "pre-restore-20240113"

    def test_backup_history(self) -> None:
        """Test BackupHistory model."""
        history = BackupHistory(container_name="test")
        assert history.total_backups == 0
        assert history.successful_backups == 0
        assert history.failed_backups == 0

    def test_backup_history_add_backup(self) -> None:
        """Test BackupHistory adding backups."""
        history = BackupHistory(container_name="test")

        # Successful backup has completed_at set
        meta1 = BackupMetadata(
            backup_id="abc-123",
            container_name="test",
            backup_name="backup-1",
            backup_type=BackupType.FULL,
            created_at=datetime.now(),
            completed_at=datetime.now(),  # This makes it successful
            size_bytes=1024,
            storage_backend=StorageBackendType.LOCAL,
            storage_path="/test",
        )
        history.add_backup(meta1)
        assert history.total_backups == 1
        assert history.successful_backups == 1
        assert history.last_backup is not None

        # Failed backup has no completed_at
        meta2 = BackupMetadata(
            backup_id="def-456",
            container_name="test",
            backup_name="backup-2",
            backup_type=BackupType.FULL,
            created_at=datetime.now(),
            size_bytes=2048,
            storage_backend=StorageBackendType.LOCAL,
            storage_path="/test2",
        )
        history.add_backup(meta2)
        assert history.total_backups == 2
        assert history.failed_backups == 1

    def test_backup_history_remove_backup(self) -> None:
        """Test BackupHistory removing backups."""
        history = BackupHistory(container_name="test")

        meta = BackupMetadata(
            backup_id="abc-123",
            container_name="test",
            backup_name="backup-1",
            backup_type=BackupType.FULL,
            created_at=datetime.now(),
            completed_at=datetime.now(),
            size_bytes=1024,
            storage_backend=StorageBackendType.LOCAL,
            storage_path="/test",
        )
        history.add_backup(meta)
        assert history.total_backups == 1
        assert len(history.backups) == 1

        # remove_backup removes from list but doesn't decrement counters
        result = history.remove_backup("abc-123")
        assert result is True
        assert len(history.backups) == 0

    def test_global_backup_config(self) -> None:
        """Test GlobalBackupConfig model."""
        config = GlobalBackupConfig()
        assert config.default_storage.backend == StorageBackendType.LOCAL
        assert config.scheduler_enabled is True
        assert config.parallel_backups == 1

    def test_scheduler_state(self) -> None:
        """Test SchedulerState model."""
        state = SchedulerState()
        assert state.running is False
        assert state.started_at is None
        assert state.next_scheduled == {}


class TestBackupExceptions:
    """Tests for backup exceptions."""

    def test_backup_error_base(self) -> None:
        """Test base BackupError."""
        err = BackupError("test error")
        assert err.message == "test error"
        assert str(err) == "test error"

    def test_container_not_found_error(self) -> None:
        """Test ContainerNotFoundError."""
        err = ContainerNotFoundError("mycontainer")
        assert err.container_name == "mycontainer"
        assert "mycontainer" in str(err)

    def test_backup_not_found_error(self) -> None:
        """Test BackupNotFoundError."""
        err = BackupNotFoundError("mycontainer", "daily-backup")
        assert err.container_name == "mycontainer"
        assert err.backup_name == "daily-backup"
        assert "daily-backup" in str(err)

    def test_policy_not_found_error(self) -> None:
        """Test PolicyNotFoundError."""
        err = PolicyNotFoundError("mycontainer")
        assert err.container_name == "mycontainer"
        assert "mycontainer" in str(err)

    def test_storage_error(self) -> None:
        """Test StorageError."""
        err = StorageError("local", "upload", "disk full")
        assert err.backend == "local"
        assert err.operation == "upload"
        assert err.reason == "disk full"
        assert "upload" in str(err)

    def test_storage_not_configured_error(self) -> None:
        """Test StorageNotConfiguredError."""
        err = StorageNotConfiguredError("S3")
        assert err.backend == "S3"
        assert "S3" in str(err)

    def test_backup_execution_error(self) -> None:
        """Test BackupExecutionError."""
        err = BackupExecutionError("export failed", output="error log")
        assert err.message == "export failed"
        assert err.output == "error log"

    def test_restore_error(self) -> None:
        """Test RestoreError."""
        err = RestoreError("mycontainer", "daily-backup", "checksum mismatch")
        assert err.container_name == "mycontainer"
        assert err.backup_name == "daily-backup"
        assert err.reason == "checksum mismatch"

    def test_retention_error(self) -> None:
        """Test RetentionError."""
        err = RetentionError("cannot delete last backup")
        assert err.message == "cannot delete last backup"
        assert "cannot delete last backup" in str(err)

    def test_verification_error(self) -> None:
        """Test VerificationError."""
        err = VerificationError("daily-backup", "checksum mismatch")
        assert err.backup_name == "daily-backup"
        assert err.reason == "checksum mismatch"

    def test_incremental_not_available_error(self) -> None:
        """Test IncrementalNotAvailableError."""
        err = IncrementalNotAvailableError("mycontainer", "no CoW support")
        assert err.container_name == "mycontainer"
        assert err.reason == "no CoW support"

    def test_scheduler_error(self) -> None:
        """Test SchedulerError."""
        err = SchedulerError("scheduler already running")
        assert err.message == "scheduler already running"
        assert "scheduler already running" in str(err)


class TestBackupStore:
    """Tests for BackupStore."""

    def test_store_init(self) -> None:
        """Test BackupStore initialization."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))
            assert store._store_dir == Path(tmpdir)

    def test_store_save_load_policy(self) -> None:
        """Test saving and loading backup policy."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            policy = BackupPolicy(
                container_name="test",
                backup_type=BackupType.FULL,
            )
            store.save_policy(policy)

            loaded = store.get_policy("test")
            assert loaded is not None
            assert loaded.container_name == "test"
            assert loaded.backup_type == BackupType.FULL

    def test_store_delete_policy(self) -> None:
        """Test deleting backup policy."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            policy = BackupPolicy(container_name="test")
            store.save_policy(policy)

            assert store.delete_policy("test") is True
            assert store.get_policy("test") is None
            assert store.delete_policy("test") is False

    def test_store_list_policies(self) -> None:
        """Test listing backup policies."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            store.save_policy(BackupPolicy(container_name="test1"))
            store.save_policy(BackupPolicy(container_name="test2"))

            policies = store.list_policies()
            assert len(policies) == 2
            names = {p.container_name for p in policies}
            assert names == {"test1", "test2"}

    def test_store_save_load_history(self) -> None:
        """Test saving and loading backup history."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            history = BackupHistory(container_name="test")
            meta = BackupMetadata(
                backup_id="abc-123",
                container_name="test",
                backup_name="backup-1",
                backup_type=BackupType.FULL,
                created_at=datetime.now(),
                size_bytes=1024,
                storage_backend=StorageBackendType.LOCAL,
                storage_path="/test",
            )
            history.add_backup(meta)
            store.save_history(history)

            loaded = store.get_history("test")
            assert loaded.container_name == "test"
            assert loaded.total_backups == 1

    def test_store_save_load_metadata(self) -> None:
        """Test saving and loading backup metadata."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            meta = BackupMetadata(
                backup_id="abc-123",
                container_name="test",
                backup_name="daily-backup",
                backup_type=BackupType.FULL,
                created_at=datetime.now(),
                size_bytes=1024,
                storage_backend=StorageBackendType.LOCAL,
                storage_path="/test",
            )
            store.save_backup_metadata(meta)

            loaded = store.get_backup_metadata("test", "abc-123")
            assert loaded is not None
            assert loaded.backup_id == "abc-123"
            assert loaded.backup_name == "daily-backup"

    def test_store_get_backup_by_name(self) -> None:
        """Test getting backup metadata by name."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            meta = BackupMetadata(
                backup_id="abc-123",
                container_name="test",
                backup_name="daily-backup",
                backup_type=BackupType.FULL,
                created_at=datetime.now(),
                size_bytes=1024,
                storage_backend=StorageBackendType.LOCAL,
                storage_path="/test",
            )
            store.save_backup_metadata(meta)

            loaded = store.get_backup_by_name("test", "daily-backup")
            assert loaded is not None
            assert loaded.backup_id == "abc-123"

            # Test non-existent backup
            assert store.get_backup_by_name("test", "nonexistent") is None

    def test_store_list_backups(self) -> None:
        """Test listing backups for a container."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            for i in range(3):
                meta = BackupMetadata(
                    backup_id=f"id-{i}",
                    container_name="test",
                    backup_name=f"backup-{i}",
                    backup_type=BackupType.FULL,
                    created_at=datetime.now() + timedelta(hours=i),
                    size_bytes=1024,
                    storage_backend=StorageBackendType.LOCAL,
                    storage_path=f"/test/{i}",
                )
                store.save_backup_metadata(meta)

            backups = store.list_backups("test")
            assert len(backups) == 3
            # Should be sorted by creation date (newest first)
            assert backups[0].backup_name == "backup-2"
            assert backups[2].backup_name == "backup-0"

    def test_store_delete_backup_metadata(self) -> None:
        """Test deleting backup metadata."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            meta = BackupMetadata(
                backup_id="abc-123",
                container_name="test",
                backup_name="daily-backup",
                backup_type=BackupType.FULL,
                created_at=datetime.now(),
                size_bytes=1024,
                storage_backend=StorageBackendType.LOCAL,
                storage_path="/test",
            )
            store.save_backup_metadata(meta)

            assert store.delete_backup_metadata("test", "abc-123") is True
            assert store.get_backup_metadata("test", "abc-123") is None
            assert store.delete_backup_metadata("test", "abc-123") is False

    def test_store_global_config(self) -> None:
        """Test global backup configuration."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))

            # Get default config
            config = store.get_global_config()
            assert config.default_storage.backend == StorageBackendType.LOCAL

            # Save custom config
            config.default_storage = StorageConfig(
                backend=StorageBackendType.S3,
                bucket="my-bucket",
            )
            store.save_global_config(config)

            # Reload and verify
            loaded = store.get_global_config()
            assert loaded.default_storage.backend == StorageBackendType.S3
            assert loaded.default_storage.bucket == "my-bucket"


class TestLocalStorageBackend:
    """Tests for LocalStorageBackend."""

    def test_local_storage_upload_download(self) -> None:
        """Test uploading and downloading files."""
        with tempfile.TemporaryDirectory() as tmpdir:
            storage = LocalStorageBackend(Path(tmpdir))

            # Create test file
            test_content = b"test backup content"
            source = Path(tmpdir) / "source.tar.gz"
            source.write_bytes(test_content)

            # Upload
            remote_path = storage.upload(source, "container/backup.tar.gz")
            assert storage.exists(remote_path)

            # Download
            dest = Path(tmpdir) / "downloaded.tar.gz"
            storage.download(remote_path, dest)
            assert dest.read_bytes() == test_content

    def test_local_storage_delete(self) -> None:
        """Test deleting files."""
        with tempfile.TemporaryDirectory() as tmpdir:
            storage = LocalStorageBackend(Path(tmpdir))

            # Create and upload test file
            source = Path(tmpdir) / "source.tar.gz"
            source.write_bytes(b"test content")
            remote_path = storage.upload(source, "container/backup.tar.gz")

            assert storage.exists(remote_path)
            storage.delete(remote_path)
            assert not storage.exists(remote_path)

    def test_local_storage_list(self) -> None:
        """Test listing files."""
        with tempfile.TemporaryDirectory() as tmpdir:
            storage = LocalStorageBackend(Path(tmpdir))

            # Create test files
            for i in range(3):
                source = Path(tmpdir) / f"source{i}.tar.gz"
                source.write_bytes(f"content {i}".encode())
                storage.upload(source, f"container/backup{i}.tar.gz")

            files = storage.list("container/")
            assert len(files) == 3

    def test_local_storage_get_size(self) -> None:
        """Test getting file size."""
        with tempfile.TemporaryDirectory() as tmpdir:
            storage = LocalStorageBackend(Path(tmpdir))

            content = b"test content 1234567890"
            source = Path(tmpdir) / "source.tar.gz"
            source.write_bytes(content)
            remote_path = storage.upload(source, "container/backup.tar.gz")

            size = storage.get_size(remote_path)
            assert size == len(content)


class TestRetentionEnforcer:
    """Tests for RetentionEnforcer."""

    def test_retention_keep_last(self) -> None:
        """Test keep_last retention rule."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir) / "store")
            storage = LocalStorageBackend(Path(tmpdir) / "storage")
            enforcer = RetentionEnforcer(store, storage)

            # Create 10 backups
            for i in range(10):
                meta = BackupMetadata(
                    backup_id=f"id-{i}",
                    container_name="test",
                    backup_name=f"backup-{i}",
                    backup_type=BackupType.FULL,
                    created_at=datetime.now() - timedelta(days=9 - i),
                    size_bytes=1024,
                    storage_backend=StorageBackendType.LOCAL,
                    storage_path=f"test/backup-{i}.tar.gz",
                )
                store.save_backup_metadata(meta)

            # Apply retention: keep last 5
            policy = RetentionPolicy(
                keep_last=5,
                keep_daily=None,
                keep_weekly=None,
                keep_monthly=None,
                max_age_days=None,
            )

            deleted = enforcer.apply_retention("test", policy, dry_run=True)
            assert len(deleted) == 5  # Should delete 5 oldest

    def test_retention_max_age(self) -> None:
        """Test max_age_days retention rule."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir) / "store")
            storage = LocalStorageBackend(Path(tmpdir) / "storage")
            enforcer = RetentionEnforcer(store, storage)

            # Create backups with different ages
            for i in range(5):
                meta = BackupMetadata(
                    backup_id=f"id-{i}",
                    container_name="test",
                    backup_name=f"backup-{i}",
                    backup_type=BackupType.FULL,
                    created_at=datetime.now() - timedelta(days=i * 30),
                    size_bytes=1024,
                    storage_backend=StorageBackendType.LOCAL,
                    storage_path=f"test/backup-{i}.tar.gz",
                )
                store.save_backup_metadata(meta)

            # Apply retention: max 60 days
            policy = RetentionPolicy(
                keep_last=None,
                keep_daily=None,
                keep_weekly=None,
                keep_monthly=None,
                max_age_days=60,
            )

            deleted = enforcer.apply_retention("test", policy, dry_run=True)
            # Backups older than 60 days should be deleted (days 90, 120)
            assert len(deleted) >= 2

    def test_retention_preview(self) -> None:
        """Test retention policy preview."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir) / "store")
            storage = LocalStorageBackend(Path(tmpdir) / "storage")
            enforcer = RetentionEnforcer(store, storage)

            # Create backups
            for i in range(5):
                meta = BackupMetadata(
                    backup_id=f"id-{i}",
                    container_name="test",
                    backup_name=f"backup-{i}",
                    backup_type=BackupType.FULL,
                    created_at=datetime.now() - timedelta(days=i),
                    size_bytes=1024,
                    storage_backend=StorageBackendType.LOCAL,
                    storage_path=f"test/backup-{i}.tar.gz",
                )
                store.save_backup_metadata(meta)

            # Disable all other retention rules, only keep_last=3
            policy = RetentionPolicy(
                keep_last=3,
                keep_daily=None,
                keep_weekly=None,
                keep_monthly=None,
                max_age_days=None,
            )
            to_keep, to_delete = enforcer.get_retention_preview("test", policy)

            assert len(to_keep) == 3
            assert len(to_delete) == 2


class TestBackupScheduler:
    """Tests for BackupScheduler."""

    def test_scheduler_start_stop(self) -> None:
        """Test starting and stopping scheduler."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))
            scheduler = BackupScheduler(store)

            assert not scheduler.is_running

            scheduler.start()
            assert scheduler.is_running
            assert scheduler.state.running is True

            scheduler.stop()
            assert not scheduler.is_running
            assert scheduler.state.running is False

    def test_scheduler_calculate_next_time_daily(self) -> None:
        """Test calculating next daily backup time."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))
            scheduler = BackupScheduler(store)

            schedule = BackupSchedule(
                schedule_type=BackupScheduleType.DAILY,
                time_of_day="03:00",
            )

            next_time = scheduler._calculate_next_scheduled_time(schedule)
            assert next_time is not None
            assert next_time.hour == 3
            assert next_time.minute == 0

    def test_scheduler_calculate_next_time_weekly(self) -> None:
        """Test calculating next weekly backup time."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))
            scheduler = BackupScheduler(store)

            schedule = BackupSchedule(
                schedule_type=BackupScheduleType.WEEKLY,
                time_of_day="02:00",
                day_of_week=6,  # Sunday
            )

            next_time = scheduler._calculate_next_scheduled_time(schedule)
            assert next_time is not None
            assert next_time.weekday() == 6
            assert next_time.hour == 2

    def test_scheduler_trigger_backup(self) -> None:
        """Test manually triggering a backup."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))
            callback_called = []

            def mock_backup(container_name: str) -> BackupResult:
                callback_called.append(container_name)
                return BackupResult(
                    container_name=container_name,
                    backup_name="triggered-backup",
                    backup_id="test-id",
                    backup_type=BackupType.FULL,
                    status=BackupStatus.COMPLETED,
                )

            scheduler = BackupScheduler(store, on_backup=mock_backup)

            result = scheduler.trigger_backup("test")
            assert result is not None
            assert result.status == BackupStatus.COMPLETED
            assert "test" in callback_called

    def test_scheduler_remove_container(self) -> None:
        """Test removing container from scheduler state."""
        with tempfile.TemporaryDirectory() as tmpdir:
            store = BackupStore(Path(tmpdir))
            scheduler = BackupScheduler(store)

            # Manually add to state
            scheduler._state.next_scheduled["test"] = datetime.now()
            assert "test" in scheduler.state.next_scheduled

            scheduler.remove_container("test")
            assert "test" not in scheduler.state.next_scheduled


class TestStorageBackendFactory:
    """Tests for storage backend factory."""

    def test_get_storage_backend_default(self) -> None:
        """Test getting default storage backend."""
        backend = get_storage_backend(None)
        assert isinstance(backend, LocalStorageBackend)

    def test_get_storage_backend_local(self) -> None:
        """Test getting local storage backend."""
        config = StorageConfig(
            backend=StorageBackendType.LOCAL,
            path="/custom/path",
        )
        backend = get_storage_backend(config)
        assert isinstance(backend, LocalStorageBackend)

    def test_get_storage_backend_s3(self) -> None:
        """Test getting S3 storage backend configuration."""
        config = StorageConfig(
            backend=StorageBackendType.S3,
            bucket="test-bucket",
            region="us-east-1",
        )
        # This will create an S3 backend (may not connect without credentials)
        backend = get_storage_backend(config)
        assert backend.name == "s3"


class TestBackupTypes:
    """Tests for backup type enumerations."""

    def test_backup_schedule_type_values(self) -> None:
        """Test BackupScheduleType values."""
        assert BackupScheduleType.MANUAL.value == "manual"
        assert BackupScheduleType.DAILY.value == "daily"
        assert BackupScheduleType.WEEKLY.value == "weekly"
        assert BackupScheduleType.MONTHLY.value == "monthly"

    def test_backup_status_values(self) -> None:
        """Test BackupStatus values."""
        assert BackupStatus.PENDING.value == "pending"
        assert BackupStatus.PREPARING.value == "preparing"
        assert BackupStatus.SNAPSHOTTING.value == "snapshotting"
        assert BackupStatus.UPLOADING.value == "uploading"
        assert BackupStatus.VERIFYING.value == "verifying"
        assert BackupStatus.COMPLETED.value == "completed"
        assert BackupStatus.FAILED.value == "failed"

    def test_backup_type_values(self) -> None:
        """Test BackupType values."""
        assert BackupType.FULL.value == "full"
        assert BackupType.INCREMENTAL.value == "incremental"
        assert BackupType.CONFIG_ONLY.value == "config_only"

    def test_storage_backend_type_values(self) -> None:
        """Test StorageBackendType values."""
        assert StorageBackendType.LOCAL.value == "local"
        assert StorageBackendType.S3.value == "s3"
