"""
SMS Sender Pro - Bridge API v3.0
Puente entre la interfaz web y el SMS Gateway HTTP del celular.
No requiere ADB — se comunica directamente con la app del celular via HTTP.
"""

import threading
import time
import sys
import os
import re
import json
import logging
import requests as req_lib
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import base64
import hmac
import hashlib

SECRET_KEY = b"SMS_SENDER_PRO_MASTER_SEC_KEY"

# ═══════════════════════════════════════════════════════════
#  NORMALIZACIÓN DE TELÉFONOS
# ═══════════════════════════════════════════════════════════

VZ_PREFIXES = ('412', '414', '416', '422', '424', '426')

def normalize_phone(raw: str) -> str:
    """Normaliza un número al formato E.164 (+XX...).
    Reglas Venezuela:
      0424XXXXXXX  → +58424XXXXXXX
      424XXXXXXX   → +58424XXXXXXX
      584XXXXXXX   → +58424XXXXXXX  (Excel eliminó el +)
      +58424XXX    → sin cambio
    """
    s = str(raw).strip()
    # Quitar espacios, guiones, paréntesis
    s = re.sub(r'[\s\-\.\(\)]', '', s)
    # Número científico de Excel (ej: 5.84241e+11)
    if re.match(r'^\d+\.?\d*[eE][+\-]?\d+$', s):
        try:
            s = str(int(float(s)))
        except Exception:
            pass

    has_plus = s.startswith('+')
    digits = re.sub(r'\D', '', s)

    if not digits:
        return ''

    # Ya tiene +, dejarlo en formato E.164
    if has_plus and len(digits) >= 10:
        return '+' + digits

    # Venezuela: 58 + prefijo operadora
    if digits.startswith('58') and len(digits) >= 11 and \
       any(digits[2:].startswith(p[:2]) for p in VZ_PREFIXES):
        return '+' + digits

    # Venezuela: 0 + prefijo (local)
    if digits.startswith('0') and len(digits) == 11 and \
       any(digits[1:].startswith(p[:2]) for p in VZ_PREFIXES):
        return '+58' + digits[1:]

    # Venezuela: directo con prefijo de operadora
    if len(digits) == 10 and any(digits.startswith(p[:2]) for p in VZ_PREFIXES):
        return '+58' + digits

    # Número largo desconocido → internacional
    if len(digits) >= 10:
        return '+' + digits

    return s

# ═══════════════════════════════════════════════════════════
#  CONFIGURACIÓN DEL GATEWAY
#  Edita estos valores si cambian en la app del celular
# ═══════════════════════════════════════════════════════════
GATEWAY_HOST     = "192.168.1.104"   # IP local del celular (ver app)
GATEWAY_PORT     = 8080
GATEWAY_USER     = "sms"
GATEWAY_PASS     = "12345678"
GATEWAY_BASE_URL = f"http://{GATEWAY_HOST}:{GATEWAY_PORT}"
GATEWAY_TIMEOUT  = 15                # segundos por petición

# Compatibilidad de directorio con PyInstaller (para compilación a .exe)
import sys
if getattr(sys, 'frozen', False):
    base_path = sys._MEIPASS
    data_path = os.path.dirname(sys.executable)
else:
    base_path = os.path.dirname(os.path.abspath(__file__))
    data_path = base_path

CONFIG_FILE = os.path.join(data_path, "config.json")
HISTORY_FILE = os.path.join(data_path, "history.json")
LICENSE_FILE = os.path.join(data_path, "license.json")

license_info = None

def load_license():
    global license_info
    if os.path.exists(LICENSE_FILE):
        try:
            with open(LICENSE_FILE, "r", encoding="utf-8") as f:
                license_info = json.load(f)
        except:
            pass

def save_license():
    try:
        with open(LICENSE_FILE, "w", encoding="utf-8") as f:
            json.dump(license_info, f)
    except:
        pass

def check_license():
    # El bloqueo ahora recae 100% en Firebase (Frontend Nube)
    # El puente local asume que, si recibe la orden, el frontend la autorizó.
    return {"valid": True, "type": "cloud"}

def consume_message():
    pass

def load_config():
    global GATEWAY_HOST, GATEWAY_PORT, GATEWAY_USER, GATEWAY_PASS, GATEWAY_BASE_URL, LOCAL_PORT
    LOCAL_PORT = 5000  # Default local port
    if os.path.exists(CONFIG_FILE):
        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                c = json.load(f)
                GATEWAY_HOST = c.get("host", GATEWAY_HOST)
                GATEWAY_PORT = c.get("port", GATEWAY_PORT)
                GATEWAY_USER = c.get("user", GATEWAY_USER)
                GATEWAY_PASS = c.get("pass", GATEWAY_PASS)
                LOCAL_PORT = c.get("local_port", LOCAL_PORT)
        except Exception as e:
            print("Error cargando config:", e)
    GATEWAY_BASE_URL = f"http://{GATEWAY_HOST}:{GATEWAY_PORT}"

def save_config():
    try:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump({
                "host": GATEWAY_HOST,
                "port": GATEWAY_PORT,
                "user": GATEWAY_USER,
                "pass": GATEWAY_PASS,
                "local_port": LOCAL_PORT
            }, f)
    except Exception as e:
        print("Error al guardar config:", e)

# Cargar configuraciones al arrancar
load_config()

def load_history():
    global send_history
    if os.path.exists(HISTORY_FILE):
        try:
            with open(HISTORY_FILE, "r", encoding="utf-8") as f:
                send_history.extend(json.load(f))
        except: pass

def save_history():
    try:
        with open(HISTORY_FILE, "w", encoding="utf-8") as f:
            json.dump(send_history, f)
    except: pass

# Cargar configuraciones al inicio
load_config()
load_license()

LOG_FILE = os.path.join(data_path, "sms_bridge.log")

# ─── Logging ────────────────────────────────────────────────
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(LOG_FILE, encoding="utf-8"),
        logging.StreamHandler(sys.stdout)
    ]
)
log = logging.getLogger("bridge")

# ─── Estado global ───────────────────────────────────────────
send_queue   = []
send_history = []
queue_lock   = threading.Lock()
worker_thread = None

campaign_status = {
    "running":    False,
    "paused":     False,
    "total":      0,
    "sent":       0,
    "failed":     0,
    "current":    "",
    "start_time": None,
    "delay":      5,
}

# ═══════════════════════════════════════════════════════════
#  FUNCIONES DEL SMS GATEWAY HTTP
# ═══════════════════════════════════════════════════════════

def gateway_auth():
    """Retorna el objeto de autenticación básica."""
    return (GATEWAY_USER, GATEWAY_PASS)


def gateway_is_online() -> tuple[bool, str, str]:
    """
    Verifica si el gateway está disponible haciendo GET /
    Retorna (online: bool, info: str, model: str)
    """
    try:
        r = req_lib.get(
            f"{GATEWAY_BASE_URL}/",
            auth=gateway_auth(),
            timeout=5
        )
        model = "SMS Gateway"
        try:
            resp_json = r.json()
            if isinstance(resp_json, dict) and "model" in resp_json:
                model = resp_json["model"]
        except Exception:
            pass
        return True, f"Gateway online (HTTP {r.status_code}) - {model}", model
    except req_lib.exceptions.ConnectionError:
        return False, f"No se puede conectar a {GATEWAY_BASE_URL}", ""
    except req_lib.exceptions.Timeout:
        return False, "Timeout — el celular no respondió", ""
    except Exception as e:
        return False, str(e), ""


def send_sms(phone: str, message: str) -> tuple[bool, str]:
    """
    Envía un SMS a través del gateway HTTP del celular.
    Normaliza el número antes de enviar.
    """
    phone = normalize_phone(phone)
    if not phone:
        return False, "Número de teléfono inválido"

    # ── Intento 1: API estándar (Android SMS Gateway app y similares)
    # POST /message  con body JSON estándar
    endpoints = [
        {
            "method": "POST",
            "url": f"{GATEWAY_BASE_URL}/message",
            "json": {
                "message":      message,
                "phoneNumbers": [phone]
            }
        },
        # ── Intento 2: Algunos gateways usan /send
        {
            "method": "POST",
            "url": f"{GATEWAY_BASE_URL}/send",
            "json": {
                "to":      phone,
                "message": message,
                "text":    message
            }
        },
        # ── Intento 3: Query params
        {
            "method": "GET",
            "url": f"{GATEWAY_BASE_URL}/send",
            "params": {
                "phone":   phone,
                "message": message,
                "to":      phone,
                "text":    message
            }
        },
    ]

    last_error = "Sin respuesta"
    for ep in endpoints:
        try:
            if ep["method"] == "POST":
                r = req_lib.post(
                    ep["url"],
                    json=ep.get("json"),
                    auth=gateway_auth(),
                    timeout=GATEWAY_TIMEOUT
                )
            else:
                r = req_lib.get(
                    ep["url"],
                    params=ep.get("params"),
                    auth=gateway_auth(),
                    timeout=GATEWAY_TIMEOUT
                )

            log.info(f"[Gateway] {ep['method']} {ep['url']} → HTTP {r.status_code} | {r.text[:120]}")

            if r.status_code in (200, 201, 202):
                # Verificar si la respuesta indica éxito real
                try:
                    resp_json = r.json()
                    # Algunos gateways devuelven {"status": "error"} con 200
                    if isinstance(resp_json, dict):
                        status = str(resp_json.get("status", "")).lower()
                        if status in ("error", "failed", "fail"):
                            last_error = resp_json.get("message", resp_json.get("error", r.text[:200]))
                            continue
                except Exception:
                    pass
                log.info(f"SMS enviado → {phone}")
                return True, f"Enviado (HTTP {r.status_code})"

            elif r.status_code == 401:
                last_error = "Credenciales incorrectas (401). Verifica usuario y contraseña."
                break  # No tiene sentido seguir probando con auth incorrecta

            else:
                last_error = f"HTTP {r.status_code}: {r.text[:200]}"

        except req_lib.exceptions.ConnectionError:
            last_error = f"Sin conexión a {GATEWAY_BASE_URL}. ¿La app está corriendo?"
            break
        except req_lib.exceptions.Timeout:
            last_error = f"Timeout ({GATEWAY_TIMEOUT}s) esperando respuesta del celular"
            break
        except Exception as e:
            last_error = str(e)

    log.warning(f"Fallo SMS → {phone}: {last_error}")
    return False, last_error


# ═══════════════════════════════════════════════════════════
#  WORKER DE CAMPAÑA (corre en hilo de fondo)
# ═══════════════════════════════════════════════════════════

def campaign_worker():
    global campaign_status
    log.info("Worker de campaña iniciado")

    while campaign_status["running"]:
        if campaign_status["paused"]:
            time.sleep(0.5)
            continue

        with queue_lock:
            if not send_queue:
                log.info("Cola vacía — campaña finalizada")
                break
            
            lic_status = check_license()
            if not lic_status["valid"]:
                log.warning("Licencia expirada o sin activar deteniendo campaña.")
                break
                
            task = send_queue.pop(0)

        phone   = task["phone"]
        message = task["message"]
        campaign_status["current"] = phone

        ok, detail = send_sms(phone, message)

        entry = {
            "phone":   phone,
            "message": message[:60] + ("..." if len(message) > 60 else ""),
            "status":  "sent" if ok else "failed",
            "detail":  detail,
            "time":    datetime.now().strftime("%H:%M:%S"),
        }

        with queue_lock:
            send_history.insert(0, entry)  # Guardar al principio para el UI
            save_history() # Guardar persistente
            if ok:
                campaign_status["sent"] += 1
                consume_message()
            else:
                campaign_status["failed"] += 1

        # Esperar el delay entre mensajes
        delay = campaign_status["delay"]
        elapsed = 0.0
        while elapsed < delay:
            if not campaign_status["running"]:
                break
            if not campaign_status["paused"]:
                elapsed += 0.5
            time.sleep(0.5)

    campaign_status["running"] = False
    campaign_status["current"] = ""
    log.info("Worker finalizado")


# ═══════════════════════════════════════════════════════════
#  FLASK APP — API REST para la interfaz web
# ═══════════════════════════════════════════════════════════

webapp_dir = os.path.join(base_path, 'web')
app = Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": "*"}})

@app.route("/")
def index():
    return send_from_directory(webapp_dir, "index.html")



@app.route("/api/license/status", methods=["GET"])
def api_license_status():
    return jsonify(check_license())

@app.route("/api/status", methods=["GET"])
def api_status():
    """Estado del bridge y del gateway del celular."""
    online, gw_info, model = gateway_is_online()
    elapsed = None
    if campaign_status["start_time"]:
        elapsed = int(time.time() - campaign_status["start_time"])

    device_name = model if model else "SMS Gateway"

    hist_sent = sum(1 for h in send_history if h.get("status") == "sent")
    hist_failed = sum(1 for h in send_history if h.get("status") != "sent")

    return jsonify({
        "bridge":  "online",
        "version": "3.0.0",
        "gateway": {
            "online":  online,
            "info":    gw_info,
            "host":    GATEWAY_HOST,
            "port":    GATEWAY_PORT,
            "model":   device_name
        },
        "devices": [{"serial": GATEWAY_HOST, "model": device_name, "state": "device"}] if online else [],
        "campaign": {
            **campaign_status,
            "sent":       hist_sent,
            "failed":     hist_failed,
            "pending":    len(send_queue),
            "elapsed_sec": elapsed,
        }
    })


@app.route("/api/gateway/config", methods=["GET"])
def api_gateway_get():
    """Retorna la configuración actual del gateway."""
    return jsonify({
        "host": GATEWAY_HOST,
        "port": GATEWAY_PORT,
        "user": GATEWAY_USER,
    })


@app.route("/api/gateway/config", methods=["POST"])
def api_gateway_set():
    """Actualiza la IP/puerto/credenciales del gateway en tiempo de ejecución."""
    global GATEWAY_HOST, GATEWAY_PORT, GATEWAY_USER, GATEWAY_PASS, GATEWAY_BASE_URL
    data = request.get_json(force=True) or {}
    if "host" in data:
        GATEWAY_HOST = data["host"]
    if "port" in data:
        GATEWAY_PORT = int(data["port"])
    if "user" in data:
        GATEWAY_USER = data["user"]
    if "pass" in data:
        GATEWAY_PASS = data["pass"]
    GATEWAY_BASE_URL = f"http://{GATEWAY_HOST}:{GATEWAY_PORT}"
    save_config()
    log.info(f"Gateway actualizado: {GATEWAY_BASE_URL} user={GATEWAY_USER}")
    return jsonify({"success": True, "base_url": GATEWAY_BASE_URL})


@app.route("/api/gateway/test", methods=["POST"])
def api_gateway_test():
    """Prueba la conexión con el gateway y opcionalmente envía un SMS de prueba."""
    data  = request.get_json(force=True) or {}
    phone = data.get("phone", "").strip()

    online, info, _ = gateway_is_online()
    if not online:
        return jsonify({"success": False, "error": info})

    if phone:
        ok, detail = send_sms(phone, "✅ SMS Sender Pro — Prueba de conexión exitosa")
        return jsonify({"success": ok, "gateway": info, "sms_detail": detail})

    return jsonify({"success": True, "gateway": info})


@app.route("/api/send/single", methods=["POST"])
def api_send_single():
    """Envía un SMS individual de inmediato."""
    data    = request.get_json(force=True) or {}
    phone   = data.get("phone", "").strip()
    message = data.get("message", "").strip()

    if not phone or not message:
        return jsonify({"success": False, "error": "Se requieren 'phone' y 'message'"}), 400

    lic_status = check_license()
    if not lic_status["valid"]:
        return jsonify({"success": False, "error": lic_status["reason"]}), 403

    online, gw_info, _ = gateway_is_online()
    if not online:
        return jsonify({"success": False, "error": f"Gateway offline: {gw_info}"}), 503

    ok, detail = send_sms(phone, message)
    if ok:
        consume_message()
    entry = {
        "phone":   phone,
        "message": message[:60] + ("..." if len(message) > 60 else ""),
        "status":  "sent" if ok else "failed",
        "detail":  detail,
        "time":    datetime.now().strftime("%H:%M:%S"),
    }
    with queue_lock:
        send_history.insert(0, entry)
        save_history()
    return jsonify({"success": ok, "detail": detail})


@app.route("/api/campaign/start", methods=["POST"])
def api_campaign_start():
    """Inicia una campaña masiva de SMS."""
    global worker_thread

    if campaign_status["running"]:
        return jsonify({"success": False, "error": "Ya hay una campaña en curso"}), 409

    data     = request.get_json(force=True) or {}
    contacts = data.get("contacts", [])
    message  = data.get("message", "").strip()
    delay    = float(data.get("delay", 5))

    if not contacts:
        return jsonify({"success": False, "error": "Lista de contactos vacía"}), 400

    # Validar que todos los contactos tendrán mensaje (propio o global)
    without_msg = [c for c in contacts if not c.get("message", "").strip() and not message]
    if without_msg:
        return jsonify({
            "success": False,
            "error": f"{len(without_msg)} contacto(s) no tienen mensaje. Agrega un mensaje por defecto o inclúyelo en el archivo."
        }), 400

    lic_status = check_license()
    if not lic_status["valid"]:
        return jsonify({"success": False, "error": lic_status["reason"]}), 403

    online, gw_info, _ = gateway_is_online()
    if not online:
        return jsonify({"success": False, "error": f"Gateway offline: {gw_info}"}), 503

    with queue_lock:
        send_queue.clear()
        # send_history.clear() # Ya no limpiamos el historial al iniciar campaña
        for c in contacts:
            phone_num = c.get("phone", "").strip()
            # Mensaje individual por contacto sobrescribe el mensaje de campaña
            contact_msg = c.get("message", "").strip()
            if contact_msg:
                msg = contact_msg.replace("{nombre}", c.get("name", "")).replace("{name}", c.get("name", ""))
            else:
                msg = message.replace("{nombre}", c.get("name", "")).replace("{name}", c.get("name", ""))
            if phone_num and msg:
                send_queue.append({"phone": phone_num, "message": msg})


        campaign_status.update({
            "running":    True,
            "paused":     False,
            "total":      len(send_queue),
            "sent":       0,
            "failed":     0,
            "current":    "",
            "start_time": time.time(),
            "delay":      delay,
        })

    worker_thread = threading.Thread(target=campaign_worker, daemon=True)
    worker_thread.start()
    log.info(f"Campaña iniciada: {len(contacts)} contactos, delay={delay}s")

    return jsonify({"success": True, "total": len(contacts), "message": "Campaña iniciada"})


@app.route("/api/campaign/pause", methods=["POST"])
def api_campaign_pause():
    if not campaign_status["running"]:
        return jsonify({"success": False, "error": "No hay campaña activa"}), 400
    campaign_status["paused"] = not campaign_status["paused"]
    state = "pausada" if campaign_status["paused"] else "reanudada"
    return jsonify({"success": True, "paused": campaign_status["paused"], "message": f"Campaña {state}"})


@app.route("/api/campaign/stop", methods=["POST"])
def api_campaign_stop():
    campaign_status["running"] = False
    campaign_status["paused"]  = False
    with queue_lock:
        send_queue.clear()
    return jsonify({"success": True, "message": "Campaña detenida"})


@app.route("/api/campaign/history", methods=["GET"])
def api_campaign_history():
    return jsonify({"history": list(send_history)})


@app.route("/api/campaign/history/clean_success", methods=["POST"])
def api_campaign_history_clean_success():
    global send_history
    with queue_lock:
        send_history = [h for h in send_history if h.get("status") != "sent"]
        save_history()
    return jsonify({"success": True, "message": "Historial exitoso limpiado", "history": list(send_history)})


@app.route("/api/campaign/history/clear", methods=["POST"])
def api_campaign_history_clear():
    global send_history
    with queue_lock:
        send_history = []
        save_history()
    return jsonify({"success": True, "message": "Historial limpiado", "history": list(send_history)})


@app.route("/<path:path>")
def serve_static(path):
    import os
    if os.path.exists(os.path.join(webapp_dir, path)):
        return send_from_directory(webapp_dir, path)
    return "Not Found", 404

# ─── Main ─────────────────────────────────────────────────
if __name__ == "__main__":
    print("=" * 58)
    print("  SMS Sender Pro - Bridge API v3.0")
    print(f"  Gateway: http://{GATEWAY_HOST}:{GATEWAY_PORT}")
    print(f"  Usuario: {GATEWAY_USER}")
    print(f"  API local: http://127.0.0.1:{LOCAL_PORT}")
    print("  Ctrl+C para detener")
    print("=" * 58)

    # Cargar historial
    load_history()

    # Verificar conexión al gateway al iniciar
    online, info, _ = gateway_is_online()
    if online:
        log.info(f"Gateway conectado: {info}")
    else:
        log.warning(f"Gateway no disponible: {info}")
        log.warning("    Asegurate de que la app del celular esté activa y en la misma red WiFi")

    import webbrowser
    import threading
    import time
    
    def open_browser():
        time.sleep(1.5)
        webbrowser.open("https://sms-sender-pro-367b3.web.app/")
        
    threading.Thread(target=open_browser, daemon=True).start()

    log.info(f"¡Atención! API local corriendo en puerto: {LOCAL_PORT}")
    app.run(host="127.0.0.1", port=LOCAL_PORT, debug=False, threaded=True)
