"""Persistent catalogue translations; the API key never enters public payloads/backups."""
import html, json, re, threading, time
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.parse import urlencode
LANGUAGES=('en','he','zh-CN')
BUILTIN=json.loads((Path(__file__).parent/'static/locales.json').read_text(encoding='utf-8'))
FIELDS={'settings':('name','tagline','intro','about','address','hours','currency'),'categories':('name',),'products':('name','description','color','finish','origin'),'projects':('name','description','location')}
_lock=threading.Lock()
_running=set()
_retry={}
_errors={}

def init(db):
    db.execute('CREATE TABLE IF NOT EXISTS translations(source TEXT NOT NULL,lang TEXT NOT NULL,value TEXT NOT NULL,manual INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(source,lang))')
    db.execute('CREATE TABLE IF NOT EXISTS translation_usage(day TEXT PRIMARY KEY,characters INTEGER NOT NULL)')

def sources(catalog):
    values=set()
    for kind,fields in FIELDS.items():
        rows=[catalog['settings']] if kind=='settings' else catalog[kind]
        for row in rows:
            for f in fields:
                value=row.get(f,'').strip()
                if value and re.search(r'[\u0600-\u06ff]',value): values.add(value)
    return sorted(values)

def configuration(db):
    row=db.execute("SELECT value FROM config WHERE key='translation_service'").fetchone()
    return json.loads(row[0]) if row else {'key':'','enabled':False,'dailyLimit':100000}

def snapshot(db,catalog,lang):
    items=set(sources(catalog)); existing={s:v for s,v in BUILTIN.get(lang,{}).items() if s in items}
    existing.update({r['source']:r['value'] for r in db.execute('SELECT source,value FROM translations WHERE lang=?',(lang,)) if r['source'] in items})
    cfg=configuration(db)
    return {'language':lang,'texts':existing,'missing':len(set(items)-existing.keys()),'configured':bool(cfg.get('key') and cfg.get('enabled')),'running':lang in _running}

def protect(text):
    # Keep embedded numbers, dimensions, URLs and Latin model codes exactly as entered.
    tokens=[]
    def hold(m):
        tokens.append(m.group(0)); return 'ZXQKEEP'+str(len(tokens)-1)+'QXZ'
    return re.sub(r'https?://\S+|[A-Za-z0-9٠-٩۰-۹]+(?:[./×x:+%_-][A-Za-z0-9٠-٩۰-۹]+)*',hold,text),tokens

def translate_batch(texts,lang,key):
    protected=[protect(s) for s in texts]
    body=json.dumps({'q':[p[0] for p in protected],'source':'ar','target':lang,'format':'text'}).encode()
    req=Request('https://translation.googleapis.com/language/translate/v2?'+urlencode({'key':key}),data=body,headers={'Content-Type':'application/json'},method='POST')
    with urlopen(req,timeout=25) as response: result=json.load(response)['data']['translations']
    if len(result)!=len(texts): raise ValueError('Invalid provider response')
    translated=[]
    for row,(_,tokens) in zip(result,protected):
        value=html.unescape(row['translatedText'])
        if not isinstance(value,str) or not value.strip() or len(value)>15000: raise ValueError('Invalid translation')
        for i,token in enumerate(tokens):
            marker='ZXQKEEP'+str(i)+'QXZ'
            if value.count(marker)!=1: raise ValueError('Protected values changed')
            value=value.replace(marker,token)
        translated.append(value)
    return translated

def enqueue(connect,catalog,lang):
    if lang not in LANGUAGES:return
    with _lock:
        if lang in _running or time.time()<_retry.get(lang,0):return
        with connect() as db:
            state=snapshot(db,catalog,lang)
            if not state['configured'] or not state['missing']:return
        _running.add(lang)
    threading.Thread(target=run,args=(connect,catalog,lang),daemon=True).start()

def run(connect,catalog,lang):
    try:
        with connect() as db:
            cfg=configuration(db); state=snapshot(db,catalog,lang)
        missing=[s for s in sources(catalog) if s not in state['texts']]
        batches=[];batch=[];size=0
        for source in missing:
            length=len(protect(source)[0])
            if batch and (size+length>10000 or len(batch)>=16):batches.append(batch);batch=[];size=0
            batch.append(source);size+=length
        if batch:batches.append(batch)
        for batch in batches:
            count=sum(len(protect(s)[0]) for s in batch); day=time.strftime('%Y-%m-%d')
            with connect() as db:
                db.execute('BEGIN IMMEDIATE')
                current=configuration(db)
                if not current.get('enabled') or not current.get('key'):break
                used=db.execute('SELECT characters FROM translation_usage WHERE day=?',(day,)).fetchone()
                if (used[0] if used else 0)+count>current.get('dailyLimit',100000):raise ValueError('Daily limit')
                db.execute('INSERT INTO translation_usage VALUES (?,?) ON CONFLICT(day) DO UPDATE SET characters=characters+excluded.characters',(day,count))
            values=translate_batch(batch,lang,current['key'])
            with connect() as db:
                for source,value in zip(batch,values):
                    db.execute('INSERT OR IGNORE INTO translations VALUES (?,?,?,0)',(source,lang,value))
        _errors.pop(lang,None)
    except Exception:
        _errors[lang]='تعذرت الترجمة. راجع المفتاح وتفعيل الخدمة والإنترنت والحد اليومي ثم أعد المحاولة.'
        _retry[lang]=time.time()+60
    finally:
        with _lock:_running.discard(lang)

def export(db):return [dict(r) for r in db.execute('SELECT * FROM translations')]
def validate_backup(rows):
    if not isinstance(rows,list) or len(rows)>100000:raise ValueError('Invalid translations')
    for r in rows:
        if not isinstance(r,dict) or r.get('lang') not in LANGUAGES:raise ValueError('Invalid language')
        if any(not isinstance(r.get(k),str) or len(r[k])>15000 for k in ('source','value')):raise ValueError('Invalid text')
    return rows

def restore(db,rows):
    db.execute('DELETE FROM translations')
    for r in rows:db.execute('INSERT OR REPLACE INTO translations VALUES (?,?,?,?)',(r['source'],r['lang'],r['value'],int(bool(r.get('manual')))))
