"""B-3 논문 서지 holder 공유 dedup 조회.

모든 논문 수집기(OpenAlex/arXiv/KoreaScience/J-STAGE)·reconcile·구매 PDF 스탬프가
ingest 전 이 함수로 holder 존재를 확인한다(있으면 skip 또는 child 링크).

- 조회 키 = lower(extract_meta #>> '{paper,doi}') == normalize_doi(...) — 라이브 partial-unique
  인덱스 uq_documents_paper_doi 와 동일 식(인덱스 사용).
- .scalars().first() — 교차게시·다중 landing-page 로 2행 이상 매칭 시 MultipleResultsFound
  raise 방지(scalar_one_or_none 금지, 2026-06 BBC 수집 중단 선례 / news_collector 동일 규율).
- 서지 holder Document 의 **생성**은 각 수집기/스탬프 경로가 소유한다(초록 signal 문서 vs 구매
  최소 holder 로 shape 가 다름). 이 모듈은 dedup 조회만 공유한다.

DB 조회라 본 모듈은 PR2(arXiv 실수집)에서 라이브 검증한다 — PR1 단위 테스트 대상은 doi.py(순수).
"""

from sqlalchemy import func, select

from models.document import Document
from services.papers.doi import normalize_doi

# 인덱스 식과 동일: lower(extract_meta #>> '{paper,doi}')
_DOI_EXPR = func.lower(Document.extract_meta[("paper", "doi")].astext)


async def find_paper_holder(session, raw_or_normalized_doi):
    """정규화 DOI 로 서지 holder Document 조회. 없으면 None.

    인자는 raw 든 정규화든 받아 normalize_doi 로 통일(저장=조회 동일 함수 보장).
    """
    doi = normalize_doi(raw_or_normalized_doi)
    if not doi:
        return None
    result = await session.execute(
        select(Document)
        .where(Document.material_type == "paper", _DOI_EXPR == doi,
               Document.deleted_at.is_(None))
        .limit(1)
    )
    return result.scalars().first()
