"""
Extended Tests for Retry Manager

Phase 16: Download Management Deep Coverage - Retry Manager Tests
Tests resource filtering, attempt recording, and retry logic.
"""

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

from local_deep_research.library.download_management.retry_manager import (
    RetryManager,
    RetryDecision,
    ResourceFilterResult,
    FilterSummary,
)
from local_deep_research.library.download_management.failure_classifier import (
    PermanentFailure,
    TemporaryFailure,
    RateLimitFailure,
)


class TestResourceFiltering:
    """Tests for resource filtering functionality"""

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_resources_all_eligible(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering when all resources are eligible for retry"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (True, None)
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        # Create mock resources
        resources = [MagicMock(id=1), MagicMock(id=2), MagicMock(id=3)]

        results = manager.filter_resources(resources)

        assert len(results) == 3
        assert all(r.can_retry for r in results)
        assert all(r.status == "available" for r in results)

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_resources_none_eligible(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering when no resources are eligible for retry"""
        mock_tracker = MagicMock()
        # Use lowercase "permanently failed" to match source code check
        mock_tracker.can_retry.return_value = (
            False,
            "permanently failed: Not found",
        )
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        resources = [MagicMock(id=1), MagicMock(id=2)]

        results = manager.filter_resources(resources)

        assert len(results) == 2
        assert not any(r.can_retry for r in results)
        assert all(r.status == "permanently_failed" for r in results)

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_resources_partial_eligible(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering when some resources are eligible"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.side_effect = [
            (True, None),
            (
                False,
                "permanently failed: 404",
            ),  # lowercase to match source code
            (True, None),
        ]
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        resources = [MagicMock(id=1), MagicMock(id=2), MagicMock(id=3)]

        results = manager.filter_resources(resources)

        assert len(results) == 3
        assert results[0].can_retry is True
        assert results[1].can_retry is False
        assert results[2].can_retry is True

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_resources_cooldown_active(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering resources with active cooldown"""
        mock_tracker = MagicMock()
        # Use lowercase "cooldown" to match source code check
        mock_tracker.can_retry.return_value = (
            False,
            "cooldown active, retry at 2025-01-01",
        )
        mock_tracker.get_resource_status.return_value = {
            "retry_after_timestamp": (
                datetime.now(UTC) + timedelta(hours=2)
            ).isoformat()
        }
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        resources = [MagicMock(id=1)]

        results = manager.filter_resources(resources)

        assert len(results) == 1
        assert results[0].can_retry is False
        assert results[0].status == "temporarily_failed"
        assert results[0].estimated_wait is not None

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_resources_daily_limit_exceeded(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering resources that exceeded daily limit"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (
            False,
            "Daily retry limit exceeded",
        )
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        resources = [MagicMock(id=1)]

        results = manager.filter_resources(resources)

        assert len(results) == 1
        assert results[0].can_retry is False
        # Not a permanent or cooldown failure, should be unavailable
        assert results[0].status == "unavailable"

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_resources_permanent_failure(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering resources with permanent failures"""
        mock_tracker = MagicMock()
        # Use lowercase "permanently failed" to match source code check
        mock_tracker.can_retry.return_value = (
            False,
            "permanently failed: File not found",
        )
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        resources = [MagicMock(id=1)]

        results = manager.filter_resources(resources)

        assert len(results) == 1
        assert results[0].can_retry is False
        assert results[0].status == "permanently_failed"

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_estimated_wait_time_calculation(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test calculation of estimated wait time"""
        future_time = datetime.now(UTC) + timedelta(hours=3)

        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (
            False,
            "cooldown active",
        )  # lowercase
        mock_tracker.get_resource_status.return_value = {
            "retry_after_timestamp": future_time.isoformat()
        }
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        resources = [MagicMock(id=1)]

        results = manager.filter_resources(resources)

        assert results[0].estimated_wait is not None
        # Should be approximately 3 hours
        assert results[0].estimated_wait > timedelta(hours=2)
        assert results[0].estimated_wait < timedelta(hours=4)

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_summary_generation(self, mock_classifier, mock_tracker_cls):
        """Test generating summary from filter results"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.side_effect = [
            (True, None),
            (False, "permanently failed"),  # lowercase to match source code
            (False, "cooldown active"),  # lowercase to match source code
        ]
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        resources = [MagicMock(id=1), MagicMock(id=2), MagicMock(id=3)]
        results = manager.filter_resources(resources)

        summary = manager.get_filter_summary(results)

        assert summary.total_count == 3
        assert summary.downloadable_count == 1

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_resource_status_determination(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test status string determination from retry decision"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        # Test available status
        assert manager._get_resource_status(True, None) == "available"

        # Test permanently_failed status (lowercase to match source code check)
        assert (
            manager._get_resource_status(False, "permanently failed: 404")
            == "permanently_failed"
        )

        # Test temporarily_failed status (lowercase to match source code check)
        assert (
            manager._get_resource_status(False, "cooldown active")
            == "temporarily_failed"
        )

        # Test unavailable status (other reason)
        assert (
            manager._get_resource_status(False, "Some other reason")
            == "unavailable"
        )

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_empty_resource_list(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering empty resource list"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        results = manager.filter_resources([])

        assert len(results) == 0

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_large_resource_batch(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test filtering a large batch of resources"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (True, None)
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        # Create 100 mock resources
        resources = [MagicMock(id=i) for i in range(100)]

        results = manager.filter_resources(resources)

        assert len(results) == 100
        assert mock_tracker.can_retry.call_count == 100


class TestAttemptRecording:
    """Tests for recording download attempts"""

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_record_attempt_success(self, mock_classifier, mock_tracker_cls):
        """Test recording successful download attempt"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        manager.record_attempt(
            resource_id=1,
            result=(True, None),
        )

        mock_tracker.mark_success.assert_called_once_with(1, session=None)

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_record_attempt_failure_temporary(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test recording temporary failure attempt"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        mock_failure = TemporaryFailure(
            "timeout", "Timed out", timedelta(minutes=30)
        )
        mock_classifier.return_value.classify_failure.return_value = (
            mock_failure
        )

        manager = RetryManager("test_user")

        manager.record_attempt(
            resource_id=1,
            result=(False, "Request timed out"),
            status_code=None,
            url="https://example.com",
            details="Connection timeout",
        )

        mock_tracker.mark_failure.assert_called_once()
        call_args = mock_tracker.mark_failure.call_args
        assert call_args[0][0] == 1  # resource_id
        assert isinstance(call_args[0][1], TemporaryFailure)

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_record_attempt_failure_permanent(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test recording permanent failure attempt"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        mock_failure = PermanentFailure("not_found", "Resource not found (404)")
        mock_classifier.return_value.classify_failure.return_value = (
            mock_failure
        )

        manager = RetryManager("test_user")

        manager.record_attempt(
            resource_id=1,
            result=(False, "Not found"),
            status_code=404,
            url="https://example.com/missing.pdf",
        )

        mock_tracker.mark_failure.assert_called_once()
        call_args = mock_tracker.mark_failure.call_args
        assert isinstance(call_args[0][1], PermanentFailure)

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_record_attempt_rate_limited(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test recording rate limit failure attempt"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        mock_failure = RateLimitFailure("arxiv.org")
        mock_classifier.return_value.classify_failure.return_value = (
            mock_failure
        )

        manager = RetryManager("test_user")

        manager.record_attempt(
            resource_id=1,
            result=(False, "Rate limited"),
            status_code=429,
            url="https://arxiv.org/pdf/2301.00001.pdf",
        )

        mock_tracker.mark_failure.assert_called_once()

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_retry_counter_increment(self, mock_classifier, mock_tracker_cls):
        """Test retry counter is incremented on failure"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        mock_failure = TemporaryFailure("error", "Error", timedelta(hours=1))
        mock_classifier.return_value.classify_failure.return_value = (
            mock_failure
        )

        manager = RetryManager("test_user")

        # Record multiple failures
        for _ in range(3):
            manager.record_attempt(
                resource_id=1,
                result=(False, "Error"),
            )

        assert mock_tracker.mark_failure.call_count == 3

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_retry_limit_exceeded(self, mock_classifier, mock_tracker_cls):
        """Test behavior when retry limit is exceeded"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (
            False,
            "Daily retry limit exceeded (3/3)",
        )
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        decision = manager.should_retry_resource(1)

        assert decision.can_retry is False
        assert "limit exceeded" in decision.reason.lower()

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_daily_counter_reset_logic(self, mock_classifier, mock_tracker_cls):
        """Test daily counter reset logic"""
        mock_tracker = MagicMock()
        mock_tracker._get_session.return_value.__enter__ = MagicMock(
            return_value=MagicMock()
        )
        mock_tracker._get_session.return_value.__exit__ = MagicMock(
            return_value=False
        )
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        # Mock the query result
        with patch.object(manager, "status_tracker") as mock_st:
            mock_session = MagicMock()
            mock_st._get_session.return_value.__enter__ = MagicMock(
                return_value=mock_session
            )
            mock_st._get_session.return_value.__exit__ = MagicMock(
                return_value=False
            )
            mock_session.query.return_value.update.return_value = 5

            result = manager.reset_daily_retry_counters()

            # Should return the count of reset records
            assert result == 5

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_cooldown_expiry_calculation(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test cooldown expiry is calculated correctly"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (True, None)  # Cooldown expired
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        decision = manager.should_retry_resource(1)

        assert decision.can_retry is True
        assert decision.reason is None

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_session_management(self, mock_classifier, mock_tracker_cls):
        """Test session is passed through correctly"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        mock_session = MagicMock()
        manager.record_attempt(
            resource_id=1,
            result=(True, None),
            session=mock_session,
        )

        mock_tracker.mark_success.assert_called_once_with(
            1, session=mock_session
        )

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_database_commit_on_record(self, mock_classifier, mock_tracker_cls):
        """Test database commit is handled properly"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        manager.record_attempt(
            resource_id=1,
            result=(True, None),
        )

        # mark_success should be called (which handles its own commit)
        mock_tracker.mark_success.assert_called_once()

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_concurrent_attempt_recording(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test concurrent attempt recording"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        # Simulate concurrent recording
        for i in range(10):
            manager.record_attempt(
                resource_id=i,
                result=(True, None),
            )

        assert mock_tracker.mark_success.call_count == 10

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_old_permanent_failure_cleanup(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test cleaning up old permanent failures"""
        mock_tracker = MagicMock()
        mock_tracker.clear_permanent_failures.return_value = 10
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        result = manager.clear_old_permanent_failures(days=30)

        assert result == 10
        mock_tracker.clear_permanent_failures.assert_called_once_with(30)

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_reset_daily_retry_counters(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test resetting daily retry counters"""
        mock_tracker = MagicMock()
        mock_tracker._get_session.return_value.__enter__ = MagicMock()
        mock_tracker._get_session.return_value.__exit__ = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        with patch.object(
            manager.status_tracker, "_get_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_get_session.return_value.__enter__ = MagicMock(
                return_value=mock_session
            )
            mock_get_session.return_value.__exit__ = MagicMock(
                return_value=False
            )
            mock_session.query.return_value.update.return_value = 5

            result = manager.reset_daily_retry_counters()

            assert result == 5


class TestRetryDecision:
    """Tests for RetryDecision dataclass"""

    def test_retry_decision_can_retry_true(self):
        """Test RetryDecision with can_retry=True"""
        decision = RetryDecision(can_retry=True)

        assert decision.can_retry is True
        assert decision.reason is None
        assert decision.estimated_wait_time is None

    def test_retry_decision_can_retry_false(self):
        """Test RetryDecision with can_retry=False"""
        decision = RetryDecision(
            can_retry=False,
            reason="Permanently failed",
        )

        assert decision.can_retry is False
        assert decision.reason == "Permanently failed"

    def test_retry_decision_with_wait_time(self):
        """Test RetryDecision with estimated wait time"""
        decision = RetryDecision(
            can_retry=False,
            reason="Cooldown active",
            estimated_wait_time=timedelta(hours=2),
        )

        assert decision.can_retry is False
        assert decision.estimated_wait_time == timedelta(hours=2)


class TestResourceFilterResult:
    """Tests for ResourceFilterResult class"""

    def test_filter_result_available(self):
        """Test filter result for available resource"""
        result = ResourceFilterResult(
            resource_id=1,
            can_retry=True,
            status="available",
        )

        assert result.resource_id == 1
        assert result.can_retry is True
        assert result.status == "available"
        assert result.reason == ""

    def test_filter_result_failed(self):
        """Test filter result for failed resource"""
        result = ResourceFilterResult(
            resource_id=1,
            can_retry=False,
            status="permanently_failed",
            reason="File not found",
        )

        assert result.can_retry is False
        assert result.status == "permanently_failed"
        assert result.reason == "File not found"

    def test_filter_result_with_wait(self):
        """Test filter result with estimated wait time"""
        result = ResourceFilterResult(
            resource_id=1,
            can_retry=False,
            status="temporarily_failed",
            reason="Cooldown active",
            estimated_wait=timedelta(hours=3),
        )

        assert result.estimated_wait == timedelta(hours=3)


class TestFilterSummary:
    """Tests for FilterSummary class"""

    def test_filter_summary_initialization(self):
        """Test FilterSummary initializes with zeros"""
        summary = FilterSummary()

        assert summary.total_count == 0
        assert summary.downloadable_count == 0
        assert summary.permanently_failed_count == 0
        assert summary.temporarily_failed_count == 0
        assert summary.available_count == 0

    def test_filter_summary_add_downloadable(self):
        """Test adding downloadable result to summary"""
        summary = FilterSummary()

        result = ResourceFilterResult(
            resource_id=1,
            can_retry=True,
            status="available",
        )

        summary.add_result(result)

        assert summary.total_count == 1
        assert summary.downloadable_count == 1
        assert summary.permanently_failed_count == 0

    def test_filter_summary_add_permanent_failure(self):
        """Test adding permanent failure to summary"""
        summary = FilterSummary()

        result = ResourceFilterResult(
            resource_id=1,
            can_retry=False,
            status="permanently_failed",
        )

        summary.add_result(result)

        assert summary.total_count == 1
        assert summary.permanently_failed_count == 1
        assert summary.downloadable_count == 0

    def test_filter_summary_add_temporary_failure(self):
        """Test adding temporary failure to summary"""
        summary = FilterSummary()

        result = ResourceFilterResult(
            resource_id=1,
            can_retry=False,
            status="temporarily_failed",
        )

        summary.add_result(result)

        assert summary.total_count == 1
        assert summary.temporarily_failed_count == 1

    def test_filter_summary_to_dict(self):
        """Test converting summary to dictionary"""
        summary = FilterSummary()
        summary.total_count = 10
        summary.downloadable_count = 7
        summary.permanently_failed_count = 2
        summary.temporarily_failed_count = 1

        result = summary.to_dict()

        assert result["total_count"] == 10
        assert result["downloadable_count"] == 7
        assert result["permanently_failed_count"] == 2
        assert result["temporarily_failed_count"] == 1

    def test_filter_summary_multiple_results(self):
        """Test adding multiple results to summary"""
        summary = FilterSummary()

        results = [
            ResourceFilterResult(1, True, "available"),
            ResourceFilterResult(2, True, "available"),
            ResourceFilterResult(3, False, "permanently_failed"),
            ResourceFilterResult(4, False, "temporarily_failed"),
            ResourceFilterResult(5, False, "unavailable"),
        ]

        for result in results:
            summary.add_result(result)

        assert summary.total_count == 5
        assert summary.downloadable_count == 2
        assert summary.permanently_failed_count == 1
        assert summary.temporarily_failed_count == 1
        assert summary.available_count == 1


class TestRetryManagerInitialization:
    """Tests for RetryManager initialization"""

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_initialization_with_password(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test initialization with password"""
        manager = RetryManager("test_user", password="test_password")

        mock_tracker_cls.assert_called_once_with("test_user", "test_password")
        assert manager.username == "test_user"

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_initialization_without_password(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test initialization without password"""
        manager = RetryManager("test_user")

        mock_tracker_cls.assert_called_once_with("test_user", None)
        assert manager.username == "test_user"

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_components_initialized(self, mock_classifier, mock_tracker_cls):
        """Test all components are properly initialized"""
        manager = RetryManager("test_user")

        assert manager.failure_classifier is not None
        assert manager.status_tracker is not None


class TestShouldRetryResource:
    """Tests for should_retry_resource method"""

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_should_retry_resource_eligible(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test resource that can be retried"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (True, None)
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        decision = manager.should_retry_resource(1)

        assert decision.can_retry is True
        assert decision.reason is None

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_should_retry_resource_not_eligible(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test resource that cannot be retried"""
        mock_tracker = MagicMock()
        mock_tracker.can_retry.return_value = (False, "Permanently failed")
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        decision = manager.should_retry_resource(1)

        assert decision.can_retry is False
        assert decision.reason == "Permanently failed"


class TestResourceWithoutId:
    """Tests for handling resources without id attribute"""

    @patch(
        "local_deep_research.library.download_management.retry_manager.ResourceStatusTracker"
    )
    @patch(
        "local_deep_research.library.download_management.retry_manager.FailureClassifier"
    )
    def test_filter_skips_resources_without_id(
        self, mock_classifier, mock_tracker_cls
    ):
        """Test that resources without id are skipped"""
        mock_tracker = MagicMock()
        mock_tracker_cls.return_value = mock_tracker

        manager = RetryManager("test_user")

        # Create resources, some without id
        resources = [
            MagicMock(id=1),
            MagicMock(spec=[]),  # No id attribute
            MagicMock(id=3),
        ]

        # Mock can_retry for resources with id
        mock_tracker.can_retry.return_value = (True, None)

        results = manager.filter_resources(resources)

        # Only resources with id should be filtered
        assert len(results) == 2
        assert mock_tracker.can_retry.call_count == 2
