"""Tests for library/download_management/failure_classifier.py."""

from datetime import timedelta, datetime, UTC


class TestBaseFailure:
    """Tests for BaseFailure class."""

    def test_stores_error_type(self):
        """Test that error_type is stored."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        # Create a concrete subclass for testing
        class TestFailure(BaseFailure):
            pass

        failure = TestFailure("test_error", "Test message", timedelta(hours=1))
        assert failure.error_type == "test_error"

    def test_stores_message(self):
        """Test that message is stored."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        failure = TestFailure("error", "Custom message", timedelta(hours=1))
        assert failure.message == "Custom message"

    def test_stores_retry_after(self):
        """Test that retry_after is stored."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        retry = timedelta(hours=2)
        failure = TestFailure("error", "message", retry)
        assert failure.retry_after == retry

    def test_sets_created_at(self):
        """Test that created_at is set to current time."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        before = datetime.now(UTC)
        failure = TestFailure("error", "message", timedelta(hours=1))
        after = datetime.now(UTC)

        assert before <= failure.created_at <= after

    def test_is_permanent_returns_true_when_no_retry(self):
        """Test that is_permanent returns True when retry_after is None."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        failure = TestFailure("error", "message", None)
        assert failure.is_permanent() is True

    def test_is_permanent_returns_false_when_has_retry(self):
        """Test that is_permanent returns False when retry_after is set."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        failure = TestFailure("error", "message", timedelta(hours=1))
        assert failure.is_permanent() is False

    def test_can_retry_now_returns_false_for_permanent(self):
        """Test that can_retry_now returns False for permanent failures."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        failure = TestFailure("error", "message", None)
        assert failure.can_retry_now() is False

    def test_can_retry_now_returns_false_during_cooldown(self):
        """Test that can_retry_now returns False during cooldown."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        # Create failure with 1 hour cooldown
        failure = TestFailure("error", "message", timedelta(hours=1))
        assert failure.can_retry_now() is False

    def test_to_dict_returns_expected_keys(self):
        """Test that to_dict returns expected dictionary keys."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        failure = TestFailure("test_error", "Test message", timedelta(hours=1))
        result = failure.to_dict()

        assert "error_type" in result
        assert "message" in result
        assert "retry_after_timestamp" in result
        assert "is_permanent" in result
        assert "created_at" in result

    def test_to_dict_values_are_correct(self):
        """Test that to_dict values are correct."""
        from local_deep_research.library.download_management.failure_classifier import (
            BaseFailure,
        )

        class TestFailure(BaseFailure):
            pass

        failure = TestFailure("my_error", "My message", timedelta(hours=2))
        result = failure.to_dict()

        assert result["error_type"] == "my_error"
        assert result["message"] == "My message"
        assert result["is_permanent"] is False


class TestPermanentFailure:
    """Tests for PermanentFailure class."""

    def test_is_always_permanent(self):
        """Test that PermanentFailure is always permanent."""
        from local_deep_research.library.download_management.failure_classifier import (
            PermanentFailure,
        )

        failure = PermanentFailure("not_found", "Resource not found")
        assert failure.is_permanent() is True

    def test_retry_after_is_none(self):
        """Test that retry_after is always None."""
        from local_deep_research.library.download_management.failure_classifier import (
            PermanentFailure,
        )

        failure = PermanentFailure("forbidden", "Access forbidden")
        assert failure.retry_after is None

    def test_can_never_retry(self):
        """Test that can_retry_now always returns False."""
        from local_deep_research.library.download_management.failure_classifier import (
            PermanentFailure,
        )

        failure = PermanentFailure("gone", "Resource gone")
        assert failure.can_retry_now() is False


class TestTemporaryFailure:
    """Tests for TemporaryFailure class."""

    def test_is_not_permanent(self):
        """Test that TemporaryFailure is not permanent."""
        from local_deep_research.library.download_management.failure_classifier import (
            TemporaryFailure,
        )

        failure = TemporaryFailure(
            "timeout", "Request timed out", timedelta(minutes=30)
        )
        assert failure.is_permanent() is False

    def test_stores_cooldown(self):
        """Test that cooldown is stored as retry_after."""
        from local_deep_research.library.download_management.failure_classifier import (
            TemporaryFailure,
        )

        cooldown = timedelta(hours=2)
        failure = TemporaryFailure("error", "message", cooldown)
        assert failure.retry_after == cooldown


class TestRateLimitFailure:
    """Tests for RateLimitFailure class."""

    def test_stores_domain(self):
        """Test that domain is stored."""
        from local_deep_research.library.download_management.failure_classifier import (
            RateLimitFailure,
        )

        failure = RateLimitFailure("arxiv.org")
        assert failure.domain == "arxiv.org"

    def test_uses_domain_specific_cooldown_for_arxiv(self):
        """Test that arxiv.org gets 6 hour cooldown."""
        from local_deep_research.library.download_management.failure_classifier import (
            RateLimitFailure,
        )

        failure = RateLimitFailure("arxiv.org")
        assert failure.retry_after == timedelta(hours=6)

    def test_uses_domain_specific_cooldown_for_pubmed(self):
        """Test that pubmed gets 2 hour cooldown."""
        from local_deep_research.library.download_management.failure_classifier import (
            RateLimitFailure,
        )

        failure = RateLimitFailure("pubmed.ncbi.nlm.nih.gov")
        assert failure.retry_after == timedelta(hours=2)

    def test_uses_default_cooldown_for_unknown_domain(self):
        """Test that unknown domains get 1 hour cooldown."""
        from local_deep_research.library.download_management.failure_classifier import (
            RateLimitFailure,
        )

        failure = RateLimitFailure("unknown-domain.com")
        assert failure.retry_after == timedelta(hours=1)

    def test_includes_details_in_message(self):
        """Test that details are included in message."""
        from local_deep_research.library.download_management.failure_classifier import (
            RateLimitFailure,
        )

        failure = RateLimitFailure("example.com", "Too many requests")
        assert "Too many requests" in failure.message

    def test_error_type_is_rate_limited(self):
        """Test that error_type is rate_limited."""
        from local_deep_research.library.download_management.failure_classifier import (
            RateLimitFailure,
        )

        failure = RateLimitFailure("example.com")
        assert failure.error_type == "rate_limited"


class TestFailureClassifier:
    """Tests for FailureClassifier class."""

    def test_404_returns_permanent_failure(self):
        """Test that 404 status code returns permanent failure."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            PermanentFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure("error", status_code=404)

        assert isinstance(result, PermanentFailure)
        assert result.error_type == "not_found"

    def test_403_returns_permanent_failure(self):
        """Test that 403 status code returns permanent failure."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            PermanentFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure("error", status_code=403)

        assert isinstance(result, PermanentFailure)
        assert result.error_type == "forbidden"

    def test_410_returns_permanent_failure(self):
        """Test that 410 status code returns permanent failure."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            PermanentFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure("error", status_code=410)

        assert isinstance(result, PermanentFailure)
        assert result.error_type == "gone"

    def test_429_returns_rate_limit_failure(self):
        """Test that 429 status code returns rate limit failure."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            RateLimitFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure(
            "error", status_code=429, url="https://example.com/resource"
        )

        assert isinstance(result, RateLimitFailure)

    def test_503_returns_temporary_failure(self):
        """Test that 503 status code returns temporary failure."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            TemporaryFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure("error", status_code=503)

        assert isinstance(result, TemporaryFailure)
        assert result.error_type == "server_error"

    def test_recaptcha_in_details_returns_temporary_failure(self):
        """Test that reCAPTCHA in details returns 3-day cooldown."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            TemporaryFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure(
            "arxiv_error", details="reCAPTCHA verification required"
        )

        assert isinstance(result, TemporaryFailure)
        assert result.retry_after == timedelta(days=3)

    def test_timeout_returns_temporary_failure(self):
        """Test that timeout error returns temporary failure."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            TemporaryFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure(
            "TimeoutError", details="Request timed out"
        )

        assert isinstance(result, TemporaryFailure)
        assert result.error_type == "timeout"
        assert result.retry_after == timedelta(minutes=30)

    def test_network_error_returns_temporary_failure(self):
        """Test that network error returns temporary failure."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            TemporaryFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure(
            "ConnectionError", details="Network unreachable"
        )

        assert isinstance(result, TemporaryFailure)
        assert result.error_type == "network_error"

    def test_unknown_error_returns_temporary_failure(self):
        """Test that unknown error returns temporary failure with 1 hour cooldown."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
            TemporaryFailure,
        )

        classifier = FailureClassifier()
        result = classifier.classify_failure(
            "SomeRandomError", details="Something happened"
        )

        assert isinstance(result, TemporaryFailure)
        assert result.error_type == "unknown_error"
        assert result.retry_after == timedelta(hours=1)

    def test_classify_from_exception(self):
        """Test classify_from_exception method."""
        from local_deep_research.library.download_management.failure_classifier import (
            FailureClassifier,
        )

        classifier = FailureClassifier()
        exception = TimeoutError("Connection timed out")

        result = classifier.classify_from_exception(exception)

        assert result.error_type == "timeout"


class TestGetCooldownRemaining:
    """Tests for get_cooldown_remaining method."""

    def test_returns_none_for_permanent_failure(self):
        """Test that None is returned for permanent failures."""
        from local_deep_research.library.download_management.failure_classifier import (
            PermanentFailure,
        )

        failure = PermanentFailure("error", "message")
        assert failure.get_cooldown_remaining() is None

    def test_returns_remaining_time_during_cooldown(self):
        """Test that remaining time is returned during cooldown."""
        from local_deep_research.library.download_management.failure_classifier import (
            TemporaryFailure,
        )

        # Create failure with 1 hour cooldown
        failure = TemporaryFailure("error", "message", timedelta(hours=1))

        remaining = failure.get_cooldown_remaining()
        assert remaining is not None
        assert remaining.total_seconds() > 0
        assert remaining <= timedelta(hours=1)

    def test_returns_none_after_cooldown_expires(self):
        """Test that None is returned after cooldown expires."""
        from local_deep_research.library.download_management.failure_classifier import (
            TemporaryFailure,
        )

        # Create failure with very short cooldown
        failure = TemporaryFailure(
            "error", "message", timedelta(microseconds=1)
        )

        # Wait briefly for cooldown to expire
        import time

        time.sleep(0.001)

        remaining = failure.get_cooldown_remaining()
        assert remaining is None
