"""Leipzig Corpora Collection API client.

Provides example sentence lookup against the Leipzig Wortschatz API.
Used as a fallback when Tatoeba returns no results.

API base: https://api.wortschatz-leipzig.de/ws
Endpoint: GET /sentences/{corpusId}/sentences/{word}?limit=N
"""

import json
import re
import sys
import urllib.parse
import urllib.request
from typing import Optional, Tuple

# Best available corpus per ISO 639-3 language code.
# Preference order: newscrawl > news > community > web > wikipedia.
# Within a source type, prefer larger size.
# Only languages that have a corpus in the Leipzig API are listed.
CORPUS_MAP: dict[str, str] = {
    "afr": "afr-za_web_2018_1M",
    "amh": "amh_wikipedia_2018_30K",
    "ara": "ara_newscrawl_2013_1M",
    "aze": "aze_wikipedia_2018_1M",
    "bel": "bel_wikipedia_2018_300K",
    "ben": "ben_community_2017",
    "bos": "bos_wikipedia_2018_300K",
    "bre": "bre_wikipedia_2018_100K",
    "bul": "bul_newscrawl_2011_1M",
    "ces": "ces_news_2013_1M",
    "dan": "dan_news_2012_1M",
    "deu": "deu_news_2012_3M",
    "ell": "ell_newscrawl_2013_1M",
    "eng": "eng_news_2013_3M",
    "est": "est_news_2014_300K",
    "fin": "fin_news_2012_300K",
    "fra": "fra_news_2011_3M",
    "guj": "guj_newscrawl_2014_1M",
    "hin": "hin_news_2011_1M",
    "hun": "hun_newscrawl_2013_1M",
    "ita": "ita_news_2012_1M",
    "lat": "lat_wikipedia_2012_100K",
    "lug": "lug_community_2017",
    "nep": "nep_news_2010_300K",
    "nld": "nld_news_2011_1M",
    "pol": "pol_news_2011_1M",
    "por": "por_news_2013_1M",
    "ron": "ron_news_2011_300K",
    "rus": "rus_news_2013_1M",
    "spa": "spa_news_2011_3M",
    "swe": "swe_news_2012_300K",
    "tat": "tat_web_2018_1M",
    "tur": "tur_newscrawl_2013_1M",
    "ukr": "ukr_newscrawl_2011_1M",
    "urd": "urd_news_2013_1M",
    "vie": "vie_newscrawl_2013_1M",
    "zul": "zul_mixed_2014_100K",
}

# CJK and other scripts where Leipzig's token-based index is unreliable.
# Queries for these will silently return None rather than log a miss.
_SILENT_LANGS = {"cmn", "yue", "jpn", "kor", "zho"}


class LeipzigClient:
    """Client for the Leipzig Wortschatz sentence API.

    Looks up example sentences from the Leipzig Corpora Collection.
    Intended as a fallback when Tatoeba returns no results.
    """

    API_BASE = "https://api.wortschatz-leipzig.de/ws"

    def __init__(self, verbose: bool = True, timeout: float = 10.0):
        """Initialise the Leipzig client.

        Args:
            verbose: Whether to print progress messages to stderr.
            timeout: Request timeout in seconds (share budget with Tatoeba).
        """
        self.verbose = verbose
        self.timeout = timeout

    def fetch_example_with_metadata(
        self,
        word: str,
        src_lang: str,
    ) -> Optional[Tuple[str, str, str, str]]:
        """Fetch an example sentence for *word* in *src_lang*.

        Args:
            word: The vocabulary word to look up.
            src_lang: ISO 639-3 language code (e.g. 'fin', 'deu').

        Returns:
            Tuple of (sentence_text, sentence_id, license, source) where
            source is ``"leipzig"``, or ``None`` if no suitable sentence
            was found or the language has no corpus mapping.
        """
        corpus = CORPUS_MAP.get(src_lang)
        if corpus is None:
            return None

        silent = src_lang in _SILENT_LANGS
        word_enc = urllib.parse.quote(word, safe="")
        url = f"{self.API_BASE}/sentences/{corpus}/sentences/{word_enc}?limit=10"
        req = urllib.request.Request(url, headers={"User-Agent": "words-to-readlang/1.0"})

        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as resp:
                data = json.loads(resp.read().decode("utf-8"))
        except Exception as exc:
            if self.verbose and not silent:
                print(f"  [Leipzig] Request failed: {exc}", file=sys.stderr)
            return None

        for item in data.get("sentences", []):
            text = item.get("sentence", "")
            if self._whole_word_in(word, text):
                sentence_id = str(item.get("id", ""))
                source_url = item.get("source", {}).get("url", "")
                if self.verbose:
                    preview = text[:60] + "..." if len(text) > 60 else text
                    print(f"  ✓ {preview} [Leipzig]", file=sys.stderr)
                return (text, sentence_id, "CC BY", source_url)

        if self.verbose and not silent:
            print(f"  ✗ not found [Leipzig]", file=sys.stderr)
        return None

    def _whole_word_in(self, word: str, sentence: str) -> bool:
        """Check if *word* appears as a whole token in *sentence*."""
        pattern = r"(?<!\w)" + re.escape(word) + r"(?!\w)"
        return bool(re.search(pattern, sentence))
