"""staan.ai Such-Client. Europäische Websuche für den EU-Modellweg (Phase 2).

Ein Baustein für Suche UND Volltextabruf. Der Parameter full_content="markdown"
liefert die kompletten Seiteninhalte der Treffer gleich mit, damit braucht die
EU-Recherche-Schleife keinen eigenen Abruf einzelner Seiten. Die Verarbeitung
bleibt vollständig beim europäischen Anbieter (European Search Perspective,
gehostet bei OVHcloud).

Befund aus Phase 0. Die API liefert praktisch nie ein Veröffentlichungsdatum
und kennt keinen Zeitraum-Filter. Die Aktualität sichern deshalb die
Query-Formulierung (macht die Schleife) und die Datumsextraktion aus den
Inhalten (macht das Modell). Der Domain-Ausschlussfilter ist Pflichtbestandteil
jeder Anfrage, ohne ihn bestehen die Trefferlisten spürbar aus YouTube,
Wikipedia und Social-Media-Seiten.
"""
import asyncio
import logging

import httpx

from config import (
    STAAN_API_KEY,
    STAAN_BASE_URL,
    STAAN_EXCLUDE_DOMAINS,
)

logger = logging.getLogger("osint.staan_client")

# Von staan offiziell unterstützte Märkte. Andere Werte fallen auf en-us zurück,
# der Index liefert auch dann fremdsprachige Treffer (in Phase 0 belegt für
# Russisch, Ukrainisch, Arabisch, Farsi, Hebräisch und Japanisch).
ALLOWED_MARKETS = {"de-de", "en-us", "fr-fr"}

# staan akzeptiert maximal 10 Ausschluss-Domains je Anfrage. Die Pflichtliste
# aus config belegt die ersten Plätze, Nutzer-Ausschlüsse füllen bis 10 auf.
_MAX_EXCLUDE_DOMAINS = 10


class StaanError(RuntimeError):
    """Fehler der staan-API (Status, Auth, Netz)."""


def market_for_language(lang_iso: str | None) -> str:
    """ISO-Sprachcode auf den passendsten staan-Markt abbilden."""
    mapping = {"de": "de-de", "en": "en-us", "fr": "fr-fr"}
    return mapping.get((lang_iso or "").lower().strip(), "en-us")


async def staan_search(
    q: str,
    market: str = "en-us",
    extra_snippets: bool = True,
    max_snippets: int = 5,
    full_content: bool = False,
    extra_exclude: list[str] | None = None,
    timeout: float = 60.0,
) -> list[dict]:
    """Eine Suchanfrage gegen staan. Gibt normalisierte Treffer zurück.

    Rückgabe je Treffer: title, url, hostname, snippet, extra_snippets (Liste
    von Text-Chunks), full_text (Markdown, gekappt), published_date (fast
    immer None, siehe Modul-Docstring).
    """
    if not STAAN_API_KEY:
        raise StaanError("STAAN_API_KEY ist nicht gesetzt, EU-Suche nicht nutzbar")

    if market not in ALLOWED_MARKETS:
        market = "en-us"

    exclude = list(STAAN_EXCLUDE_DOMAINS)
    for d in extra_exclude or []:
        d = (d or "").strip().lower()
        if not d or d in exclude:
            continue
        if len(exclude) >= _MAX_EXCLUDE_DOMAINS:
            break
        exclude.append(d)

    body: dict = {"q": (q or "").strip()[:400], "market": market, "exclude_domains": exclude}
    if extra_snippets:
        body["extra_snippets"] = True
        body["max_snippets"] = max(1, min(int(max_snippets), 10))
    if full_content:
        body["full_content"] = "markdown"

    headers = {"Authorization": f"Bearer {STAAN_API_KEY}", "Content-Type": "application/json"}

    data = None
    async with httpx.AsyncClient(timeout=timeout) as client:
        for attempt in (1, 2):
            try:
                resp = await client.post(f"{STAAN_BASE_URL}/search/web", json=body, headers=headers)
            except httpx.TimeoutException as e:
                raise StaanError(f"staan Timeout nach {timeout}s ({e})") from e
            except httpx.HTTPError as e:
                raise StaanError(f"staan Netzwerkfehler ({type(e).__name__}: {e})") from e
            if resp.status_code == 429 and attempt == 1:
                # Rate-Limit (20 req/s), kurz warten und einmal wiederholen
                await asyncio.sleep(1.5)
                continue
            if resp.status_code not in (200, 201):
                # GET antwortet mit 200, POST mit 201 (Created)
                raise StaanError(f"staan HTTP {resp.status_code}: {resp.text[:200]}")
            try:
                data = resp.json()
            except ValueError as e:
                raise StaanError(f"staan lieferte kein JSON ({e})") from e
            break

    results: list[dict] = []
    for r in (data.get("web") or {}).get("results", []):
        fc = r.get("full_content") or {}
        results.append({
            "title": (r.get("title") or "").strip(),
            "url": (r.get("url") or "").strip(),
            "hostname": (r.get("hostname") or "").strip(),
            "snippet": (r.get("snippet") or "").strip(),
            "extra_snippets": [
                (c.get("chunk") or "").strip()
                for c in (r.get("extra_snippets") or [])
                if (c.get("chunk") or "").strip()
            ],
            "full_text": ((fc.get("text") or "").strip())[:6000],
            "published_date": r.get("published_date"),
        })

    logger.info(
        "staan [%s] %r -> %d Treffer%s",
        market, body["q"][:60], len(results), " mit Volltexten" if full_content else "",
    )
    return results
