"""
Comprehensive tests for NewsScheduler class.
Tests scheduling, user sessions, document processing, and configuration.
"""

import pytest
from unittest.mock import Mock, patch
from datetime import datetime
import threading


class TestNewsSchedulerSingleton:
    """Tests for NewsScheduler singleton pattern."""

    def test_returns_same_instance(self):
        """Test returns same instance on multiple calls."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        # Reset singleton for test
        NewsScheduler._instance = None

        s1 = NewsScheduler()
        s2 = NewsScheduler()
        assert s1 is s2

    def test_thread_safe_singleton(self):
        """Test singleton is thread-safe."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        instances = []

        def create_instance():
            instances.append(NewsScheduler())

        threads = [threading.Thread(target=create_instance) for _ in range(10)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        # All instances should be the same
        assert all(i is instances[0] for i in instances)


class TestNewsSchedulerInit:
    """Tests for NewsScheduler initialization."""

    def test_initializes_user_sessions_dict(self):
        """Test user_sessions initialized as empty dict."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        assert scheduler.user_sessions == {}

    def test_initializes_lock(self):
        """Test lock is initialized."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        assert isinstance(scheduler.lock, type(threading.Lock()))

    def test_initializes_background_scheduler(self):
        """Test BackgroundScheduler is initialized."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )
        from apscheduler.schedulers.background import BackgroundScheduler

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        assert isinstance(scheduler.scheduler, BackgroundScheduler)

    def test_is_running_initially_false(self):
        """Test is_running is False initially."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        assert scheduler.is_running is False


class TestLoadDefaultConfig:
    """Tests for _load_default_config method."""

    def test_returns_dict(self):
        """Test returns a dictionary."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        config = scheduler._load_default_config()
        assert isinstance(config, dict)

    def test_includes_enabled_key(self):
        """Test config includes enabled key."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        config = scheduler._load_default_config()
        assert "enabled" in config

    def test_includes_retention_hours(self):
        """Test config includes retention_hours."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        config = scheduler._load_default_config()
        assert "retention_hours" in config
        assert config["retention_hours"] == 48

    def test_includes_max_jitter_seconds(self):
        """Test config includes max_jitter_seconds."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        config = scheduler._load_default_config()
        assert "max_jitter_seconds" in config


class TestInitializeWithSettings:
    """Tests for initialize_with_settings method."""

    def test_stores_settings_manager(self):
        """Test stores settings manager reference."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        mock_settings = Mock()
        mock_settings.get_setting.return_value = True

        scheduler.initialize_with_settings(mock_settings)
        assert scheduler.settings_manager is mock_settings

    def test_loads_config_from_settings(self):
        """Test loads config from settings manager."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        mock_settings = Mock()
        mock_settings.get_setting.side_effect = lambda k, default: {
            "news.scheduler.enabled": False,
            "news.scheduler.retention_hours": 24,
        }.get(k, default)

        scheduler.initialize_with_settings(mock_settings)
        assert scheduler.config["enabled"] is False
        assert scheduler.config["retention_hours"] == 24

    def test_handles_settings_exception(self):
        """Test handles exception during settings load."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        mock_settings = Mock()
        mock_settings.get_setting.side_effect = Exception("Settings error")

        # Should not raise
        scheduler.initialize_with_settings(mock_settings)


class TestGetSetting:
    """Tests for _get_setting method."""

    def test_returns_default_without_settings_manager(self):
        """Test returns default when no settings manager."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        result = scheduler._get_setting("some.key", default=42)
        assert result == 42

    def test_calls_settings_manager(self):
        """Test calls settings manager get_setting."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        mock_settings = Mock()
        mock_settings.get_setting.return_value = 100
        scheduler.settings_manager = mock_settings

        _ = scheduler._get_setting("some.key", default=42)
        mock_settings.get_setting.assert_called_with("some.key", default=42)


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

    def test_is_frozen(self):
        """Test dataclass is frozen (immutable)."""
        from local_deep_research.news.subscription_manager.scheduler import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()
        with pytest.raises(Exception):  # FrozenInstanceError
            settings.enabled = False

    def test_default_enabled_is_true(self):
        """Test default enabled is True."""
        from local_deep_research.news.subscription_manager.scheduler import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()
        assert settings.enabled is True

    def test_default_interval_seconds(self):
        """Test default interval_seconds is 1800."""
        from local_deep_research.news.subscription_manager.scheduler import (
            DocumentSchedulerSettings,
        )

        settings = DocumentSchedulerSettings()
        assert settings.interval_seconds == 1800

    def test_defaults_factory_method(self):
        """Test defaults() factory method."""
        from local_deep_research.news.subscription_manager.scheduler import (
            DocumentSchedulerSettings,
        )

        defaults = DocumentSchedulerSettings.defaults()
        assert defaults.enabled is True
        assert defaults.interval_seconds == 1800


class TestGetDocumentSchedulerSettings:
    """Tests for _get_document_scheduler_settings method."""

    def test_returns_cached_value(self):
        """Test returns cached value when available."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        cached_settings = DocumentSchedulerSettings(
            enabled=False, interval_seconds=600
        )
        scheduler._settings_cache["testuser"] = cached_settings

        result = scheduler._get_document_scheduler_settings("testuser")
        assert result is cached_settings

    def test_returns_defaults_without_session(self):
        """Test returns defaults when no user session."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        result = scheduler._get_document_scheduler_settings("unknown_user")
        defaults = DocumentSchedulerSettings.defaults()
        assert result.enabled == defaults.enabled

    def test_force_refresh_bypasses_cache(self):
        """Test force_refresh bypasses cache."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        cached_settings = DocumentSchedulerSettings(enabled=False)
        scheduler._settings_cache["testuser"] = cached_settings

        # Without session, should return defaults even with cache
        result = scheduler._get_document_scheduler_settings(
            "testuser", force_refresh=True
        )
        # Returns defaults since no session
        assert result.enabled is True


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

    def test_removes_user_from_cache(self):
        """Test removes user from cache."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        scheduler._settings_cache["testuser"] = DocumentSchedulerSettings()
        scheduler.invalidate_user_settings_cache("testuser")

        assert "testuser" not in scheduler._settings_cache

    def test_returns_true_if_found(self):
        """Test returns True when user was in cache."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        scheduler._settings_cache["testuser"] = DocumentSchedulerSettings()
        result = scheduler.invalidate_user_settings_cache("testuser")

        assert result is True

    def test_returns_false_if_not_found(self):
        """Test returns False when user not in cache."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        result = scheduler.invalidate_user_settings_cache("unknown_user")
        assert result is False


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

    def test_clears_all_entries(self):
        """Test clears all cache entries."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        scheduler._settings_cache["user1"] = DocumentSchedulerSettings()
        scheduler._settings_cache["user2"] = DocumentSchedulerSettings()

        scheduler.invalidate_all_settings_cache()

        assert len(scheduler._settings_cache) == 0

    def test_returns_count_cleared(self):
        """Test returns count of cleared entries."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        scheduler._settings_cache["user1"] = DocumentSchedulerSettings()
        scheduler._settings_cache["user2"] = DocumentSchedulerSettings()
        scheduler._settings_cache["user3"] = DocumentSchedulerSettings()

        count = scheduler.invalidate_all_settings_cache()
        assert count == 3


class TestStartScheduler:
    """Tests for start method."""

    def test_sets_is_running_true(self):
        """Test sets is_running to True."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        with patch.object(scheduler.scheduler, "start"):
            scheduler.start()
            assert scheduler.is_running is True

    def test_starts_background_scheduler(self):
        """Test starts the background scheduler."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        with patch.object(scheduler.scheduler, "start") as mock_start:
            scheduler.start()
            mock_start.assert_called_once()

    def test_does_not_start_if_already_running(self):
        """Test does not start if already running."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        scheduler.is_running = True

        with patch.object(scheduler.scheduler, "start") as mock_start:
            scheduler.start()
            mock_start.assert_not_called()


class TestStopScheduler:
    """Tests for stop method."""

    def test_sets_is_running_false(self):
        """Test sets is_running to False."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        scheduler.is_running = True

        with patch.object(scheduler.scheduler, "shutdown"):
            scheduler.stop()
            assert scheduler.is_running is False

    def test_shuts_down_scheduler(self):
        """Test shuts down the background scheduler."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        scheduler.is_running = True

        with patch.object(scheduler.scheduler, "shutdown") as mock_shutdown:
            scheduler.stop()
            mock_shutdown.assert_called_once()


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

    def test_creates_new_session(self):
        """Test creates new session for new user."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        scheduler.is_running = True  # Enable scheduler

        with patch.object(scheduler, "_schedule_user_subscriptions"):
            scheduler.update_user_info("newuser", "password123")

        assert "newuser" in scheduler.user_sessions
        assert "password" not in scheduler.user_sessions["newuser"]
        assert scheduler._credential_store.retrieve("newuser") == "password123"

    def test_updates_last_activity(self):
        """Test updates last_activity timestamp."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )
        from datetime import timezone

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        scheduler.is_running = True

        before = datetime.now(timezone.utc)
        with patch.object(scheduler, "_schedule_user_subscriptions"):
            scheduler.update_user_info("testuser", "password")
        after = datetime.now(timezone.utc)

        last_activity = scheduler.user_sessions["testuser"]["last_activity"]
        assert before <= last_activity <= after

    def test_initializes_scheduled_jobs_set(self):
        """Test initializes empty scheduled_jobs set."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        scheduler.is_running = True

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

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


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

    def test_removes_user_from_sessions(self):
        """Test removes user from sessions."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        scheduler.user_sessions["testuser"] = {
            "last_activity": datetime.now(),
            "scheduled_jobs": set(),
        }
        scheduler._credential_store.store("testuser", "test")

        scheduler.unregister_user("testuser")

        assert "testuser" not in scheduler.user_sessions

    def test_invalidates_settings_cache(self):
        """Test invalidates settings cache for user."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
            DocumentSchedulerSettings,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        scheduler.user_sessions["testuser"] = {
            "last_activity": datetime.now(),
            "scheduled_jobs": set(),
        }
        scheduler._credential_store.store("testuser", "test")
        scheduler._settings_cache["testuser"] = DocumentSchedulerSettings()

        scheduler.unregister_user("testuser")

        assert "testuser" not in scheduler._settings_cache

    def test_handles_nonexistent_user(self):
        """Test handles unregistering nonexistent user."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

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


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

    def test_returns_dict(self):
        """Test returns a dictionary."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        result = scheduler.get_document_scheduler_status("testuser")
        assert isinstance(result, dict)

    def test_includes_enabled_flag(self):
        """Test includes enabled flag."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        result = scheduler.get_document_scheduler_status("testuser")
        assert "enabled" in result

    def test_returns_disabled_for_unknown_user(self):
        """Test returns disabled for unknown user."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        result = scheduler.get_document_scheduler_status("unknown")
        # Default settings should have enabled=True
        assert "enabled" in result


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

    def test_returns_false_if_no_session(self):
        """Test returns False if no user session."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        result = scheduler.trigger_document_processing("unknown_user")
        assert result is False

    def test_returns_false_if_not_running(self):
        """Test returns False if scheduler not running."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()
        scheduler.is_running = False
        scheduler.user_sessions["testuser"] = {
            "last_activity": datetime.now(),
            "scheduled_jobs": set(),
        }
        scheduler._credential_store.store("testuser", "test")

        result = scheduler.trigger_document_processing("testuser")
        assert result is False


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

    def test_lock_used_for_user_sessions(self):
        """Test lock is used when modifying user_sessions."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        # The lock should be accessible
        assert hasattr(scheduler, "lock")
        assert isinstance(scheduler.lock, type(threading.Lock()))

    def test_settings_cache_lock_exists(self):
        """Test settings cache lock exists."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        assert hasattr(scheduler, "_settings_cache_lock")
        assert isinstance(
            scheduler._settings_cache_lock, type(threading.Lock())
        )


class TestSchedulerConfigDefaults:
    """Tests for scheduler configuration defaults."""

    def test_default_retention_hours_is_48(self):
        """Test default retention_hours is 48."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        assert scheduler.config["retention_hours"] == 48

    def test_default_cleanup_interval_is_1_hour(self):
        """Test default cleanup_interval_hours is 1."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        assert scheduler.config["cleanup_interval_hours"] == 1

    def test_default_max_jitter_is_300_seconds(self):
        """Test default max_jitter_seconds is 300."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        assert scheduler.config["max_jitter_seconds"] == 300

    def test_default_max_concurrent_jobs_is_10(self):
        """Test default max_concurrent_jobs is 10."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None
        scheduler = NewsScheduler()

        assert scheduler.config["max_concurrent_jobs"] == 10
