"""Verification harness for the ModuleDesk translation catalogs.

Logs in once, then fetches every app page in English and in the target language
and reports, per page: HTTP status, the <html lang> actually rendered, how many
fragments of visible text still look like English, and whether every inline
<script> still parses (the JS blocks are where wrapping breaks things).

    venv/bin/python scripts/i18n-verify.py es
    venv/bin/python scripts/i18n-verify.py de /settings /admin

The english-fragment count is a HINT, not a verdict: French and Italian share a
lot of vocabulary with the word list, so expect false positives there. A real
untranslated string is one you can also find as an empty msgstr in the catalog.
"""
import json
import re
import subprocess
import sys
import tempfile

import requests
import urllib3

urllib3.disable_warnings()

# Must be the HTTPS host: the app sets SESSION_COOKIE_SECURE in this
# environment, so a client talking plain HTTP to 127.0.0.1 silently drops the
# session cookie and every login "fails" with a missing-CSRF 400.
BASE = "https://support.claude-baby.dev"

# One verification account PER LANGUAGE. The locale is stored on users.locale,
# so two runs sharing an account would keep flipping each other's language
# mid-sweep; a dedicated account per language is what makes parallel runs safe.
ACCOUNTS = {
    "fr": "i18n-fr@local.test",
    "de": "i18n-de@local.test",
    "it": "i18n-it@local.test",
    "pl": "i18n-pl@local.test",
}
DEFAULT_EMAIL = "premium@local.test"
PASSWORD_BY_EMAIL = {DEFAULT_EMAIL: "Test123!Premium"}
DEFAULT_PASSWORD = "Test123!I18n"


def account_for(lang_code):
    email = ACCOUNTS.get(lang_code, DEFAULT_EMAIL)
    return email, PASSWORD_BY_EMAIL.get(email, DEFAULT_PASSWORD)

# Visible-text words that betray an untranslated fragment. Brand/technical terms
# that legitimately stay English are in KEEP and never count.
ENGLISH = re.compile(
    r"\b(the|and|your|you|with|from|for|that|this|when|what|which|every|into|about|"
    r"before|after|without|reply|replies|inbox|ticket|tickets|customer|customers|"
    r"support|search|module|modules|seller|answer|write|send|sent|free|team|plan|"
    r"month|user|users|credits|settings|save|cancel|delete|close|open|new|all|none|"
    r"loading|error|success|warning|please|click|select|choose|enter|add|remove|"
    r"edit|view|show|hide|filter|sort|page|next|previous|back|first|last)\b",
    re.I,
)
KEEP = re.compile(
    r"ModuleDesk|PrestaShop|Addons|Claude|Cursor|Stripe|OpenAI|VirusTotal|BYOK|"
    r"TL;DR|GPT|RAG|CSV|KPI|FTP|SFTP|SSH|VIES|Docusaurus|MAX|Premium|Free|"
    r"back office|https?://|@|\.js|\.py|\.html"
)


def login(email, password):
    s = requests.Session()
    s.verify = False
    page = s.get(f"{BASE}/login", timeout=30).text
    m = re.search(r'name="csrf_token"[^>]*value="([^"]+)"', page) or \
        re.search(r'value="([^"]+)"[^>]*name="csrf_token"', page)
    if not m:
        raise SystemExit("no csrf_token on /login")
    r = s.post(f"{BASE}/login",
               data={"username": email, "password": password,
                     "csrf_token": m.group(1)},
               # Flask-WTF enforces a same-origin Referer on HTTPS; without it
               # the POST is rejected before the credentials are ever checked.
               headers={"Referer": f"{BASE}/login"},
               allow_redirects=True, timeout=30)
    # Trust a real authenticated page, not the redirect target: a failed login
    # also returns 200, just with the form again.
    probe = s.get(f"{BASE}/dashboard", timeout=30)
    if 'name="csrf_token"' in probe.text and "/login" in probe.url:
        raise SystemExit("login failed — check credentials")
    return s


def visible_text(html):
    t = re.sub(r"<script.*?</script>|<style.*?</style>", "", html, flags=re.S)
    t = re.sub(r"<!--.*?-->", "", t, flags=re.S)
    return re.sub(r"[ \t]+", " ", re.sub(r"<[^>]+>", "\n", t))


def english_fragments(html):
    out = []
    for frag in visible_text(html).split("\n"):
        frag = frag.strip()
        if len(frag) < 12 or KEEP.search(frag):
            continue
        if len(ENGLISH.findall(frag)) >= 2:
            out.append(frag)
    return out


def scripts_ok(html):
    """node --check every inline script; returns list of failures."""
    bad = []
    # Only real JS: skip external scripts and data blocks such as
    # <script type="application/json">, which node rightly refuses to parse.
    for i, (attrs, blk) in enumerate(re.findall(r"<script\b([^>]*)>(.*?)</script>",
                                                html, re.S)):
        if "src=" in attrs:
            continue
        m = re.search(r'type=["\']([^"\']+)', attrs)
        if m and not re.search(r"javascript|module", m.group(1)):
            continue
        if not blk.strip():
            continue
        with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False) as f:
            f.write(blk)
            path = f.name
        p = subprocess.run(["node", "--check", path], capture_output=True, text=True)
        if p.returncode:
            bad.append(f"script#{i}: {p.stderr.strip().splitlines()[-1][:110]}")
    return bad


def main():
    import os
    if len(sys.argv) < 2:
        raise SystemExit("usage: i18n-verify.py <lang> [path-filter ...]")
    lang_code = sys.argv[1]
    here = os.path.dirname(os.path.abspath(__file__))
    with open(os.path.join(here, "i18n-pages.json")) as f:
        pages = json.load(f)
    filters = sys.argv[2:]
    if filters:
        pages = [p for p in pages if any(f in p for f in filters)]

    s = login(*account_for(lang_code))
    s.get(f"{BASE}/lang/en", timeout=30)

    worst = 0
    up = lang_code.upper()
    print(f"{'page':40} {'EN':>6} {up:>6} {'lang':>5} {'eng-frags':>10}  js")
    print("-" * 84)
    for path in pages:
        en = s.get(BASE + path, timeout=60)
        s.get(f"{BASE}/lang/{lang_code}", timeout=30)
        es = s.get(BASE + path, timeout=60)
        s.get(f"{BASE}/lang/en", timeout=30)

        lang = (re.search(r'<html lang="([a-z-]+)"', es.text) or [None, "?"])[1]
        frags = english_fragments(es.text) if es.status_code == 200 else []
        bad = scripts_ok(es.text) if es.status_code == 200 else []
        worst = max(worst, len(bad) + (es.status_code != 200) + (lang != lang_code))
        flag = "OK" if not bad else "FAIL"
        print(f"{path:40} {en.status_code:>6} {es.status_code:>6} {lang:>5} "
              f"{len(frags):>10}  {flag}")
        for b in bad:
            print(f"      JS {b}")
        for fr in frags[:6]:
            print(f"      EN {fr[:100]}")

    # Leave the account on ITS language. /lang/<code> writes users.locale, so a
    # run that ends on /lang/en silently flips the account to English and the
    # next person to look at that page wonders why the translation vanished.
    s.get(f"{BASE}/lang/{lang_code}", timeout=30)
    return 1 if worst else 0


if __name__ == "__main__":
    sys.exit(main())
