"""Hermes V3D: propuestas de asignación desde tarjetas+EPG+Sling. Sin escritura en DB.
Solo muestra datos no sensibles al modelo. Las propuestas requieren aprobación independiente.
"""
import os, re, base64, sqlite3, urllib.request, json, secrets, hashlib
from pathlib import Path
from datetime import datetime, timezone
from urllib.parse import urlsplit
from hermes_football_link_manager_v2_1 import tokens, epg_date, channel_num, epg_num

DB='/var/lib/hermes-nexus/football.db'
EPG='/var/lib/hermes-nexus/epg.db'
PENDING=Path('/var/lib/hermes-nexus/football_pending')
HOST='15.204.47.5'

def _playlist():
    user=os.environ.get('HERMES_O11_USER')
    password=os.environ.get('HERMES_O11_PASSWORD')
    if not user or not password: raise RuntimeError('Credenciales O11 no cargadas')
    auth=base64.b64encode((user+':'+password).encode()).decode()
    req=urllib.request.Request('http://15.204.47.5:1234/playlist.m3u',headers={'Authorization':'Basic '+auth,'User-Agent':'Hermes-V3D'})
    with urllib.request.urlopen(req,timeout=25) as resp: content=resp.read(25*1024*1024+1)
    if len(content)>25*1024*1024: raise ValueError('M3U excede límite')
    channels={}; name=''
    for line in content.decode('utf-8-sig',errors='replace').splitlines():
        line=line.strip()
        if line.startswith('#EXTINF'): name=line.rsplit(',',1)[-1].strip()
        elif line.startswith('http') and name:
            p=urlsplit(line)
            key=channel_num(name)
            if key and p.scheme=='http' and p.hostname==HOST and p.port==1234 and p.path.startswith('/stream/sling/') and not p.username and not p.password:
                channels.setdefault(key,[]).append({'nombre':name,'url':line})
            name=''
    return channels

def preparar_asignacion_futbol(event_id):
    if not isinstance(event_id,str) or not re.fullmatch(r'[a-zA-Z0-9_.:-]{5,100}',event_id):
        raise ValueError('ID de partido inválido')
    with sqlite3.connect('file:'+DB+'?mode=ro',uri=True) as db:
        db.row_factory=sqlite3.Row
        event=db.execute('SELECT * FROM events WHERE id=?',(event_id,)).fetchone()
        if not event:return {'estado':'EVENTO_NO_ENCONTRADO'}
        event=dict(event)
        existing=db.execute('SELECT manual_url FROM football_links WHERE event_id=?',(event_id,)).fetchone()
        if existing and existing[0]:return {'estado':'ENLACE_MANUAL_PROTEGIDO'}
    when=datetime.fromisoformat(event['starts_utc'].replace('Z','+00:00')).astimezone(timezone.utc)
    with sqlite3.connect('file:'+EPG+'?mode=ro',uri=True) as db:
        rows=db.execute("SELECT canal,titulo,inicio,fin FROM epg_programas WHERE fuente='sling' AND (canal LIKE 'vixpremiumdeportes%' OR canal LIKE 'vixdeportes%') AND inicio LIKE ? LIMIT 500",(when.strftime('%Y%m%d')+'%',)).fetchall()
    home=tokens(event['home']);away=tokens(event['away'])
    candidates=[]
    for canal,titulo,inicio,fin in rows:
        words=tokens(titulo)
        if not home or not away or not (home & words) or not (away & words):continue
        try: delta=round((epg_date(inicio)-when).total_seconds()/60)
        except (ValueError,TypeError):continue
        if abs(delta)>20:continue
        key=epg_num(canal)
        if key:candidates.append({'canal':canal,'titulo':titulo,'inicio':inicio,'delta':delta,'key':key})
    # No descarga catálogo si no hay coincidencia EPG.
    if not candidates:return {'estado':'SIN_COINCIDENCIA_EPG','partido':event['away']+' vs '+event['home']}
    catalog=_playlist()
    matches=[]
    for c in candidates:
        entries=catalog.get(c['key'],[])
        if len(entries)==1:matches.append((c,entries[0]))
    urls={m[1]['url'] for m in matches}
    if len(urls)!=1:return {'estado':'AMBIGUO','coincidencias_epg':len(candidates),'coincidencias_catalogo':len(matches)}
    c,entry=matches[0]
    PENDING.mkdir(mode=0o700,parents=True,exist_ok=True)
    os.chmod(PENDING,0o700)
    ticket=secrets.token_hex(12)
    proposal={'event_id':event_id,'partido':event['away']+' vs '+event['home'],'epg_canal':c['canal'],
      'epg_titulo':c['titulo'],'diferencia_minutos':c['delta'],'catalogo_nombre':entry['nombre'],
      'url':entry['url'],'fuente_publica':'','verificado_por':'',
      'created_utc':datetime.now(timezone.utc).isoformat()}
    file=PENDING/(ticket+'.json')
    with open(file,'x',encoding='utf-8') as fh:json.dump(proposal,fh,ensure_ascii=False,indent=2)
    os.chmod(file,0o600)
    return {'estado':'PROPUESTA_PENDIENTE','ticket':ticket,'event_id':event_id,
      'partido':proposal['partido'],'canal_epg':c['canal'],'canal_catalogo':entry['nombre'],
      'diferencia_minutos':c['delta'],'verificacion_publica':'PENDIENTE',
      'guardado':False,'online':'NO_COMPROBADO',
      'instruccion':'Revisar fuente pública independiente y autorizar fuera de Gemini antes de guardar.'}
