"""High-value tests for library/download_management/failure_classifier.py."""

from datetime import timedelta, datetime


from local_deep_research.library.download_management.failure_classifier import (
    PermanentFailure,
    TemporaryFailure,
    RateLimitFailure,
    FailureClassifier,
)


# ---------------------------------------------------------------------------
# BaseFailure / PermanentFailure
# ---------------------------------------------------------------------------


class TestPermanentFailure:
    """PermanentFailure behavior."""

    def test_is_permanent(self):
        f = PermanentFailure("not_found", "Gone")
        assert f.is_permanent() is True

    def test_can_never_retry(self):
        f = PermanentFailure("not_found", "Gone")
        assert f.can_retry_now() is False

    def test_cooldown_remaining_is_none(self):
        f = PermanentFailure("forbidden", "No access")
        assert f.get_cooldown_remaining() is None

    def test_to_dict_fields(self):
        f = PermanentFailure("not_found", "Resource not found")
        d = f.to_dict()
        assert d["error_type"] == "not_found"
        assert d["message"] == "Resource not found"
        assert d["is_permanent"] is True
        assert d["retry_after_timestamp"] is None
        assert isinstance(d["created_at"], datetime)

    def test_error_type_stored(self):
        f = PermanentFailure("gone", "Permanently removed")
        assert f.error_type == "gone"

    def test_message_stored(self):
        f = PermanentFailure("gone", "Permanently removed")
        assert f.message == "Permanently removed"


# ---------------------------------------------------------------------------
# TemporaryFailure
# ---------------------------------------------------------------------------


class TestTemporaryFailure:
    """TemporaryFailure with cooldown."""

    def test_not_permanent(self):
        f = TemporaryFailure("timeout", "Timed out", timedelta(minutes=30))
        assert f.is_permanent() is False

    def test_cannot_retry_immediately(self):
        f = TemporaryFailure("timeout", "Timed out", timedelta(hours=1))
        assert f.can_retry_now() is False

    def test_can_retry_after_cooldown(self):
        f = TemporaryFailure("timeout", "Timed out", timedelta(seconds=0))
        assert f.can_retry_now() is True

    def test_cooldown_remaining_positive(self):
        f = TemporaryFailure("error", "err", timedelta(hours=2))
        remaining = f.get_cooldown_remaining()
        assert remaining is not None
        assert remaining > timedelta(0)

    def test_cooldown_remaining_none_after_expiry(self):
        f = TemporaryFailure("error", "err", timedelta(seconds=0))
        assert f.get_cooldown_remaining() is None

    def test_to_dict_has_retry_timestamp(self):
        f = TemporaryFailure("timeout", "t", timedelta(hours=1))
        d = f.to_dict()
        assert d["is_permanent"] is False
        assert d["retry_after_timestamp"] is not None
        assert isinstance(d["retry_after_timestamp"], datetime)

    def test_retry_after_stored(self):
        cooldown = timedelta(minutes=30)
        f = TemporaryFailure("timeout", "t", cooldown)
        assert f.retry_after == cooldown


# ---------------------------------------------------------------------------
# RateLimitFailure
# ---------------------------------------------------------------------------


class TestRateLimitFailure:
    """Domain-specific rate limit cooldowns."""

    def test_arxiv_6h_cooldown(self):
        f = RateLimitFailure("arxiv.org")
        assert f.retry_after == timedelta(hours=6)

    def test_pubmed_2h_cooldown(self):
        f = RateLimitFailure("pubmed.ncbi.nlm.nih.gov")
        assert f.retry_after == timedelta(hours=2)

    def test_biorxiv_6h_cooldown(self):
        f = RateLimitFailure("biorxiv.org")
        assert f.retry_after == timedelta(hours=6)

    def test_semantic_scholar_4h_cooldown(self):
        f = RateLimitFailure("semanticscholar.org")
        assert f.retry_after == timedelta(hours=4)

    def test_researchgate_12h_cooldown(self):
        f = RateLimitFailure("researchgate.net")
        assert f.retry_after == timedelta(hours=12)

    def test_unknown_domain_1h_default(self):
        f = RateLimitFailure("random-site.com")
        assert f.retry_after == timedelta(hours=1)

    def test_error_type_is_rate_limited(self):
        f = RateLimitFailure("arxiv.org")
        assert f.error_type == "rate_limited"

    def test_domain_stored(self):
        f = RateLimitFailure("arxiv.org")
        assert f.domain == "arxiv.org"

    def test_details_in_message(self):
        f = RateLimitFailure("arxiv.org", "Too many requests")
        assert "Too many requests" in f.message

    def test_is_not_permanent(self):
        f = RateLimitFailure("arxiv.org")
        assert f.is_permanent() is False


# ---------------------------------------------------------------------------
# FailureClassifier.classify_failure()
# ---------------------------------------------------------------------------


class TestClassifyFailureHTTPStatus:
    """HTTP status code classification."""

    def setup_method(self):
        self.classifier = FailureClassifier()

    def test_404_permanent(self):
        f = self.classifier.classify_failure("http_error", status_code=404)
        assert isinstance(f, PermanentFailure)
        assert f.error_type == "not_found"

    def test_403_permanent(self):
        f = self.classifier.classify_failure("http_error", status_code=403)
        assert isinstance(f, PermanentFailure)
        assert f.error_type == "forbidden"

    def test_410_permanent(self):
        f = self.classifier.classify_failure("http_error", status_code=410)
        assert isinstance(f, PermanentFailure)
        assert f.error_type == "gone"

    def test_429_rate_limit(self):
        f = self.classifier.classify_failure(
            "http_error", status_code=429, url="https://arxiv.org/pdf/1234"
        )
        assert isinstance(f, RateLimitFailure)
        assert f.domain == "arxiv.org"

    def test_503_temporary(self):
        f = self.classifier.classify_failure("http_error", status_code=503)
        assert isinstance(f, TemporaryFailure)
        assert f.error_type == "server_error"


class TestClassifyFailurePatterns:
    """Error message pattern classification."""

    def setup_method(self):
        self.classifier = FailureClassifier()

    def test_arxiv_recaptcha_3_day_cooldown(self):
        f = self.classifier.classify_failure(
            "arxiv_error", details="reCAPTCHA challenge detected"
        )
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(days=3)

    def test_timeout_30_min_cooldown(self):
        f = self.classifier.classify_failure("timeout")
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(minutes=30)

    def test_network_error_5_min_cooldown(self):
        f = self.classifier.classify_failure("network_error")
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(minutes=5)

    def test_connection_error_5_min(self):
        f = self.classifier.classify_failure("connection_error")
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(minutes=5)

    def test_unknown_defaults_to_1h_temporary(self):
        f = self.classifier.classify_failure("something_weird")
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(hours=1)

    def test_arxiv_not_pdf_permanent(self):
        f = self.classifier.classify_failure(
            "arxiv_error", details="Not a PDF file"
        )
        assert isinstance(f, PermanentFailure)

    def test_arxiv_html_instead_of_pdf_permanent(self):
        f = self.classifier.classify_failure(
            "arxiv_error", details="Received HTML content instead of PDF"
        )
        assert isinstance(f, PermanentFailure)
        assert f.error_type == "incompatible_format"

    def test_429_empty_url_unknown_domain(self):
        f = self.classifier.classify_failure(
            "http_error", status_code=429, url=""
        )
        assert isinstance(f, RateLimitFailure)
        assert f.domain == "unknown"
        assert f.retry_after == timedelta(hours=1)

    def test_arxiv_html_with_pdf_content_type_not_permanent(self):
        """The application/pdf guard prevents HTML detection from firing."""
        f = self.classifier.classify_failure(
            "arxiv_error", details="html content; application/pdf"
        )
        assert not isinstance(f, PermanentFailure)

    def test_500_default_temporary(self):
        f = self.classifier.classify_failure("http_error", status_code=500)
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(hours=1)


# ---------------------------------------------------------------------------
# classify_from_exception()
# ---------------------------------------------------------------------------


class TestClassifyFromException:
    """Exception-based classification."""

    def setup_method(self):
        self.classifier = FailureClassifier()

    def test_timeout_exception(self):
        exc = TimeoutError("Request timed out")
        f = self.classifier.classify_from_exception(exc)
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(minutes=30)

    def test_connection_error(self):
        exc = ConnectionError("Connection refused")
        f = self.classifier.classify_from_exception(exc)
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(minutes=5)

    def test_generic_exception_unknown(self):
        exc = RuntimeError("Something broke")
        f = self.classifier.classify_from_exception(exc)
        assert isinstance(f, TemporaryFailure)
        assert f.retry_after == timedelta(hours=1)
