"""Stop Predatory Journals data source.

Community successor to Jeffrey Beall's original predatory publishers
list (Beall took down his original blog post in 2017). The successor
project maintains three CSV files (publishers, journals, hijacked) on
GitHub which we merge into a single predatory.json.
"""

from __future__ import annotations

import csv
import io
import json
from pathlib import Path

from loguru import logger

from .base import DataSource

_PREDATORY_BASE = (
    "https://raw.githubusercontent.com/stop-predatory-journals/"
    "stop-predatory-journals.github.io/master/_data"
)
_PREDATORY_FILES = {
    "publishers": "publishers.csv",
    "journals": "journals.csv",
    "hijacked": "hijacked.csv",
}

# Safety floor — the upstream lists carry thousands of entries each.
# Refuse to overwrite a healthy on-disk snapshot with a near-empty
# payload (e.g. CDN partial outage where two of the three CSVs
# returned 0 rows) since that would silently disable predatory
# filtering for everyone.
_MIN_PREDATORY_TOTAL = 100


class PredatorySource(DataSource):
    key = "predatory"  # gitleaks:allow
    name = "Stop Predatory Journals"
    url = (
        "https://github.com/stop-predatory-journals/"
        "stop-predatory-journals.github.io"
    )
    dataset_url = (
        "https://github.com/stop-predatory-journals/"
        "stop-predatory-journals.github.io/tree/master/_data"
    )
    license = "MIT"
    license_url = "https://opensource.org/license/mit"
    description = (
        "Community successor to Beall's List — predatory publishers, "
        "journals, and hijacked journal entries"
    )
    filename = "predatory.json"
    count_label = "predatory entries"
    auto_download = True  # ~0.3 MB; fetch on first filter use
    required = False
    approx_size_mb = 0.3

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

        publishers: list[dict] = []
        journals: list[dict] = []
        hijacked: list[dict] = []

        def _read_csv(filename: str) -> list[dict]:
            url = f"{_PREDATORY_BASE}/{filename}"
            resp = safe_get(url, timeout=30, consume_body=True)
            resp.raise_for_status()
            reader = csv.DictReader(io.StringIO(resp.text))
            return [
                {k: (v or "").strip() for k, v in row.items() if k}
                for row in reader
            ]

        for row in _read_csv(_PREDATORY_FILES["publishers"]):
            name = row.get("name", "")
            if name:
                publishers.append({"name": name, "url": row.get("url", "")})

        for row in _read_csv(_PREDATORY_FILES["journals"]):
            name = row.get("name", "")
            if name:
                journals.append({"name": name, "url": row.get("url", "")})

        for row in _read_csv(_PREDATORY_FILES["hijacked"]):
            # Upstream column names: hijacked, hijackedabbr, hijackedurl,
            # althijackedurl, authentic, authenticabbr, authenticurl.
            # The rest of the codebase reads `hijacked_name`, so map across.
            name = row.get("hijacked", "")
            if name:
                hijacked.append(
                    {
                        "hijacked_name": name,
                        "original_name": row.get("authentic", ""),
                        "hijacked_url": row.get("hijackedurl", ""),
                        "original_url": row.get("authenticurl", ""),
                    }
                )

        payload = {
            "metadata": {
                "source": (
                    "Stop Predatory Journals "
                    "(https://github.com/stop-predatory-journals/"
                    "stop-predatory-journals.github.io) — "
                    "community successor to Beall's List"
                ),
                "license": "MIT",
                "publisher_count": len(publishers),
                "journal_count": len(journals),
                "hijacked_count": len(hijacked),
            },
            "publishers": publishers,
            "journals": journals,
            "hijacked": hijacked,
        }

        total = len(publishers) + len(journals) + len(hijacked)
        if total < _MIN_PREDATORY_TOTAL:
            raise RuntimeError(
                f"Predatory: suspiciously few records "
                f"({total} < {_MIN_PREDATORY_TOTAL}); refusing to "
                "overwrite existing data"
            )

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

        logger.info(
            f"Predatory: saved {len(publishers)} publishers + "
            f"{len(journals)} journals + {len(hijacked)} hijacked"
        )
        return total
