from __future__ import annotations

import json
import random
import re
from datetime import date
from pathlib import Path
from typing import Any, Dict, Iterable, List

import requests

EXTERNAL_SUITES = ("squad_v1_external",)

SQUAD_V1_LICENSE = "CC BY-SA 4.0"
SQUAD_V1_LICENSE_URL = "https://creativecommons.org/licenses/by-sa/4.0/"
SQUAD_V1_HOMEPAGE = "https://rajpurkar.github.io/SQuAD-explorer/"
SQUAD_V1_SOURCE_URLS = (
    # Official SQuAD explorer dataset endpoint.
    "https://rajpurkar.github.io/SQuAD-explorer/dataset/dev-v1.1.json",
    # Public mirror fallback.
    "https://raw.githubusercontent.com/rajpurkar/SQuAD-explorer/master/dataset/dev-v1.1.json",
)


def _today() -> str:
    return date.today().isoformat()


def _jsonl_write(path: Path, rows: Iterable[Dict[str, Any]]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as f:
        for r in rows:
            f.write(json.dumps(r, ensure_ascii=False) + "\n")


def _fetch_json(urls: Iterable[str], timeout_s: int = 30) -> Dict[str, Any]:
    last_err: Exception | None = None
    for u in urls:
        try:
            r = requests.get(u, timeout=timeout_s)
            r.raise_for_status()
            return r.json()
        except Exception as e:  # noqa: BLE001 - fetch loop fallback
            last_err = e
            continue
    raise RuntimeError(
        f"Failed to fetch external benchmark from all URLs: {list(urls)}"
    ) from last_err


def _norm_text(s: str) -> str:
    return re.sub(r"\s+", " ", (s or "").strip())


def build_squad_v1_external(
    *,
    out_path: Path,
    sample_size: int = 250,
    seed: int = 1337,
    min_context_chars: int = 80,
) -> Path:
    """
    Build an external, non-synthetic benchmark from SQuAD v1.1 dev set.

    Output format is the native entropic-evals JSONL schema.
    """
    raw = _fetch_json(SQUAD_V1_SOURCE_URLS)
    data = raw.get("data") if isinstance(raw, dict) else None
    if not isinstance(data, list):
        raise RuntimeError("Unexpected SQuAD payload format: missing list field 'data'.")

    rows: List[Dict[str, Any]] = [
        {
            "type": "meta",
            "suite": "squad_v1_external",
            "version": "1.0.0",
            "created": _today(),
            "license": SQUAD_V1_LICENSE,
            "license_url": SQUAD_V1_LICENSE_URL,
            "description": (
                "External public benchmark (SQuAD v1.1 dev subset). "
                "Converted to closed-world evidence QA with verifiable checks."
            ),
            "source": "SQuAD v1.1 dev (public)",
            "source_homepage": SQUAD_V1_HOMEPAGE,
            "source_urls": list(SQUAD_V1_SOURCE_URLS),
            "attribution": "Rajpurkar et al., SQuAD (Stanford Question Answering Dataset).",
        }
    ]

    items: List[Dict[str, Any]] = []
    for art in data:
        paragraphs = art.get("paragraphs") if isinstance(art, dict) else None
        if not isinstance(paragraphs, list):
            continue
        for para in paragraphs:
            context = _norm_text(str(para.get("context") or "")) if isinstance(para, dict) else ""
            if len(context) < int(min_context_chars):
                continue
            qas = para.get("qas") if isinstance(para, dict) else None
            if not isinstance(qas, list):
                continue
            for qa in qas:
                qid = str(qa.get("id") or "").strip() if isinstance(qa, dict) else ""
                q = _norm_text(str(qa.get("question") or "")) if isinstance(qa, dict) else ""
                answers = qa.get("answers") if isinstance(qa, dict) else None
                if not qid or not q or not isinstance(answers, list) or not answers:
                    continue
                vals = []
                for a in answers:
                    if not isinstance(a, dict):
                        continue
                    t = _norm_text(str(a.get("text") or ""))
                    if t:
                        vals.append(t)
                vals = list(dict.fromkeys(vals))  # stable unique
                if not vals:
                    continue
                items.append(
                    {
                        "type": "case",
                        "id": f"squad_v1_external_{qid}",
                        "category": "factual_qa_external",
                        "context": [{"id": "E1", "text": context}],
                        "question": q,
                        "expected": {"answers_any_of": vals},
                        "checks": [
                            {"type": "json_schema"},
                            {"type": "evidence_ids_valid"},
                            {"type": "requires_evidence", "min_ids": 1},
                            {
                                "type": "answer_any_of_normalized",
                                "field": "answer",
                                "values": vals,
                            },
                            {
                                "type": "claim_groundedness",
                                "min_supported_rate": 0.0,
                                "max_unsupported_claims": 0,
                                "max_citation_invented": 0,
                                "allow_unverifiable": True,
                            },
                        ],
                    }
                )

    rnd = random.Random(seed)
    rnd.shuffle(items)
    if sample_size and sample_size > 0:
        items = items[:sample_size]
    rows.extend(items)

    _jsonl_write(out_path, rows)
    return out_path
