from __future__ import annotations

import pytest
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.db.models import Publication, PublicationIdentifier
from app.services.domains.arxiv import application as arxiv_service
from app.services.domains.publication_identifiers import application as identifier_service
from app.services.domains.publication_identifiers.types import IdentifierKind
from app.services.domains.publications.types import UnreadPublicationItem


def test_derive_display_identifier_prefers_doi_over_arxiv() -> None:
    display = identifier_service.derive_display_identifier_from_values(
        doi="10.1000/example",
        pub_url="https://arxiv.org/abs/1504.08025",
        pdf_url=None,
    )
    assert display is not None
    assert display.kind == "doi"
    assert display.value == "10.1000/example"
    assert display.url == "https://doi.org/10.1000/example"


def test_derive_display_identifier_uses_arxiv_when_doi_missing() -> None:
    display = identifier_service.derive_display_identifier_from_values(
        doi=None,
        pub_url="https://arxiv.org/pdf/1504.08025v2",
        pdf_url=None,
    )
    assert display is not None
    assert display.kind == "arxiv"
    assert display.value == "1504.08025v2"
    assert display.label == "arXiv: 1504.08025v2"


def test_derive_display_identifier_uses_pmcid_when_present() -> None:
    display = identifier_service.derive_display_identifier_from_values(
        doi=None,
        pub_url=None,
        pdf_url="https://pmc.ncbi.nlm.nih.gov/articles/PMC2175868/pdf/file.pdf",
    )
    assert display is not None
    assert display.kind == "pmcid"
    assert display.value == "PMC2175868"


def test_normalize_arxiv_id_handles_urls() -> None:
    from app.services.domains.publication_identifiers.normalize import normalize_arxiv_id

    assert normalize_arxiv_id("https://arxiv.org/abs/1504.08025") == "1504.08025"
    assert normalize_arxiv_id("http://arxiv.org/pdf/1504.08025v2.pdf") == "1504.08025v2"
    assert normalize_arxiv_id("https://arxiv.org/html/1504.08025v2") == "1504.08025v2"
    # Modern arxiv format
    assert normalize_arxiv_id("https://arxiv.org/abs/2012.00001") == "2012.00001"
    # Old arxiv format
    assert normalize_arxiv_id("https://arxiv.org/abs/math/9901123") == "math/9901123"


def test_normalize_arxiv_id_handles_raw_text() -> None:
    from app.services.domains.publication_identifiers.normalize import normalize_arxiv_id

    assert normalize_arxiv_id("1504.08025v1") == "1504.08025v1"
    assert normalize_arxiv_id("arXiv:1504.08025") == "1504.08025"
    assert normalize_arxiv_id("arxiv: 1504.08025v1") == "1504.08025v1"
    assert normalize_arxiv_id("Preprint at arXiv:math/9901123v2") == "math/9901123v2"
    assert normalize_arxiv_id("Not an arxiv: 123") is None


def test_normalize_pmcid_handles_urls_and_text() -> None:
    from app.services.domains.publication_identifiers.normalize import normalize_pmcid

    assert normalize_pmcid("https://pmc.ncbi.nlm.nih.gov/articles/PMC2175868/") == "PMC2175868"
    assert normalize_pmcid("http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1234567") == "PMC1234567"
    assert normalize_pmcid("PMCID: PMC1234567") == "PMC1234567"
    assert normalize_pmcid("pmc1234567") == "PMC1234567"
    assert normalize_pmcid("Not a PMCID 1234567") is None


def test_normalize_pmid_handles_urls() -> None:
    from app.services.domains.publication_identifiers.normalize import normalize_pmid

    assert normalize_pmid("https://pubmed.ncbi.nlm.nih.gov/12345678/") == "12345678"
    assert normalize_pmid("http://pubmed.ncbi.nlm.nih.gov/12345678") == "12345678"
    assert normalize_pmid("https://pubmed.ncbi.nlm.nih.gov/not-a-pmid/") is None


def _publication(*, title: str, pub_url: str | None = None) -> Publication:
    return Publication(
        cluster_id=None,
        fingerprint_sha256="f" * 64,
        title_raw=title,
        title_normalized=title.lower(),
        year=2024,
        citation_count=0,
        author_text=None,
        venue_text=None,
        pub_url=pub_url,
        pdf_url=None,
    )


@pytest.mark.asyncio
async def test_discover_and_sync_skips_arxiv_when_crossref_finds_doi(
    db_session: AsyncSession,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    publication = _publication(title="Reliable Paper Title for DOI discovery")
    db_session.add(publication)
    await db_session.flush()

    async def _fake_crossref(*, item):
        return "10.1000/strong-doi"

    async def _fail_arxiv(*, item, request_email=None, timeout_seconds=None):
        raise AssertionError("arXiv should be skipped after strong DOI evidence.")

    monkeypatch.setattr(
        "app.services.domains.crossref.application.discover_doi_for_publication",
        _fake_crossref,
    )
    monkeypatch.setattr(
        "app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
        _fail_arxiv,
    )

    await identifier_service.discover_and_sync_identifiers_for_publication(
        db_session,
        publication=publication,
        scholar_label="Ada Lovelace",
    )

    result = await db_session.execute(
        select(PublicationIdentifier).where(
            PublicationIdentifier.publication_id == int(publication.id),
            PublicationIdentifier.kind == IdentifierKind.DOI.value,
        )
    )
    assert result.scalar_one_or_none() is not None


@pytest.mark.asyncio
async def test_discover_and_sync_skips_arxiv_for_low_quality_title(
    db_session: AsyncSession,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    publication = _publication(title="AI 2024")
    db_session.add(publication)
    await db_session.flush()
    calls = {"count": 0}

    async def _fake_crossref(*, item):
        return None

    async def _fake_arxiv(*, item, request_email=None, timeout_seconds=None):
        calls["count"] += 1
        return "1234.5678"

    monkeypatch.setattr(
        "app.services.domains.crossref.application.discover_doi_for_publication",
        _fake_crossref,
    )
    monkeypatch.setattr(
        "app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
        _fake_arxiv,
    )

    await identifier_service.discover_and_sync_identifiers_for_publication(
        db_session,
        publication=publication,
        scholar_label="Alan Turing",
    )
    assert calls["count"] == 0


@pytest.mark.asyncio
async def test_discover_and_sync_calls_arxiv_when_item_is_eligible(
    db_session: AsyncSession,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    publication = _publication(title="Neural Representation Learning for Graph Signals")
    db_session.add(publication)
    await db_session.flush()

    async def _fake_crossref(*, item):
        return None

    async def _fake_arxiv(*, item, request_email=None, timeout_seconds=None):
        return "2501.00001v2"

    monkeypatch.setattr(
        "app.services.domains.crossref.application.discover_doi_for_publication",
        _fake_crossref,
    )
    monkeypatch.setattr(
        "app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
        _fake_arxiv,
    )

    await identifier_service.discover_and_sync_identifiers_for_publication(
        db_session,
        publication=publication,
        scholar_label="Grace Hopper",
    )

    result = await db_session.execute(
        select(PublicationIdentifier).where(
            PublicationIdentifier.publication_id == int(publication.id),
            PublicationIdentifier.kind == IdentifierKind.ARXIV.value,
        )
    )
    identifier = result.scalar_one_or_none()
    assert identifier is not None
    assert identifier.value_normalized == "2501.00001v2"


@pytest.mark.asyncio
async def test_discover_arxiv_id_returns_none_if_no_title() -> None:
    item = UnreadPublicationItem(
        publication_id=1,
        scholar_profile_id=1,
        scholar_label="First Last",
        title="",
        year=2023,
        citation_count=0,
        venue_text=None,
        pub_url=None,
        pdf_url=None,
    )
    result = await arxiv_service.discover_arxiv_id_for_publication(item=item)
    assert result is None


@pytest.mark.asyncio
async def test_build_arxiv_query() -> None:
    query = arxiv_service._build_arxiv_query("Super AI Model", "Smith")
    assert query == 'ti:"Super AI Model" AND au:"Smith"'

    query2 = arxiv_service._build_arxiv_query("Only Title Here", None)
    assert query2 == 'ti:"Only Title Here"'
