"""TDD tests for axm_bib.clients.unpaywall. Written BEFORE implementation."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import httpx
import pytest

from axm_bib.clients.unpaywall import UnpaywallClient, UnpaywallError
from tests.conftest import (
    SAMPLE_DOI,
    SAMPLE_UNPAYWALL_CLOSED,
    SAMPLE_UNPAYWALL_RESPONSE,
)


def _ok_response(json_data: dict) -> MagicMock:  # type: ignore[type-arg]
    """Build a mock 200 Response."""
    resp = MagicMock(spec=httpx.Response)
    resp.status_code = 200
    resp.json.return_value = json_data
    resp.headers = {}
    return resp


class TestGetPdfUrls:
    """UnpaywallClient.get_pdf_urls() tests."""

    @patch("axm_bib.http.httpx.request")
    def test_returns_all_urls(self, mock_request: MagicMock) -> None:
        """Returns all OA locations' URLs."""
        response = {
            "doi": SAMPLE_DOI,
            "is_oa": True,
            "oa_locations": [
                {
                    "url_for_pdf": "https://dl.acm.org/pdf/test.pdf",
                    "url": "https://dl.acm.org/doi/test",
                    "host_type": "publisher",
                },
                {
                    "url_for_pdf": "https://arxiv.org/pdf/1234.pdf",
                    "url": "https://arxiv.org/abs/1234",
                    "host_type": "repository",
                },
            ],
        }
        mock_request.return_value = _ok_response(response)

        client = UnpaywallClient(email="test@example.com")
        urls = client.get_pdf_urls(SAMPLE_DOI)
        assert len(urls) == 2

    @patch("axm_bib.http.httpx.request")
    def test_repos_before_publishers(self, mock_request: MagicMock) -> None:
        """Repository URLs come before publisher URLs."""
        response = {
            "doi": SAMPLE_DOI,
            "is_oa": True,
            "oa_locations": [
                {
                    "url_for_pdf": "https://publisher.com/pdf",
                    "host_type": "publisher",
                },
                {
                    "url_for_pdf": "https://arxiv.org/pdf/1234.pdf",
                    "host_type": "repository",
                },
            ],
        }
        mock_request.return_value = _ok_response(response)

        client = UnpaywallClient(email="test@example.com")
        urls = client.get_pdf_urls(SAMPLE_DOI)
        assert urls[0] == "https://arxiv.org/pdf/1234.pdf"
        assert urls[1] == "https://publisher.com/pdf"

    @patch("axm_bib.http.httpx.request")
    def test_prefers_url_for_pdf(self, mock_request: MagicMock) -> None:
        """url_for_pdf is preferred over url when both exist."""
        response = {
            "doi": SAMPLE_DOI,
            "is_oa": True,
            "oa_locations": [
                {
                    "url_for_pdf": "https://direct.pdf",
                    "url": "https://landing.page",
                    "host_type": "repository",
                },
            ],
        }
        mock_request.return_value = _ok_response(response)

        client = UnpaywallClient(email="test@example.com")
        urls = client.get_pdf_urls(SAMPLE_DOI)
        assert urls == ["https://direct.pdf"]

    @patch("axm_bib.http.httpx.request")
    def test_closed_access_returns_empty(self, mock_request: MagicMock) -> None:
        """Closed-access paper → empty list."""
        mock_request.return_value = _ok_response(SAMPLE_UNPAYWALL_CLOSED)

        client = UnpaywallClient(email="test@example.com")
        urls = client.get_pdf_urls("10.1234/closed.5678")
        assert urls == []

    @patch("axm_bib.http.httpx.request")
    def test_doi_not_found_returns_empty(self, mock_request: MagicMock) -> None:
        """Unknown DOI → empty list, no error (404 is non-retryable)."""
        mock_resp = MagicMock(spec=httpx.Response)
        mock_resp.status_code = 404
        mock_resp.headers = {}
        mock_resp.raise_for_status.side_effect = httpx.HTTPStatusError(
            "404",
            request=MagicMock(),
            response=mock_resp,
        )
        # request_with_retry sees 404 (not in retry_on) and raises immediately
        mock_request.return_value = mock_resp

        client = UnpaywallClient(email="test@example.com")
        urls = client.get_pdf_urls("10.9999/unknown")
        assert urls == []

    @patch("axm_bib.http.httpx.request")
    def test_network_error_raises(self, mock_request: MagicMock) -> None:
        """Connection failure raises UnpaywallError."""
        mock_request.side_effect = ConnectionError("Network unreachable")

        client = UnpaywallClient(email="test@example.com")
        with pytest.raises(UnpaywallError):
            client.get_pdf_urls(SAMPLE_DOI)

    def test_missing_email_raises(self) -> None:
        """No email → UnpaywallError at init."""
        with patch("axm_bib.config.get_email", return_value=None):
            with pytest.raises(UnpaywallError, match="real email"):
                UnpaywallClient(email="")

    @patch("axm_bib.http.httpx.request")
    def test_422_raises_not_silent(self, mock_request: MagicMock) -> None:
        """HTTP 422 (bad email) raises error, not silent empty list."""
        mock_resp = MagicMock(spec=httpx.Response)
        mock_resp.status_code = 422
        mock_resp.headers = {}
        mock_resp.raise_for_status.side_effect = httpx.HTTPStatusError(
            "422",
            request=MagicMock(),
            response=mock_resp,
        )
        # request_with_retry sees 422 (not in retry_on) and raises immediately
        mock_request.return_value = mock_resp

        client = UnpaywallClient(email="test@example.com")
        with pytest.raises(UnpaywallError, match="422"):
            client.get_pdf_urls(SAMPLE_DOI)

    @patch("axm_bib.http.httpx.request")
    def test_single_oa_with_old_response_shape(self, mock_request: MagicMock) -> None:
        """Works with original SAMPLE_UNPAYWALL_RESPONSE fixture."""
        mock_request.return_value = _ok_response(SAMPLE_UNPAYWALL_RESPONSE)

        client = UnpaywallClient(email="test@example.com")
        urls = client.get_pdf_urls(SAMPLE_DOI)
        assert len(urls) > 0
