"""DOAJ (Directory of Open Access Journals) data source.

Downloads the **public CSV dump** of all DOAJ journals from
``https://doaj.org/csv`` — a single HTTP GET, no auth, no rate
limits, ~25 MB, ~22K journals. This replaces the previous paginated
``/api/search/journals`` implementation, which required hundreds of
requests with polite sleeps between them.
"""

from __future__ import annotations

import csv
import io
import json
import time
from pathlib import Path

from loguru import logger

from ...utilities.citation_normalizer import normalize_issn
from .base import DataSource

# Public CSV of the full DOAJ journal list. CC0 metadata.
_DOAJ_CSV_URL = "https://doaj.org/csv"

# Column headers in the DOAJ public CSV (as of the current schema).
# DOAJ has historically been stable about these but we look them up
# by header name so a column reorder doesn't break us.
_COL_TITLE = "Journal title"
_COL_PISSN = "Journal ISSN (print version)"
_COL_EISSN = "Journal EISSN (online version)"
_COL_SEAL = "DOAJ Seal"
_COL_PUBLISHER = "Publisher"

# Safety floor — DOAJ has ~22K journals. A fetch that returns far fewer
# records almost certainly indicates a schema change upstream (e.g.
# column rename breaking ISSN lookups) and should NOT overwrite the
# existing good data file.
_MIN_DOAJ_JOURNALS = 5_000


class DOAJSource(DataSource):
    key = "doaj"  # gitleaks:allow
    name = "Directory of Open Access Journals"
    url = "https://doaj.org"
    dataset_url = "https://doaj.org/docs/public-data-dump"
    license = "CC0 (metadata)"
    license_url = "https://creativecommons.org/publicdomain/zero/1.0/"
    description = "~22K verified open access journals with DOAJ Seal status"
    filename = "doaj_journals.json"
    count_label = "DOAJ journals"
    auto_download = False
    required = False  # best-effort
    approx_size_mb = 5.0

    def fetch(self, data_dir: Path, progress_cb=None) -> int:
        from ...security.safe_requests import (
            safe_get_with_retries as safe_get,
        )

        logger.info(f"Fetching DOAJ public CSV dump: {_DOAJ_CSV_URL}")
        start = time.time()
        # consume_body: the CSV is ~25 MB, so a mid-stream
        # ChunkedEncodingError / ReadTimeout is a realistic failure
        # mode worth retrying. Without this flag the body-read fires
        # outside safe_get_with_retries' retry loop.
        resp = safe_get(_DOAJ_CSV_URL, timeout=120, consume_body=True)
        resp.raise_for_status()

        # DOAJ serves UTF-8 CSV. Parse in-memory — the whole file is
        # ~25 MB and we need random column access.
        text = resp.content.decode("utf-8", errors="replace")
        reader = csv.DictReader(io.StringIO(text))

        journals: dict = {}
        for row in reader:
            # Prefer print ISSN, fall back to electronic. The CSV uses
            # empty strings for missing values. Normalize to the
            # 8-char no-dash canonical form so lookups (which also
            # normalize) match regardless of the upstream format.
            raw_issn = (row.get(_COL_PISSN) or "").strip() or (
                row.get(_COL_EISSN) or ""
            ).strip()
            issn = normalize_issn(raw_issn)
            if not issn:
                continue

            # DOAJ's seal column is actually ternary in the CSV: "yes",
            # "no", or blank (never applied). The current consumer
            # (scoring.py) only needs the boolean floor, so we collapse
            # blank and "no" into False here. If a future scoring tier
            # wants to distinguish "applied and was denied" from "never
            # applied", preserve the raw value in a new dict key and
            # plumb it through the Source ORM.
            seal_raw = (row.get(_COL_SEAL) or "").strip().lower()
            journals[issn] = {
                "name": (row.get(_COL_TITLE) or "").strip(),
                "has_seal": seal_raw in ("yes", "true", "1"),
                "publisher": (row.get(_COL_PUBLISHER) or "").strip(),
            }

        if len(journals) < _MIN_DOAJ_JOURNALS:
            raise RuntimeError(
                f"DOAJ: suspiciously few journals "
                f"({len(journals):,} < {_MIN_DOAJ_JOURNALS:,}); "
                "refusing to overwrite existing data. "
                "Possible CSV schema change upstream."
            )

        output = data_dir / self.filename
        tmp = data_dir / f"{self.filename}.tmp"
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump({"journals": journals}, f)
        tmp.rename(output)

        elapsed = time.time() - start
        logger.info(f"DOAJ: saved {len(journals):,} journals in {elapsed:.0f}s")
        return len(journals)
