"""
Extended tests for news/subscription_manager/scheduler.py

Additional tests cover:
- _process_user_documents method
- _check_user_overdue_subscriptions method
- Concurrency and thread safety
- Error recovery scenarios
- Cache management edge cases
- Job scheduling edge cases
"""

from dataclasses import FrozenInstanceError
from unittest.mock import patch, MagicMock
from datetime import datetime, timedelta, UTC

import pytest
from apscheduler.jobstores.base import JobLookupError


class TestProcessUserDocuments:
    """Tests for BackgroundJobScheduler._process_user_documents method."""

    def test_returns_early_if_no_session(self):
        """Returns early when user has no session."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        # Reset singleton
        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions = {}  # No sessions

            # Should not raise
            result = scheduler._process_user_documents("user123")

            # Method should return None or handle gracefully
            assert result is None or result == 0

    def test_logs_processing_start(self):
        """Logs the entry banner when processing starts.

        The previous version of this test mocked the logger but never
        asserted on it (the trailing ``# Should have logged something``
        comment had no matching ``assert``), so it passed even if the
        log statement was removed. The bare ``except Exception: pass``
        also swallowed any real failure.
        """
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            # No user_sessions entry — _process_user_documents will hit
            # the "No session info" branch and return cleanly after
            # emitting the entry-banner log line.
            scheduler.user_sessions = {}

            with patch(
                "local_deep_research.scheduler.background.logger"
            ) as mock_logger:
                scheduler._process_user_documents("user123")

                mock_logger.info.assert_any_call(
                    "[DOC_SCHEDULER] Processing documents for user user123"
                )


class TestCheckUserOverdueSubscriptions:
    """Tests for BackgroundJobScheduler._check_user_overdue_subscriptions method."""

    def test_returns_early_when_credentials_missing(self):
        """When no credentials are cached, _check_user_overdue_subscriptions
        must return without raising and without touching the database.

        Replaces a prior version that tried to call the method with a
        user that lacked credentials and swallowed any resulting
        exception, asserting nothing.
        """
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions = {
                "user123": {
                    "session": MagicMock(),
                    "scheduled_jobs": set(),
                }
            }
            # No credentials stored for user123

            with patch(
                "local_deep_research.database.session_context.get_user_db_session"
            ) as mock_get_db:
                scheduler._check_user_overdue_subscriptions("user123")
                mock_get_db.assert_not_called()


class TestSchedulerConcurrency:
    """Tests for scheduler thread safety."""

    def test_user_sessions_lock_exists(self):
        """Scheduler has a lock for user_sessions."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert hasattr(scheduler, "_lock") or hasattr(scheduler, "lock")

    def test_settings_cache_lock_exists(self):
        """Settings cache has thread protection."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            # Scheduler should have cache protection
            assert hasattr(scheduler, "_settings_cache_lock") or hasattr(
                scheduler, "_lock"
            )


class TestErrorRecovery:
    """Tests for scheduler error recovery scenarios."""

    def test_unregister_swallows_job_lookup_error(self):
        """``unregister_user`` must call ``scheduler.remove_job`` for each
        scheduled job and swallow JobLookupError if the job is already
        gone — see the except handler at background.py:463-464.

        Replaces a prior version that just verified the mock raises
        when called directly (testing the mock, not the scheduler).
        """
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_apscheduler = MagicMock()
            mock_apscheduler.remove_job.side_effect = JobLookupError(
                "stale-job"
            )
            mock_bg.return_value = mock_apscheduler

            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions["user123"] = {
                "last_activity": MagicMock(),
                "scheduled_jobs": {"stale-job-1", "stale-job-2"},
            }
            scheduler._credential_store.store("user123", "pw")

            # Must not propagate JobLookupError out of unregister_user
            scheduler.unregister_user("user123")

            # Both stale jobs were attempted; both raised; both swallowed
            assert mock_apscheduler.remove_job.call_count == 2
            assert "user123" not in scheduler.user_sessions
            assert scheduler._credential_store.retrieve("user123") is None


class TestCacheManagement:
    """Tests for settings cache management."""

    def test_cache_has_ttl(self):
        """Settings cache has time-to-live."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            # Cache should have TTL functionality
            assert hasattr(
                scheduler, "_document_scheduler_settings_cache"
            ) or hasattr(scheduler, "_settings_cache")

    def test_invalidate_removes_entry(self):
        """invalidate_user_settings_cache removes user entry."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            # Add entry to cache
            if hasattr(scheduler, "_document_scheduler_settings_cache"):
                scheduler._document_scheduler_settings_cache["user123"] = {
                    "data": "test"
                }

                result = scheduler.invalidate_user_settings_cache("user123")

                assert (
                    result is True
                    or "user123"
                    not in scheduler._document_scheduler_settings_cache
                )


class TestJobScheduling:
    """Tests for job scheduling edge cases."""

    def test_job_id_format(self):
        """Job IDs follow expected format."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            BackgroundJobScheduler()

            # Job IDs should include user info
            # This tests the format convention

    def test_jitter_within_bounds(self):
        """Jitter is calculated within allowed bounds."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            BackgroundJobScheduler()

            # Jitter should be a positive value
            # Implementation may vary

    def test_interval_trigger_for_frequent_checks(self):
        """Uses IntervalTrigger for frequent checks (<=60 min)."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            BackgroundJobScheduler()

            # Should use interval trigger for 30-minute checks


class TestDocumentSchedulerSettings:
    """Tests for document scheduler settings."""

    def test_settings_dataclass(self):
        """Document scheduler settings is a dataclass or dict."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            BackgroundJobScheduler()

            # Settings should be retrievable
            # Default values should be used when not configured

    def test_default_enabled_false(self):
        """Document processing defaults to disabled."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            BackgroundJobScheduler()

            # Default should be disabled


class TestTriggerDocumentProcessing:
    """Tests for trigger_document_processing method."""

    def test_returns_false_for_unknown_user(self):
        """Returns False for unknown user."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions = {}

            result = scheduler.trigger_document_processing("unknown-user")

            assert result is False

    def test_returns_false_when_not_running(self):
        """Returns False when scheduler not running."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_scheduler.running = False
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler._scheduler = mock_scheduler
            scheduler.user_sessions = {"user123": {"session": MagicMock()}}

            result = scheduler.trigger_document_processing("user123")

            assert result is False


class TestGetDocumentSchedulerStatus:
    """Tests for get_document_scheduler_status method."""

    def test_returns_dict(self):
        """Returns a dictionary with status info."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            result = scheduler.get_document_scheduler_status("user123")

            assert isinstance(result, dict)

    def test_includes_enabled_key(self):
        """Status includes enabled key."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            result = scheduler.get_document_scheduler_status("user123")

            assert (
                "enabled" in result
                or "is_enabled" in result
                or len(result) >= 0
            )

    def test_handles_exception(self):
        """Handles exception gracefully."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            with patch.object(
                scheduler,
                "_get_document_scheduler_settings",
                side_effect=Exception("Error"),
            ):
                # Should not raise
                result = scheduler.get_document_scheduler_status("user123")

                assert isinstance(result, dict)


class TestCheckSubscription:
    """Tests for _check_subscription method."""

    def test_removes_job_if_no_session(self):
        """Removes job when user session is gone."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler._scheduler = mock_scheduler
            scheduler.user_sessions = {}  # No sessions

            # When the user is not in sessions, _check_subscription must
            # return cleanly without raising and without touching the DB.
            with patch(
                "local_deep_research.database.session_context.get_user_db_session"
            ) as mock_get_db:
                scheduler._check_subscription("user123", "sub-456")
                mock_get_db.assert_not_called()

    def test_skips_processing_when_user_not_in_sessions(self):
        """Skips processing when user is not in sessions."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions = {}  # No user

            with patch(
                "local_deep_research.database.session_context.get_user_db_session"
            ) as mock_get_db:
                scheduler._check_subscription("user123", "sub-456")
                mock_get_db.assert_not_called()


class TestSchedulerLifecycle:
    """Tests for scheduler lifecycle management."""

    def test_stop_clears_user_sessions(self):
        """stop() clears user sessions."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler.scheduler = mock_scheduler
            scheduler.is_running = True
            scheduler.user_sessions = {
                "user123": {
                    "session": MagicMock(),
                    "scheduled_jobs": {"job1", "job2"},
                }
            }

            scheduler.stop()

            # User sessions should be cleared
            assert len(scheduler.user_sessions) == 0

    def test_start_initializes_scheduler(self):
        """start() initializes the scheduler."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_scheduler.running = False
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler._scheduler = mock_scheduler
            scheduler.set_app(MagicMock())

            scheduler.start()

            mock_scheduler.start.assert_called()


class TestUpdateUserInfo:
    """Tests for update_user_info method."""

    def test_creates_new_session_entry(self):
        """Creates new session entry for new user."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions = {}
            scheduler.is_running = True

            with patch.object(scheduler, "_schedule_user_subscriptions"):
                with patch.object(scheduler, "_schedule_document_processing"):
                    scheduler.update_user_info("user123", "password")

            assert "user123" in scheduler.user_sessions

    def test_updates_last_activity_time(self):
        """Updates last activity time for user."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            old_time = datetime.now(UTC) - timedelta(hours=1)
            scheduler.user_sessions = {
                "user123": {
                    "scheduled_jobs": set(),
                    "last_activity": old_time,
                }
            }
            scheduler._credential_store.store("user123", "old")
            scheduler.is_running = True

            with patch.object(scheduler, "_schedule_user_subscriptions"):
                with patch.object(scheduler, "_schedule_document_processing"):
                    scheduler.update_user_info("user123", "new_password")

            # Last activity should be updated to a more recent time
            assert (
                scheduler.user_sessions["user123"]["last_activity"] > old_time
            )


class TestUnregisterUser:
    """Tests for unregister_user method."""

    def test_removes_user_from_sessions(self):
        """Removes user from user_sessions."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler._scheduler = mock_scheduler
            scheduler.user_sessions = {
                "user123": {"session": MagicMock(), "scheduled_jobs": set()}
            }

            scheduler.unregister_user("user123")

            assert "user123" not in scheduler.user_sessions

    def test_removes_scheduled_jobs(self):
        """Removes all scheduled jobs for user."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler._scheduler = mock_scheduler
            scheduler.user_sessions = {
                "user123": {
                    "session": MagicMock(),
                    "scheduled_jobs": {"job1", "job2", "job3"},
                }
            }

            scheduler.unregister_user("user123")

            # Should have tried to remove jobs
            assert mock_scheduler.remove_job.call_count >= 0

    def test_handles_nonexistent_user(self):
        """Handles unregistering nonexistent user gracefully."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions = {}

            # Should not raise
            scheduler.unregister_user("nonexistent")

    def test_invalidates_settings_cache(self):
        """Invalidates settings cache for user."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.user_sessions = {
                "user123": {"session": MagicMock(), "scheduled_jobs": set()}
            }

            with patch.object(
                scheduler, "invalidate_user_settings_cache"
            ) as mock_invalidate:
                scheduler.unregister_user("user123")

                mock_invalidate.assert_called_once_with("user123")


class TestInvalidateAllSettingsCache:
    """Tests for invalidate_all_settings_cache method."""

    def test_clears_all_entries(self):
        """Clears all cache entries."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            if hasattr(scheduler, "_document_scheduler_settings_cache"):
                scheduler._document_scheduler_settings_cache["user1"] = {}
                scheduler._document_scheduler_settings_cache["user2"] = {}

                result = scheduler.invalidate_all_settings_cache()

                assert result >= 0

    def test_returns_count(self):
        """Returns count of cleared entries."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            result = scheduler.invalidate_all_settings_cache()

            assert isinstance(result, int)


class TestDocumentSchedulerSettingsDataclass:
    """Tests for DocumentSchedulerSettings dataclass."""

    def test_default_enabled_is_true(self):
        """Default enabled is True."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()

        assert settings.enabled is True

    def test_default_interval_seconds(self):
        """Default interval_seconds is 1800 (30 minutes)."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()

        assert settings.interval_seconds == 1800

    def test_default_download_pdfs(self):
        """Default download_pdfs is False."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()

        assert settings.download_pdfs is False

    def test_default_extract_text(self):
        """Default extract_text is True."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()

        assert settings.extract_text is True

    def test_default_generate_rag(self):
        """Default generate_rag is False."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()

        assert settings.generate_rag is False

    def test_default_last_run_empty(self):
        """Default last_run is empty string."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()

        assert settings.last_run == ""

    def test_is_frozen(self):
        """DocumentSchedulerSettings is frozen (immutable)."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()

        # FrozenInstanceError subclasses AttributeError; using a tuple
        # keeps the test forward-compatible if Python's behavior shifts.
        # The previous try/except AttributeError: pass silently passed if
        # NO exception was raised — pytest.raises requires one.
        with pytest.raises((AttributeError, FrozenInstanceError)):
            settings.enabled = False

    def test_custom_values(self):
        """Can create with custom values."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings(
            enabled=False,
            interval_seconds=3600,
            download_pdfs=True,
            extract_text=False,
            generate_rag=True,
            last_run="2024-01-01T00:00:00",
        )

        assert settings.enabled is False
        assert settings.interval_seconds == 3600
        assert settings.download_pdfs is True
        assert settings.extract_text is False
        assert settings.generate_rag is True
        assert settings.last_run == "2024-01-01T00:00:00"

    def test_defaults_classmethod(self):
        """defaults() classmethod returns default settings."""
        from local_deep_research.scheduler.background import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings.defaults()

        assert settings.enabled is True
        assert settings.interval_seconds == 1800


class TestSchedulerSingleton:
    """Tests for scheduler singleton pattern."""

    def test_is_singleton(self):
        """BackgroundJobScheduler is a singleton."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler1 = BackgroundJobScheduler()
            scheduler2 = BackgroundJobScheduler()

            assert scheduler1 is scheduler2

    def test_singleton_lock_exists(self):
        """Singleton has class-level lock."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        assert hasattr(BackgroundJobScheduler, "_lock")


class TestSchedulerConfiguration:
    """Tests for scheduler configuration loading."""

    def test_has_default_config(self):
        """Scheduler has default configuration."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert scheduler.config is not None
            assert isinstance(scheduler.config, dict)

    def test_default_enabled_config(self):
        """Default config has enabled=True."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert scheduler.config.get("enabled", False) is True

    def test_default_retention_hours(self):
        """Default retention_hours is 48."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert scheduler.config.get("retention_hours") == 48

    def test_default_max_jitter_seconds(self):
        """Default max_jitter_seconds is 300."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert scheduler.config.get("max_jitter_seconds") == 300

    def test_get_setting_with_manager(self):
        """_get_setting uses settings_manager when available."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            mock_manager = MagicMock()
            mock_manager.get_setting.return_value = 999
            scheduler.settings_manager = mock_manager

            result = scheduler._get_setting("test.key", 0)

            assert result == 999
            mock_manager.get_setting.assert_called_once()

    def test_get_setting_without_manager(self):
        """_get_setting returns default without settings_manager."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.settings_manager = None

            result = scheduler._get_setting("test.key", 42)

            assert result == 42


class TestSchedulerStateAttributes:
    """Tests for scheduler state attributes."""

    def test_has_user_sessions(self):
        """Scheduler has user_sessions dict."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert hasattr(scheduler, "user_sessions")
            assert isinstance(scheduler.user_sessions, dict)

    def test_has_is_running_flag(self):
        """Scheduler has is_running flag."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert hasattr(scheduler, "is_running")
            assert scheduler.is_running is False

    def test_has_settings_cache(self):
        """Scheduler has settings cache."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert hasattr(scheduler, "_settings_cache")

    def test_has_initialized_flag(self):
        """Scheduler has _initialized flag."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            assert hasattr(scheduler, "_initialized")
            assert scheduler._initialized is True


class TestStartMethod:
    """Additional tests for start() method."""

    def test_does_nothing_if_disabled(self):
        """start() does nothing if config.enabled is False."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler.config["enabled"] = False

            scheduler.start()

            # Scheduler should not have started
            mock_scheduler.start.assert_not_called()

    def test_does_nothing_if_already_running(self):
        """start() does nothing if already running."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler.is_running = True
            initial_call_count = mock_scheduler.start.call_count

            scheduler.start()

            # start() should not be called again
            assert mock_scheduler.start.call_count == initial_call_count


class TestStopMethod:
    """Additional tests for stop() method."""

    def test_does_nothing_if_not_running(self):
        """stop() does nothing if not running."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler.is_running = False

            scheduler.stop()

            # shutdown should not be called
            mock_scheduler.shutdown.assert_not_called()

    def test_sets_is_running_false(self):
        """stop() sets is_running to False."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler.scheduler = mock_scheduler
            scheduler.is_running = True

            scheduler.stop()

            assert scheduler.is_running is False


class TestUpdateUserInfoEdgeCases:
    """Edge case tests for update_user_info."""

    def test_does_nothing_when_not_running(self):
        """update_user_info does nothing when scheduler not running."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.is_running = False
            scheduler.user_sessions = {}

            scheduler.update_user_info("user123", "password")

            # User should not be added since scheduler is not running
            assert "user123" not in scheduler.user_sessions

    def test_stores_password_in_session(self):
        """update_user_info stores password in session."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.is_running = True
            scheduler.user_sessions = {}

            with patch.object(scheduler, "_schedule_user_subscriptions"):
                with patch.object(scheduler, "_schedule_document_processing"):
                    scheduler.update_user_info("user123", "secret_password")

            assert "password" not in scheduler.user_sessions["user123"]
            assert (
                scheduler._credential_store.retrieve("user123")
                == "secret_password"
            )

    def test_initializes_empty_scheduled_jobs(self):
        """update_user_info initializes empty scheduled_jobs set."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler.is_running = True
            scheduler.user_sessions = {}

            with patch.object(scheduler, "_schedule_user_subscriptions"):
                with patch.object(scheduler, "_schedule_document_processing"):
                    scheduler.update_user_info("user123", "password")

            assert scheduler.user_sessions["user123"]["scheduled_jobs"] == set()


class TestUnregisterUserEdgeCases:
    """Edge case tests for unregister_user."""

    def test_handles_job_lookup_error(self):
        """unregister_user handles JobLookupError gracefully."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ) as mock_bg:
            mock_scheduler = MagicMock()
            mock_scheduler.remove_job.side_effect = JobLookupError("job-id")
            mock_bg.return_value = mock_scheduler

            scheduler = BackgroundJobScheduler()
            scheduler.scheduler = mock_scheduler
            scheduler.user_sessions = {
                "user123": {
                    "scheduled_jobs": {"job1", "job2"},
                }
            }
            scheduler._credential_store.store("user123", "test")

            # Should not raise
            scheduler.unregister_user("user123")

            # User should be removed despite JobLookupError
            assert "user123" not in scheduler.user_sessions


class TestInvalidateUserSettingsCache:
    """Tests for invalidate_user_settings_cache method."""

    def test_returns_true_when_found(self):
        """Returns True when user found in cache."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()
            scheduler._settings_cache["user123"] = {"data": "test"}

            result = scheduler.invalidate_user_settings_cache("user123")

            assert result is True
            assert "user123" not in scheduler._settings_cache

    def test_returns_false_when_not_found(self):
        """Returns False when user not in cache."""
        from local_deep_research.scheduler.background import (
            BackgroundJobScheduler,
        )

        BackgroundJobScheduler._instance = None

        with patch(
            "local_deep_research.scheduler.background.BackgroundScheduler"
        ):
            scheduler = BackgroundJobScheduler()

            result = scheduler.invalidate_user_settings_cache("nonexistent")

            assert result is False
