import json, sqlite3, time, re, os
from pathlib import Path
from urllib.parse import urlencode
import requests
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse, StreamingResponse
from starlette.middleware.sessions import SessionMiddleware

BASE=Path('/opt/vodcreator3'); DATA=BASE/'data'; CACHE=BASE/'cache'; DB=DATA/'vodcreator.db'; CFG=DATA/'config.json'
DATA.mkdir(parents=True,exist_ok=True); CACHE.mkdir(parents=True,exist_ok=True)
app=FastAPI(title='VOD Creator V3'); app.add_middleware(SessionMiddleware,secret_key=os.environ.get('VOD_SECRET','v3-change-me'))

def conn():
 c=sqlite3.connect(DB); c.row_factory=sqlite3.Row; return c

def init():
 c=conn(); c.executescript('''CREATE TABLE IF NOT EXISTS items(id INTEGER PRIMARY KEY,name TEXT,kind TEXT,category TEXT,source_url TEXT UNIQUE,last_access INTEGER DEFAULT 0);'''); c.commit(); c.close()
 if not CFG.exists(): CFG.write_text(json.dumps({'xui_url':'','access_code':'','api_key':'','vod_m3u':'','channels_main':'','channels_backup':'','storage':str(CACHE),'expire_days':7,'cache_limit_gb':0},indent=2))
init()

def cfg(): return json.loads(CFG.read_text())
def savecfg(x): CFG.write_text(json.dumps(x,indent=2))
def auth(r): return bool(r.session.get('auth'))
def esc(x):
 import html; return html.escape(str(x or ''))

def xui_url(action,extra=None):
 c=cfg(); base=c['xui_url'].rstrip('/')+'/'+c['access_code'].strip('/')+'/'
 q={'api_key':c['api_key'],'action':action}; q.update(extra or {}); return base+'?'+urlencode(q,doseq=True)

def xui(action,extra=None):
 r=requests.get(xui_url(action,extra),timeout=30); r.raise_for_status()
 try:return r.json()
 except:return {'status':'HTTP_OK','raw':r.text}

def parse_m3u(text):
 out=[]; cur={}
 for ln in text.splitlines():
  ln=ln.strip()
  if not ln: continue
  if ln.startswith('#EXTINF'):
   cur={}; m=re.search(r',([^,]*)$',ln); cur['name']=m.group(1).strip() if m else 'Sem nome'
   for k,v in re.findall(r'([\w-]+)="([^"]*)"',ln): cur[k]=v
   cur['category']=cur.get('group-title','')
  elif not ln.startswith('#') and cur:
   cur['url']=ln; n=cur['name'].lower(); g=cur.get('category','').lower()
   series=bool(re.search(r'\bS\d{1,2}(?:E\d{1,3})?\b|\bseason\b|\btemporada\b|\bT\d{1,2}\b',n,re.I))
   live_words=('tv','canais','canal','live','ao vivo','esportes','news','notícias','radio')
   kind='series' if series else ('channel' if any(w in g or w in n for w in live_words) else 'movie')
   out.append((cur['name'],kind,cur.get('category',''),cur['url'])); cur={}
 return out

def sync_local(url,channels=False):
 r=requests.get(url,timeout=90,headers={'User-Agent':'VOD-Creator-V3'}); r.raise_for_status(); items=parse_m3u(r.text)
 if channels: items=[x for x in items if x[1]=='channel']
 else: items=[x for x in items if x[1] in ('movie','series')]
 c=conn()
 for x in items:c.execute('INSERT INTO items(name,kind,category,source_url) VALUES(?,?,?,?) ON CONFLICT(source_url) DO UPDATE SET name=excluded.name,kind=excluded.kind,category=excluded.category',(x[0],x[1],x[2],x[3]))
 c.commit();c.close();return len(items)

def clean(kind):
 action={'movie':'get_movies','series':'get_series_list','channel':'get_channels'}[kind]; data=xui(action); rows=data.get('data',data) if isinstance(data,dict) else data
 if not isinstance(rows,list):return {'deleted':0,'error':data}
 deleted=0; errors=[]
 for row in rows:
  name=str(row.get('stream_display_name') or row.get('name') or '')
  if not name.startswith('[VODC3]'):continue
  rid=row.get('id') or row.get('stream_id');
  if not rid:continue
  try:
   res=xui({'movie':'delete_movie','series':'delete_series','channel':'delete_channel'}[kind],{'id':rid})
   if str(res.get('status','')).startswith('STATUS_SUCCESS'):deleted+=1
   else:errors.append(str(res))
  except Exception as e:errors.append(str(e))
 return {'deleted':deleted,'errors':errors[:5]}

def sync_xui(kind):
 c=conn(); rows=c.execute('SELECT * FROM items WHERE kind=?',(kind,)).fetchall(); c.close(); ok=bad=0; errors=[]
 for row in rows:
  try:
   if kind=='movie': p={'stream_display_name':'[VODC3] '+row['name'],'category_id':'[]','stream_source':json.dumps([row['source_url']]),'direct_source':'1','target_container':'ts'}; action='create_movie'
   elif kind=='series': p={'name':'[VODC3] '+row['name'],'category_id':'[]'}; action='create_series'
   else: p={'stream_display_name':'[VODC3] '+row['name'],'category_id':'[]','stream_source':json.dumps([row['source_url']]),'direct_source':'1','server_id':'1'}; action='create_channel'
   res=xui(action,p); 
   if str(res.get('status','')).startswith('STATUS_SUCCESS'):ok+=1
   else:bad+=1;errors.append(row['name']+': '+str(res))
  except Exception as e:bad+=1;errors.append(row['name']+': '+str(e))
 return {'ok':ok,'failed':bad,'errors':errors[:10]}

@app.get('/health')
def health():return {'ok':True,'service':'vodcreator-v3'}
@app.get('/login',response_class=HTMLResponse)
def login():return HTMLResponse('<h2>VOD Creator V3</h2><form method="post"><input name="u" placeholder="admin"><input name="p" type="password" placeholder="senha"><button>Entrar</button></form>')
@app.post('/login')
def loginp(request:Request,u:str=Form(...),p:str=Form(...)):
 if u=='admin' and p=='admin':request.session['auth']=1;return RedirectResponse('/',303)
 return HTMLResponse('Login inválido',401)
@app.get('/logout')
def logout(request:Request):request.session.clear();return RedirectResponse('/login',303)
@app.get('/',response_class=HTMLResponse)
def home(request:Request):
 if not auth(request):return RedirectResponse('/login',303)
 c=conn(); nums={k:c.execute('SELECT count(*) FROM items WHERE kind=?',(k,)).fetchone()[0] for k in ('movie','series','channel')};c.close()
 return HTMLResponse(f'''<h1>VOD Creator V3</h1><p>Filmes: {nums['movie']} | Séries: {nums['series']} | Canais: {nums['channel']}</p><a href="/xui">Integração XUI</a> | <a href="/vod">VOD</a> | <a href="/channels">Canais</a> | <a href="/storage">Armazenamento</a> | <a href="/logout">Sair</a>''')
@app.get('/xui',response_class=HTMLResponse)
def xuip(request:Request):
 if not auth(request):return RedirectResponse('/login',303)
 c=cfg();return HTMLResponse(f'''<h2>Integração XUI</h2><form method="post" action="/xui/save"><p>URL do XUI</p><input name="xui_url" value="{esc(c['xui_url'])}" style="width:600px"><p>Access Code</p><input name="access_code" value="{esc(c['access_code'])}"><p>API Key</p><input name="api_key" value="{esc(c['api_key'])}" style="width:600px"><br><br><button>Salvar</button></form><form method="post" action="/xui/test"><button>TESTAR CONEXÃO</button></form><p>Admin API: Access Code + API Key. Usuário/senha/Bearer não são necessários.</p><a href="/">Voltar</a>''')
@app.post('/xui/save')
def xuis(request:Request,xui_url:str=Form(''),access_code:str=Form(''),api_key:str=Form('')):
 if not auth(request):return RedirectResponse('/login',303)
 c=cfg();c.update({'xui_url':xui_url.strip(),'access_code':access_code.strip(),'api_key':api_key.strip()});savecfg(c);return RedirectResponse('/xui',303)
@app.post('/xui/test')
def xuit(request:Request):
 if not auth(request):return RedirectResponse('/login',303)
 try:return HTMLResponse('<pre>STATUS: XUI CONECTADO\n'+esc(json.dumps(xui('user_info'),indent=2,ensure_ascii=False))+'</pre><a href="/xui">Voltar</a>')
 except Exception as e:return HTMLResponse('<pre>ERRO: '+esc(e)+'</pre><a href="/xui">Voltar</a>',502)
@app.get('/vod',response_class=HTMLResponse)
def vod(request:Request):
 if not auth(request):return RedirectResponse('/login',303)
 c=cfg();return HTMLResponse(f'''<h2>VOD - Filmes e Séries</h2><form method="post" action="/vod/sync"><input name="url" value="{esc(c['vod_m3u'])}" placeholder="URL M3U" style="width:700px"><br><label><input type="checkbox" name="clean_movies"> Limpar filmes</label><label><input type="checkbox" name="clean_series"> Limpar séries</label><br><label><input type="checkbox" name="sync_movies" checked> Sincronizar filmes</label><label><input type="checkbox" name="sync_series" checked> Sincronizar séries</label><br><button>Sincronizar agora</button></form><a href="/">Voltar</a>''')
@app.post('/vod/sync')
def vods(request:Request,url:str=Form(...),clean_movies:str=Form(None),clean_series:str=Form(None),sync_movies:str=Form(None),sync_series:str=Form(None)):
 if not auth(request):return RedirectResponse('/login',303)
 c=cfg();c['vod_m3u']=url;savecfg(c); out=['M3U: '+str(sync_local(url))]
 if clean_movies:out.append('Limpar filmes: '+str(clean('movie')))
 if clean_series:out.append('Limpar séries: '+str(clean('series')))
 if sync_movies:out.append('XUI filmes: '+str(sync_xui('movie')))
 if sync_series:out.append('XUI séries: '+str(sync_xui('series')))
 return HTMLResponse('<pre>'+esc('\n'.join(out))+'</pre><a href="/vod">Voltar</a>')
@app.get('/channels',response_class=HTMLResponse)
def channels(request:Request):
 if not auth(request):return RedirectResponse('/login',303)
 c=cfg();return HTMLResponse(f'''<h2>Canais - Principal / Backup</h2><form method="post" action="/channels/sync"><p>Principal</p><input name="main" value="{esc(c['channels_main'])}" style="width:700px"><p>Backup</p><input name="backup" value="{esc(c['channels_backup'])}" style="width:700px"><br><label><input type="checkbox" name="clean"> Limpar canais VODC3</label><br><button>Sincronizar canais</button></form><a href="/">Voltar</a>''')
@app.post('/channels/sync')
def channelss(request:Request,main:str=Form(...),backup:str=Form(''),cleanflag:str=Form(None)):
 if not auth(request):return RedirectResponse('/login',303)
 c=cfg();c.update({'channels_main':main,'channels_backup':backup});savecfg(c);out=['Principal: '+str(sync_local(main,True))]
 if cleanflag:out.append('Limpar canais: '+str(clean('channel')))
 out.append('XUI canais: '+str(sync_xui('channel')))
 if backup:
  try:out.append('Backup acessível: '+str(len(parse_m3u(requests.get(backup,timeout=60).text))))
  except Exception as e:out.append('Backup erro: '+str(e))
 return HTMLResponse('<pre>'+esc('\n'.join(out))+'</pre><a href="/channels">Voltar</a>')
@app.get('/storage',response_class=HTMLResponse)
def storage(request:Request):
 if not auth(request):return RedirectResponse('/login',303)
 c=cfg();return HTMLResponse(f'''<h2>Armazenamento</h2><form method="post"><input name="storage" value="{esc(c['storage'])}" style="width:700px"><p>Expirar cache após dias</p><input name="expire_days" value="{c['expire_days']}" type="number"><p>Limite GB (0=sem limite)</p><input name="cache_limit_gb" value="{c['cache_limit_gb']}" type="number"><br><button>Salvar</button></form><a href="/">Voltar</a>''')
@app.post('/storage')
def storages(request:Request,storage:str=Form(...),expire_days:int=Form(7),cache_limit_gb:float=Form(0)):
 if not auth(request):return RedirectResponse('/login',303)
 p=Path(storage).expanduser();p.mkdir(parents=True,exist_ok=True);c=cfg();c.update({'storage':str(p),'expire_days':expire_days,'cache_limit_gb':cache_limit_gb});savecfg(c);return RedirectResponse('/storage',303)
@app.get('/api/status')
def status():
 c=conn();d={k:c.execute('SELECT count(*) FROM items WHERE kind=?',(k,)).fetchone()[0] for k in ('movie','series','channel')};c.close();return d
