"""Translator-Agent: uebersetzt fremdsprachige Artikel ins Deutsche.

Eigener Agent (separat vom Analyzer), damit Token-Limits nicht zwischen
Lagebild und Uebersetzung konkurrieren. Nutzt CLAUDE_MODEL_FAST (Haiku) in
Batches.

Aufgerufen vom Orchestrator nach analyzer.analyze() und vor post_refresh_qc.
Backfill-Skript nutzt dieselbe Funktion fuer rueckwirkendes Auffuellen.
"""
import json
import logging
import re

from agents.claude_client import call_claude, ClaudeUsage, UsageAccumulator
from config import CLAUDE_MODEL_FAST, TRANSLATOR_ENABLED

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

# Pro Batch nicht mehr als so viele Artikel an Claude geben.
# Bei Haiku ist das Output-Limit ca. 8k Tokens. Pro Artikel kommen leicht
# 400-600 Tokens raus (headline_de + content_de bis 1000 Zeichen). Bei 15
# wurde regelmaessig getrunkt (mid-JSON broken). 5 ist sicher mit Reserve.
DEFAULT_BATCH_SIZE = 5

# content_original wird ohnehin auf 1000 Zeichen gecappt (rss_parser).
# Fuer den Translator nochmal verkuerzen, falls vorhanden mehr.
CONTENT_INPUT_MAX = 1200

# content_de soll wie content_original auf 1000 Zeichen begrenzt sein.
CONTENT_OUTPUT_MAX = 1000


def _extract_complete_objects(text: str) -> list[dict]:
    """Extrahiert vollstaendige JSON-Objekte aus moeglicherweise abgeschnittenem Text.

    Klammer-Counter-Ansatz: jedes balancierte {...} wird probiert.
    """
    results = []
    depth = 0
    start = -1
    in_string = False
    escape = False
    for i, ch in enumerate(text):
        if escape:
            escape = False
            continue
        if ch == "\\":
            escape = True
            continue
        if ch == '"' and not escape:
            in_string = not in_string
            continue
        if in_string:
            continue
        if ch == "{":
            if depth == 0:
                start = i
            depth += 1
        elif ch == "}":
            depth -= 1
            if depth == 0 and start >= 0:
                obj_text = text[start:i + 1]
                try:
                    obj = json.loads(obj_text)
                    if isinstance(obj, dict):
                        results.append(obj)
                except json.JSONDecodeError:
                    pass
                start = -1
    return results


def _build_prompt(articles: list[dict], output_lang: str = "de") -> str:
    """Bauen den Translation-Prompt fuer eine Batch."""
    lang_label = {"de": "Deutsch", "en": "Englisch"}.get(output_lang, output_lang)

    items = []
    for a in articles:
        items.append({
            "id": a["id"],
            "headline": a.get("headline", "") or "",
            "content": (a.get("content_original") or "")[:CONTENT_INPUT_MAX],
            "source_lang": a.get("language", "en"),
        })

    return f"""Du bist ein praeziser Uebersetzer fuer Nachrichten-Artikel.
Uebersetze die folgenden Artikel nach {lang_label}.

WICHTIG:
- Verwende IMMER echte UTF-8-Umlaute (ä, ö, ü, ß) - NIEMALS Umschreibungen wie ae, oe, ue, ss.
  Beispiele: "Gespraeche" -> "Gespräche", "Fuehrer" -> "Führer", "grosse" -> "große".
- Behalte Eigennamen (Personen, Orte, Organisationen) im Original.
- Headline kurz und buendig wie im Original.
- Content auf MAX {CONTENT_OUTPUT_MAX} Zeichen kuerzen, kein HTML, kein Markdown.
- Wenn der Artikel schon auf {lang_label} ist (z.B. source_lang="{output_lang}"),
  kopiere headline und content unveraendert.

Antworte AUSSCHLIESSLICH mit einem flachen JSON-Array (kein Wrapper-Objekt!).
Format genau so:
[
  {{"id": 1, "headline_de": "Titel auf Deutsch", "content_de": "Inhalt auf Deutsch"}},
  {{"id": 2, "headline_de": "...", "content_de": "..."}}
]

NICHT erlaubt: {{"translations": [...]}} oder {{"items": [...]}} oder Markdown-Codefences.
Nur das Array, ohne Einleitung, ohne Erklaerung.

ARTIKEL:
{json.dumps(items, ensure_ascii=False, indent=2)}
"""


def _parse_response(text: str) -> list[dict]:
    """Robustes JSON-Array-Parsing.

    Handhabt:
    - reines JSON
    - JSON in Markdown-Codefence ```json ... ```
    - abgeschnittene Antworten (extrahiert vollstaendige Top-Level-Objekte)
    """
    text = text.strip()
    # Markdown-Codefence entfernen
    if text.startswith("```"):
        text = re.sub(r"^```(?:json)?\s*", "", text)
        text = re.sub(r"\s*```\s*$", "", text)
        text = text.strip()

    try:
        data = json.loads(text)
    except json.JSONDecodeError:
        # Erst Array versuchen
        match = re.search(r"\[.*\]", text, re.DOTALL)
        if match:
            try:
                data = json.loads(match.group(0))
            except json.JSONDecodeError:
                # Truncate-Fallback: einzelne Top-Level-Objekte extrahieren
                data = _extract_complete_objects(text)
        else:
            data = _extract_complete_objects(text)

    # Claude wraps das Array gelegentlich in {"translations": [...]} oder {"items": [...]}
    if isinstance(data, dict):
        for key in ("translations", "items", "results", "data"):
            if isinstance(data.get(key), list):
                data = data[key]
                break
        else:
            # Einzelnes Objekt? Dann als Liste mit einem Element behandeln
            if "id" in data:
                data = [data]
            else:
                raise ValueError(f"Translator-Antwort: Dict ohne erwarteten Array-Key (keys={list(data.keys())[:5]})")

    if not isinstance(data, list):
        raise ValueError(f"Translator-Antwort ist kein Array: {type(data).__name__}")

    cleaned = []
    for item in data:
        if not isinstance(item, dict):
            continue
        aid = item.get("id")
        if not isinstance(aid, int):
            try:
                aid = int(aid)
            except (TypeError, ValueError):
                continue
        cleaned.append({
            "id": aid,
            "headline_de": (item.get("headline_de") or "").strip() or None,
            "content_de": (item.get("content_de") or "").strip() or None,
        })
    return cleaned


async def translate_articles_batch(
    articles: list[dict],
    output_lang: str = "de",
) -> tuple[list[dict], ClaudeUsage]:
    """Uebersetzt eine Batch von Artikeln.

    Erwartet articles als Liste von Dicts mit den Feldern id, headline,
    content_original, language.

    Rueckgabe: (uebersetzte_artikel, usage)
    Wenn der Call fehlschlaegt, wird ([], leere_usage) zurueckgegeben - der
    Caller kann entscheiden, ob retry oder skip.
    """
    if not articles:
        return [], ClaudeUsage()

    prompt = _build_prompt(articles, output_lang)

    try:
        result_text, usage = await call_claude(prompt, tools=None, model=CLAUDE_MODEL_FAST)
    except Exception as e:
        logger.error(f"Translator Claude-Call fehlgeschlagen: {e}")
        return [], ClaudeUsage()

    try:
        translations = _parse_response(result_text)
    except Exception as e:
        logger.error(f"Translator JSON-Parsing fehlgeschlagen: {e}; raw: {result_text[:300]!r}")
        return [], usage

    # Validierung: nur Translations zurueckgeben, deren id wirklich
    # in der angefragten Batch war
    requested_ids = {a["id"] for a in articles}
    valid = [t for t in translations if t["id"] in requested_ids]
    if len(valid) != len(translations):
        logger.warning(
            "Translator: %d von %d Translations referenzieren unbekannte IDs",
            len(translations) - len(valid), len(translations),
        )
    return valid, usage


async def translate_articles(
    articles: list[dict],
    output_lang: str = "de",
    batch_size: int = DEFAULT_BATCH_SIZE,
    usage_accumulator: UsageAccumulator | None = None,
) -> list[dict]:
    """Uebersetzt eine beliebige Anzahl Artikel in Batches.

    Bringt die Batches durch Logik in `translate_articles_batch` und gibt
    EINE flache Liste der Translations zurueck. Wenn ein Batch fehlschlaegt,
    wird er uebersprungen (anderer Batches laufen weiter).
    """
    if not articles:
        return []

    if not TRANSLATOR_ENABLED:
        logger.info(
            "Translator deaktiviert (TRANSLATOR_ENABLED=false), %d Artikel uebersprungen",
            len(articles),
        )
        return []

    all_translations = []
    for i in range(0, len(articles), batch_size):
        batch = articles[i : i + batch_size]
        translations, usage = await translate_articles_batch(batch, output_lang)
        if usage_accumulator is not None:
            usage_accumulator.add(usage)
        all_translations.extend(translations)
        logger.info(
            "Translator-Batch %d/%d: %d/%d uebersetzt (cost=$%.4f)",
            (i // batch_size) + 1,
            (len(articles) + batch_size - 1) // batch_size,
            len(translations), len(batch),
            usage.cost_usd,
        )
    return all_translations
