"""Text-Normalisierung vor dem TTS.

Leitprinzip: NICHT duplizieren, was espeak-ng (in piper) bereits gut kann
(Kardinal-/Dezimalzahlen). Nur die belegten Lücken füllen: Ordinalzahlen,
Einheiten-Abkürzungen, gängige Abkürzungen und ein optionales YAML-Lexikon.

Stufen (`level`):
  - "off":   keine Änderung (Text unverändert durchreichen).
  - "light": nur harmlose Glättung (URLs/E-Mails, Whitespace, Abschlusspunkt) –
             für Cloud-TTS, das Zahlen/Abkürzungen selbst gut spricht.
  - "full":  zusätzlich Ordinalia, Einheiten, Abkürzungen, Lexikon, Symbole –
             für lokales TTS (piper).
"""

from __future__ import annotations

import re
from functools import lru_cache
from pathlib import Path

from app.pipeline import german_numbers as gn

try:
    import yaml
except ModuleNotFoundError:  # pragma: no cover - YAML optional
    yaml = None

_CONFIG_DIR = Path(__file__).resolve().parents[2] / "config"

_MONTHS = (
    "Januar|Februar|März|April|Mai|Juni|Juli|August|"
    "September|Oktober|November|Dezember"
)
_MONTH_ORDINAL = re.compile(rf"\b(\d{{1,2}})\.\s+(?=(?:{_MONTHS})\b)")
# Aufzählungsmarker wie "1)" oder "1.)" -> adverbiale Ordinalzahl.
_ENUM_MARKER = re.compile(r"\b(\d{1,2})\.?\)")
# Folge von >=2 Zahl-Punkt-Markern ("1. 2. 3.") -> jeweils adverbial.
_ENUM_SEQ = re.compile(r"(?:(?<![\d,])\d{1,2}\.\s*){2,}")
_NUM_DOT = re.compile(r"(\d{1,2})\.")

# Eingebaute Defaults (auch ohne YAML aktiv). YAML erweitert/überschreibt sie.
_DEFAULT_ABBREVS_DE = {
    "z.B.": "zum Beispiel", "z. B.": "zum Beispiel",
    "d.h.": "das heißt", "d. h.": "das heißt",
    "u.a.": "unter anderem", "bzw.": "beziehungsweise",
    "ca.": "circa", "usw.": "und so weiter", "etc.": "et cetera",
    "Dr.": "Doktor", "Prof.": "Professor", "Nr.": "Nummer", "Str.": "Straße",
}
_DEFAULT_UNITS_DE = {
    "kg": "Kilogramm", "g": "Gramm", "mg": "Milligramm",
    "km": "Kilometer", "m": "Meter", "cm": "Zentimeter", "mm": "Millimeter",
    "l": "Liter", "ml": "Milliliter", "h": "Stunden", "min": "Minuten",
    "km/h": "Kilometer pro Stunde",
}
_DEFAULT_SYMBOLS_DE = {
    "24/7": "vierundzwanzig sieben", "&": " und ", "%": " Prozent",
    "€": " Euro", "$": " Dollar",
}
_DEFAULT_SYMBOLS_EN = {
    "24/7": "twenty four seven", "&": " and ", "%": " percent",
    "€": " euros", "$": " dollars", "e.g.": "for example", "i.e.": "that is",
}


@lru_cache(maxsize=8)
def _load_lexicon(language: str) -> tuple:
    """Lädt config/pronunciation.<lang>.yaml; gibt (abbrevs, units, terms) zurück."""
    abbrevs, units, terms = {}, {}, {}
    path = _CONFIG_DIR / f"pronunciation.{language}.yaml"
    if yaml is not None and path.exists():
        try:
            data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
            abbrevs = {str(k): str(v) for k, v in (data.get("abbreviations") or {}).items()}
            units = {str(k): str(v) for k, v in (data.get("units") or {}).items()}
            terms = {str(k): str(v) for k, v in (data.get("terms") or {}).items()}
        except (OSError, ValueError, AttributeError):
            pass  # defektes YAML -> nur Defaults
    return abbrevs, units, terms


def _apply_ordinals(text: str) -> str:
    # Datum: "1. Mai" -> "erster Mai"
    def _date(m):
        n = int(m.group(1))
        return f"{gn.ordinal_attributive(n)} " if gn.has_ordinal(n) else m.group(0)

    text = _MONTH_ORDINAL.sub(_date, text)

    # Aufzählungs-Folge "1. 2. 3." -> "erstens zweitens drittens" (alle in der Folge).
    def _num(m):
        n = int(m.group(1))
        return f"{gn.ordinal_adverbial(n)} " if gn.has_ordinal(n) else m.group(0)

    text = _ENUM_SEQ.sub(lambda m: _NUM_DOT.sub(_num, m.group(0)), text)

    # Marker "1)" / "1.)" -> "erstens"
    def _marker(m):
        n = int(m.group(1))
        return gn.ordinal_adverbial(n) if gn.has_ordinal(n) else m.group(0)

    text = _ENUM_MARKER.sub(_marker, text)
    return text


def _apply_units(text: str, units: dict) -> str:
    # Nur DIREKT nach einer Zahl ersetzen, damit "Meter" nicht jedes "m" trifft.
    # Längere Einheitenkürzel zuerst (km/h vor km, mm vor m).
    for unit in sorted(units, key=len, reverse=True):
        spoken = units[unit]
        pattern = rf"(?<=\d)\s*{re.escape(unit)}\b"
        text = re.sub(pattern, f" {spoken}", text)
    return text


def _apply_dict(text: str, mapping: dict, ignore_case: bool = False) -> str:
    # Längste Schlüssel zuerst, wortgrenzen-bewusst (Abkürzungen enden oft auf ".").
    flags = re.IGNORECASE if ignore_case else 0
    for key in sorted(mapping, key=len, reverse=True):
        repl = mapping[key]
        if key.isalnum():  # reines Wort/Akronym -> mit Wortgrenzen
            text = re.sub(rf"\b{re.escape(key)}\b", repl, text, flags=flags)
        elif ignore_case:
            text = re.sub(re.escape(key), repl, text, flags=re.IGNORECASE)
        else:              # enthält Punkte/Sonderzeichen -> direkte Ersetzung
            text = text.replace(key, repl)
    return text


class TTSNormalizer:
    async def run(self, text: str, language: str = "de", level: str = "full") -> str:
        if not text:
            return ""
        if level == "off":
            return text

        t = text

        # --- immer (light + full): harmlose Glättung ---
        t = re.sub(r"https?://\S+", "Link", t)
        t = re.sub(r"\b[\w\.-]+@[\w\.-]+\.\w+\b", "E-Mail-Adresse", t)

        if level == "full":
            abbrevs, units, terms = _load_lexicon(language)

            if language == "de":
                t = _apply_ordinals(t)
                t = _apply_units(t, {**_DEFAULT_UNITS_DE, **units})
                t = _apply_dict(t, {**_DEFAULT_ABBREVS_DE, **abbrevs})
                t = _apply_dict(t, terms, ignore_case=True)
                t = _apply_dict(t, _DEFAULT_SYMBOLS_DE)
            else:
                t = _apply_units(t, units)
                t = _apply_dict(t, abbrevs)
                t = _apply_dict(t, terms, ignore_case=True)
                t = _apply_dict(t, _DEFAULT_SYMBOLS_EN)

            # Slash zwischen Wörtern/Zahlen sprachfreundlich, Striche entschärfen.
            t = re.sub(r"(\w)/(\w)", r"\1 oder \2", t)
            t = t.replace("–", " bis ").replace("—", ", ").replace(" - ", ", ")

        # Mehrfache Leerzeichen glätten, Abschlusspunkt sicherstellen.
        t = re.sub(r"\s+", " ", t).strip()
        if t and not t.endswith((".", "!", "?")):
            t += "."
        return t
