"""
Batch DOI → OpenAlex source_id enrichment.

Resolves paper DOIs to OpenAlex source IDs in a single batch HTTP request
(up to 50 DOIs per call). This populates ``openalex_source_id`` and
``source_type`` on result dicts so the journal reputation filter can look
up journals by ID rather than fragile name matching.

Runs as a pre-enrichment layer before content filters — the existing
tiered scoring system is unchanged. This just gives Tier 2 (OpenAlex
snapshot lookup) a reliable key to work with.
"""

from typing import Any, Dict, List, Optional

from loguru import logger

from ..constants import OPENALEX_ENRICHMENT_API_TIMEOUT, USER_AGENT
from ..security.safe_requests import safe_get
from .citation_normalizer import _extract_doi


_OPENALEX_API = "https://api.openalex.org"
_MAX_DOIS_PER_REQUEST = 50


def _normalize_doi(doi: str) -> str:
    """Normalize a DOI to ``https://doi.org/<...>`` form for OpenAlex.

    The ``startswith`` prefix checks below are fully anchored and
    CodeQL-safe. A previous code-scanning bot comment cited alert 7635
    (``py/incomplete-url-substring-sanitization``) against an earlier
    snapshot of this file; the current CodeQL scan does not raise it,
    and the anchored ``startswith`` pattern is the rule's recommended
    mitigation. Refactoring to a bare-first normalization was
    evaluated (PR #3081) but rejected as no-op churn — the
    ``https://doi.org/`` form OpenAlex actually returns round-trips
    unchanged through every branch here. Do not refactor without a
    new, reproducible functional issue.
    """
    doi = doi.strip()
    if doi.startswith("https://doi.org/"):
        return doi
    if doi.startswith("http://doi.org/"):
        return doi.replace("http://", "https://")
    if doi.startswith("10."):
        return f"https://doi.org/{doi}"
    return doi


def enrich_results_with_source_ids(
    results: List[Dict[str, Any]],
    email: Optional[str] = None,
) -> List[Dict[str, Any]]:
    """
    Batch-enrich results with OpenAlex source_id by DOI lookup.

    For results that have a DOI but no ``openalex_source_id``, makes a
    single batch request to the OpenAlex works endpoint to resolve
    DOI → journal/conference source_id.

    The results list is modified in-place and also returned.

    Args:
        results: List of result dicts from search engines.
        email: Optional email for OpenAlex polite pool.

    Returns:
        The same results list, with ``openalex_source_id`` and
        ``source_type`` injected where resolved.
    """
    if not results:
        return results

    # Collect DOIs for results that need enrichment
    doi_to_indices: Dict[str, List[int]] = {}
    for i, result in enumerate(results):
        # Skip results that already have a source_id
        if result.get("openalex_source_id"):
            continue

        doi = _extract_doi(result)
        if doi:
            normalized = _normalize_doi(doi)
            doi_to_indices.setdefault(normalized, []).append(i)

    if not doi_to_indices:
        logger.debug("DOI enrichment: no DOIs to resolve")
        return results

    # Batch DOIs into chunks of MAX_DOIS_PER_REQUEST
    all_dois = list(doi_to_indices.keys())
    enriched_count = 0

    for chunk_start in range(0, len(all_dois), _MAX_DOIS_PER_REQUEST):
        chunk = all_dois[chunk_start : chunk_start + _MAX_DOIS_PER_REQUEST]
        doi_filter = "|".join(chunk)

        params = {
            "filter": f"doi:{doi_filter}",
            "per_page": str(len(chunk)),
            "select": "doi,primary_location",
        }
        if email:
            params["mailto"] = email

        # safe_get auto-injects the project User-Agent. We only override
        # it here when an email is configured so OpenAlex's polite pool
        # can identify us. The mailto query param above also achieves
        # the polite-pool effect on its own.
        headers: Dict[str, str] = {"Accept": "application/json"}
        if email:
            headers["User-Agent"] = f"{USER_AGENT} ({email})"

        try:
            response = safe_get(
                f"{_OPENALEX_API}/works",
                params=params,
                headers=headers,
                timeout=OPENALEX_ENRICHMENT_API_TIMEOUT,
            )
            if response.status_code != 200:
                logger.warning(
                    f"DOI enrichment: OpenAlex returned {response.status_code}"
                )
                continue

            data = response.json()
            works = data.get("results", [])

            for work in works:
                work_doi = work.get("doi", "")
                if not work_doi:
                    continue

                # Normalize for matching
                work_doi_normalized = _normalize_doi(work_doi)

                # Extract source info
                location = work.get("primary_location") or {}
                source = location.get("source") or {}
                source_id_raw = source.get("id", "")
                source_type = source.get("type")

                if not source_id_raw:
                    continue

                # Extract short ID from URL
                source_id = source_id_raw.split("/")[-1]

                # Apply to all results with this DOI
                indices = doi_to_indices.get(work_doi_normalized, [])
                for idx in indices:
                    results[idx]["openalex_source_id"] = source_id
                    if source_type:
                        results[idx]["source_type"] = source_type
                    enriched_count += 1

        except Exception:
            logger.exception("DOI enrichment: OpenAlex batch lookup failed")
            # Graceful: results pass through unenriched
            continue

    if enriched_count > 0:
        logger.info(
            f"DOI enrichment: resolved {enriched_count} of "
            f"{len(doi_to_indices)} DOIs to OpenAlex source IDs"
        )
    else:
        logger.debug(
            f"DOI enrichment: no matches from {len(doi_to_indices)} DOIs"
        )

    return results
