#!/usr/bin/env python3
"""Mede a banda da VPS abrindo N canais DIFERENTES ao mesmo tempo.

Por que um curlsozinho nao basta: pedir o mesmo canal varias vezes em paralelo
nao mede a banda da VPS, mede o limite do provedor para aquele canal (ele
estrangula conexoes repetidas no mesmo ID). E preciso um canal por conexao.

Uso (na VPS, dentro da pasta vps/):

    python3 bandwidth_test.py --m3u 'http://provedor/get.php?username=U&password=P&type=m3u_plus'

Opcoes:
    --canais 5      quantos canais simultaneos
    --segundos 20   duracao de cada conexao
    --local http://127.0.0.1:$SERVER_PORT   alvo (default: a propria VPS)
"""

import argparse
import base64
import hashlib
import hmac
import json
import os
import re
import sys
import threading
import time
import urllib.parse
from concurrent.futures import ThreadPoolExecutor

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# O arquivo do proxy pode se chamar stream_server.py ou bot.py, dependendo do
# que esta configurado no painel (PY_FILE). Descobre pelo conteudo, nao pelo nome.
SECRET_KEY = None
for _cand in ('stream_server', 'bot'):
    try:
        _mod = __import__(_cand)
        if hasattr(_mod, 'SECRET_KEY') and hasattr(_mod, '_verify_stream_token'):
            SECRET_KEY = _mod.SECRET_KEY
            break
    except ModuleNotFoundError:
        continue
if SECRET_KEY is None:
    print('ERRO: nao encontrei o modulo do proxy (stream_server.py ou bot.py).')
    sys.exit(1)

import requests  # noqa: E402


def log(msg):
    print(msg, flush=True)


def ler_canais(m3u_url, quantidade, palavra=''):
    """Baixa o M3U e devolve N URLs de live (preferindo .ts para evitar HLS)."""
    log('baixando M3U...')
    r = requests.get(m3u_url, timeout=60, headers={'User-Agent': 'VLC/3.0'})
    r.raise_for_status()
    linhas = r.text.splitlines()

    urls = []
    nome_atual = ''
    for linha in linhas:
        if linha.startswith('#EXTINF'):
            nome_atual = linha.split(',', 1)[-1].strip()
        elif linha.startswith('#') or not linha.strip():
            continue
        elif linha.startswith('http://') or linha.startswith('https://'):
            if palavra and palavra.lower() not in nome_atual.lower():
                continue
            if '/live/' in linha and linha.rstrip().endswith('.ts'):
                urls.append(linha.strip())
            if len(urls) >= quantidade:
                break

    log(f'{len(urls)} canais TS encontrados')
    return urls


def gerar_token(url, exp_s=600):
    payload = json.dumps({
        'url': url, 'uid': 1, 'referrer': '', 'user_agent': 'VLC/3.0',
        'source_index': None, 'exp': time.time() + exp_s,
    }, separators=(',', ':'))
    pb = base64.urlsafe_b64encode(payload.encode()).decode()
    return pb + '.' + hmac.new(SECRET_KEY.encode(), pb.encode(), hashlib.sha256).hexdigest()


def baixar(args, alvo, idx, url, saida):
    token = gerar_token(url)
    endpoint = alvo.rstrip('/') + '/stream?token=' + urllib.parse.quote(token, safe='')
    bytes_total = 0
    t0 = time.time()
    try:
        with requests.get(endpoint, stream=True, timeout=(10, args.segundos)) as resp:
            if resp.status_code != 200:
                saida[idx] = (0, 0, f'HTTP {resp.status_code}')
                return
            for chunk in resp.iter_content(65536):
                if not chunk:
                    continue
                bytes_total += len(chunk)
                if time.time() - t0 > args.segundos:
                    break
    except Exception as exc:
        saida[idx] = (bytes_total, time.time() - t0, type(exc).__name__)
        return
    saida[idx] = (bytes_total, time.time() - t0, '')


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--m3u', required=True, help='URL completa do M3U')
    ap.add_argument('--canais', type=int, default=5)
    ap.add_argument('--segundos', type=int, default=20)
    ap.add_argument('--filtro', default='', help='palavra para filtrar nomes de canal')
    ap.add_argument('--local', default=None, help='default: http://127.0.0.1:$SERVER_PORT')
    args = ap.parse_args()

    porta = os.environ.get('SERVER_PORT')
    if not args.local and not porta:
        print('ERRO: defina SERVER_PORT ou use --local http://127.0.0.1:PORTA')
        return 1
    alvo = args.local or f'http://127.0.0.1:{porta}'
    log(f'alvo: {alvo}')

    if SECRET_KEY == 'troco-play-stream-secret':
        log('ERRO: STREAM_PROXY_SECRET nao esta definida no .env.')
        return 1

    urls = ler_canais(args.m3u, args.canais, args.filtro)
    if len(urls) < args.canais:
        log(f'AVISO: só achei {len(urls)} canais (pedi {args.canais}).')

    saida = [None] * len(urls)
    log(f'\nabrindo {len(urls)} conexões por {args.segundos}s...\n')
    t0 = time.time()
    with ThreadPoolExecutor(max_workers=len(urls)) as pool:
        futuros = [pool.submit(baixar, args, alvo, i, u, saida) for i, u in enumerate(urls)]
        for f in futuros:
            f.result()
    elapsed = time.time() - t0

    log(f'{"canal":<34} {"MB":>7}  {"Mbps":>7}  obs')
    log('-' * 66)
    total = 0
    for i, (b, t, obs) in enumerate(saida):
        if b is None:
            continue
        total += b
        nome = re.sub(r'/live/[^/]+/[^/]+/', '/', urls[i])
        log(f'{nome:<34} {b/1048576:>7.2f}  {b*8/max(t, .01)/1e6:>7.2f}  {obs}')

    mbps = total * 8 / max(elapsed, .01) / 1e6
    log('-' * 66)
    log(f'TOTAL {total/1048576:.2f} MB em {elapsed:.1f}s = {mbps:.2f} Mbps agregados')
    log(f'media por canal: {mbps/max(len(urls), 1):.2f} Mbps')
    log('\nPara caber N viewers simultaneos, a VPS precisa de upload >= '
        f'{mbps/max(len(urls), 1) * 2:.2f} Mbps por viewer (com folga x2).')
    return 0


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