"""
Tests for news/subscription_manager/scheduler.py

Tests cover:
- NewsScheduler singleton pattern
- Configuration loading
- User session management
- Scheduler lifecycle
"""

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


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

    def test_news_scheduler_is_singleton(self):
        """NewsScheduler follows singleton pattern."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        # Reset singleton for test
        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()

            scheduler1 = NewsScheduler()
            scheduler2 = NewsScheduler()

            assert scheduler1 is scheduler2

    def test_scheduler_has_required_attributes(self):
        """NewsScheduler has required attributes after init."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        # Reset singleton for test
        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()

            scheduler = NewsScheduler()

            assert hasattr(scheduler, "user_sessions")
            assert hasattr(scheduler, "lock")
            assert hasattr(scheduler, "scheduler")
            assert hasattr(scheduler, "config")
            assert hasattr(scheduler, "is_running")


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

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_default_config_values(self, scheduler):
        """Default configuration has expected values."""
        config = scheduler.config

        assert config["enabled"] is True
        assert config["retention_hours"] == 48
        assert config["cleanup_interval_hours"] == 1
        assert config["max_jitter_seconds"] == 300
        assert config["max_concurrent_jobs"] == 10
        assert config["subscription_batch_size"] == 5
        assert config["activity_check_interval_minutes"] == 5

    def test_initialize_with_settings(self, scheduler):
        """Scheduler can be initialized with settings manager."""
        mock_settings = Mock()
        mock_settings.get.return_value = None

        # Should not raise
        scheduler.initialize_with_settings(mock_settings)

        assert scheduler.settings_manager is mock_settings


class TestSchedulerLifecycle:
    """Tests for scheduler start/stop lifecycle."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler_instance = MagicMock()
            mock_scheduler.return_value = mock_scheduler_instance
            instance = NewsScheduler()
            yield instance

    def test_scheduler_initial_state_not_running(self, scheduler):
        """Scheduler is not running initially."""
        assert scheduler.is_running is False

    def test_user_sessions_initially_empty(self, scheduler):
        """User sessions dict is initially empty."""
        assert scheduler.user_sessions == {}


class TestUserSessionManagement:
    """Tests for user session tracking."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_lock_is_thread_lock(self, scheduler):
        """Scheduler has threading lock for thread safety."""
        assert isinstance(scheduler.lock, type(threading.Lock()))


class TestSchedulerAvailability:
    """Tests for scheduler availability flag."""

    def test_scheduler_is_available(self):
        """Scheduler availability flag is True."""
        from local_deep_research.news.subscription_manager.scheduler import (
            SCHEDULER_AVAILABLE,
        )

        assert SCHEDULER_AVAILABLE is True


class TestSchedulerStart:
    """Tests for scheduler start method."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler_instance = MagicMock()
            mock_scheduler.return_value = mock_scheduler_instance
            instance = NewsScheduler()
            yield instance

    def test_start_sets_is_running(self, scheduler):
        """Starting scheduler sets is_running to True."""
        scheduler.start()

        assert scheduler.is_running is True
        scheduler.scheduler.start.assert_called_once()

    def test_start_when_disabled(self, scheduler):
        """Scheduler doesn't start when disabled."""
        scheduler.config["enabled"] = False

        scheduler.start()

        assert scheduler.is_running is False
        scheduler.scheduler.start.assert_not_called()

    def test_start_when_already_running(self, scheduler):
        """Scheduler warns when already running."""
        scheduler.is_running = True

        scheduler.start()

        # Should not call start again
        scheduler.scheduler.start.assert_not_called()

    def test_start_adds_cleanup_job(self, scheduler):
        """Starting scheduler adds cleanup job."""
        scheduler.start()

        # Check that add_job was called at least once
        assert scheduler.scheduler.add_job.called


class TestSchedulerStop:
    """Tests for scheduler stop method."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler_instance = MagicMock()
            mock_scheduler.return_value = mock_scheduler_instance
            instance = NewsScheduler()
            yield instance

    def test_stop_sets_is_running_false(self, scheduler):
        """Stopping scheduler sets is_running to False."""
        scheduler.is_running = True
        scheduler.stop()

        assert scheduler.is_running is False

    def test_stop_when_not_running(self, scheduler):
        """Stopping scheduler when not running is safe."""
        scheduler.is_running = False

        # Should not raise
        scheduler.stop()

        assert scheduler.is_running is False


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

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_get_setting_with_settings_manager(self, scheduler):
        """_get_setting uses settings manager when available."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = 100

        scheduler.settings_manager = mock_settings

        result = scheduler._get_setting("some.key", 50)

        assert result == 100
        mock_settings.get_setting.assert_called_once_with(
            "some.key", default=50
        )

    def test_get_setting_without_settings_manager(self, scheduler):
        """_get_setting returns default without settings manager."""
        # No settings manager

        result = scheduler._get_setting("some.key", 50)

        assert result == 50


class TestSchedulerStatus:
    """Tests for scheduler status methods."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_get_status_when_not_running(self, scheduler):
        """Get status when scheduler is not running."""
        scheduler.is_running = False

        if hasattr(scheduler, "get_status"):
            status = scheduler.get_status()
            assert (
                status.get("running") is False
                or status.get("is_running") is False
            )

    def test_get_status_when_running(self, scheduler):
        """Get status when scheduler is running."""
        scheduler.is_running = True

        if hasattr(scheduler, "get_status"):
            status = scheduler.get_status()
            assert (
                status.get("running") is True
                or status.get("is_running") is True
            )


class TestSchedulerRegisterUser:
    """Tests for user registration methods."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_register_user_adds_to_sessions(self, scheduler):
        """Registering user adds them to sessions dict."""
        if hasattr(scheduler, "register_user_activity"):
            scheduler.register_user_activity("testuser", "password123")

            assert "testuser" in scheduler.user_sessions

    def test_register_user_updates_activity(self, scheduler):
        """Registering existing user updates last_activity."""
        if hasattr(scheduler, "register_user_activity"):
            scheduler.register_user_activity("testuser", "password123")
            first_activity = scheduler.user_sessions["testuser"].get(
                "last_activity"
            )

            # Register again
            import time

            time.sleep(0.1)
            scheduler.register_user_activity("testuser", "password123")
            second_activity = scheduler.user_sessions["testuser"].get(
                "last_activity"
            )

            # Activity should be updated
            if first_activity and second_activity:
                assert second_activity >= first_activity


class TestSchedulerUnregisterUser:
    """Tests for user unregistration."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_unregister_removes_user(self, scheduler):
        """Unregistering user removes them from sessions."""
        # Set up proper session structure
        scheduler.user_sessions["testuser"] = {
            "password": "test",
            "scheduled_jobs": [],
            "last_activity": None,
        }

        if hasattr(scheduler, "unregister_user"):
            scheduler.unregister_user("testuser")
            assert "testuser" not in scheduler.user_sessions

    def test_unregister_nonexistent_user(self, scheduler):
        """Unregistering non-existent user is safe."""
        if hasattr(scheduler, "unregister_user"):
            # Should not raise
            scheduler.unregister_user("nonexistent")


class TestScheduleUserSubscriptions:
    """Tests for _schedule_user_subscriptions method."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_schedule_user_subscriptions_uses_jitter(self, scheduler):
        """_schedule_user_subscriptions applies random jitter."""
        # Verify the scheduler has max_jitter_seconds config
        assert "max_jitter_seconds" in scheduler.config
        assert scheduler.config["max_jitter_seconds"] == 300

    def test_schedule_user_subscriptions_respects_batch_size(self, scheduler):
        """_schedule_user_subscriptions respects subscription_batch_size."""
        assert "subscription_batch_size" in scheduler.config
        assert scheduler.config["subscription_batch_size"] == 5

    def test_schedule_user_subscriptions_jitter_calculation(self, scheduler):
        """Jitter is calculated based on max_jitter_seconds."""
        import random

        random.seed(42)  # Make deterministic for test
        max_jitter = scheduler.config["max_jitter_seconds"]

        # Generate some jitter values
        jitters = [random.randint(0, max_jitter) for _ in range(10)]

        # All values should be within range
        assert all(0 <= j <= max_jitter for j in jitters)

    def test_schedule_user_subscriptions_schedules_jobs(self, scheduler):
        """_schedule_user_subscriptions adds jobs to the scheduler."""
        if hasattr(scheduler, "_schedule_user_subscriptions"):
            # Method exists
            assert callable(scheduler._schedule_user_subscriptions)


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

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_process_user_documents_batch_processing(self, scheduler):
        """_process_user_documents processes in batches."""
        # Verify batch size config exists
        assert "subscription_batch_size" in scheduler.config

    def test_process_user_documents_max_concurrent(self, scheduler):
        """_process_user_documents respects max_concurrent_jobs."""
        assert "max_concurrent_jobs" in scheduler.config
        assert scheduler.config["max_concurrent_jobs"] == 10


class TestStoreResearchResult:
    """Tests for _store_research_result method."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_store_research_result_serialization(self, scheduler):
        """Research results are properly serialized."""
        # The scheduler should have retention_hours configured
        assert "retention_hours" in scheduler.config
        assert scheduler.config["retention_hours"] == 48


class TestCleanupOldResults:
    """Tests for cleanup functionality."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_cleanup_interval_configured(self, scheduler):
        """Cleanup interval is properly configured."""
        assert "cleanup_interval_hours" in scheduler.config
        assert scheduler.config["cleanup_interval_hours"] == 1

    def test_retention_hours_configured(self, scheduler):
        """Retention hours is properly configured."""
        assert "retention_hours" in scheduler.config
        assert scheduler.config["retention_hours"] == 48


class TestActivityTracking:
    """Tests for user activity tracking."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_activity_check_interval_configured(self, scheduler):
        """Activity check interval is properly configured."""
        assert "activity_check_interval_minutes" in scheduler.config
        assert scheduler.config["activity_check_interval_minutes"] == 5

    def test_inactive_user_detection(self, scheduler):
        """Inactive users can be detected."""
        from datetime import datetime, timedelta, UTC

        if hasattr(scheduler, "user_sessions"):
            # Set up a user session with old activity
            old_activity = datetime.now(UTC) - timedelta(hours=1)
            scheduler.user_sessions["old_user"] = {
                "password": "test",
                "scheduled_jobs": [],
                "last_activity": old_activity,
            }

            # The user session should be in the dict
            assert "old_user" in scheduler.user_sessions


class TestSchedulerExceptionHandling:
    """Tests for scheduler exception handling."""

    @pytest.fixture
    def scheduler(self):
        """Create a fresh scheduler instance."""
        from local_deep_research.news.subscription_manager.scheduler import (
            NewsScheduler,
        )

        NewsScheduler._instance = None

        with patch(
            "local_deep_research.news.subscription_manager.scheduler.BackgroundScheduler"
        ) as mock_scheduler:
            mock_scheduler.return_value = MagicMock()
            instance = NewsScheduler()
            yield instance

    def test_scheduler_handles_job_exceptions(self, scheduler):
        """Scheduler handles exceptions in job execution."""
        # The scheduler should have proper error handling
        assert scheduler.scheduler is not None

    def test_scheduler_recovers_from_errors(self, scheduler):
        """Scheduler can recover from errors."""
        scheduler.is_running = True

        # Stopping should work even after errors
        scheduler.stop()
        assert scheduler.is_running is False
