import os
import re
import time
import requests
from flask import Flask, request, jsonify, render_template, abort

app = Flask(__name__)

@app.after_request
def add_security_headers(response):
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, PUT, DELETE"
    response.headers["Access-Control-Allow-Headers"] = "*"
    response.headers["X-Frame-Options"] = "ALLOWALL"
    response.headers["Content-Security-Policy"] = "frame-ancestors *"
    return response

# Configuración del servicio
IOACS_SERVER_URL = os.getenv("IOACS_SERVER_URL", "http://15.235.32.113:5502").rstrip("/")
IOACS_API_TOKEN = os.getenv("IOACS_API_TOKEN", "ioacs_1d969611748e4694da15804eb12c6049d1deca9d85943dfc")
IOACS_TIMEOUT = int(os.getenv("IOACS_TIMEOUT", "30"))

def get_headers():
    """Genera las cabeceras de autorización Bearer para el servidor IoACS"""
    return {
        "Authorization": f"Bearer {IOACS_API_TOKEN}",
        "Content-Type": "application/json",
        "Accept": "application/json"
    }

def format_uptime(seconds):
    """Convierte segundos en formato amigable (días, horas, minutos)"""
    try:
        sec = int(seconds)
        days = sec // 86400
        hours = (sec % 86400) // 3600
        minutes = (sec % 3600) // 60
        if days > 0:
            return f"{days}d {hours}h {minutes}m"
        if hours > 0:
            return f"{hours}h {minutes}m"
        return f"{minutes}m"
    except (ValueError, TypeError):
        return "N/D"

def evaluate_signal_quality(rx_power_str):
    """Evalúa la calidad del enlace óptico en base al valor en dBm"""
    try:
        val = float(str(rx_power_str).replace("dBm", "").strip())
        if val >= -24.0:
            return {"level": "optimal", "label": "Excelente", "color": "#10b981", "val": val}
        elif val >= -27.0:
            return {"level": "warning", "label": "Aceptable", "color": "#f59e0b", "val": val}
        else:
            return {"level": "critical", "label": "Crítica / Débil", "color": "#ef4444", "val": val}
    except (ValueError, TypeError):
        return {"level": "unknown", "label": "Normal", "color": "#60a5fa", "val": None}


def fetch_device_detail_from_acs(device_id):
    """
    Consulta get-device-detail.php manejando codificaciones especiales de device_id
    """
    url = f"{IOACS_SERVER_URL}/api/get-device-detail.php"
    # 1. Probar con el ID recibido directamente
    try:
        resp = requests.get(url, headers=get_headers(), params={"device_id": device_id}, timeout=IOACS_TIMEOUT)
        if resp.status_code == 200:
            data = resp.json()
            if data.get("success") and data.get("device"):
                return data.get("device")
    except Exception:
        pass

    # 2. Si contiene %2D o guiones, intentar con doble urlencode si fue decodificado
    if "%" in device_id or "-" in device_id:
        try:
            # En GenieACS IDs como C4CD50-FD714GS1%2DR850-CDTC505900DE requieren %252D
            encoded_id = device_id.replace("%2D", "%252D").replace("%2d", "%252D")
            if encoded_id != device_id:
                resp = requests.get(f"{url}?device_id={encoded_id}", headers=get_headers(), timeout=IOACS_TIMEOUT)
                if resp.status_code == 200:
                    data = resp.json()
                    if data.get("success") and data.get("device"):
                        return data.get("device")
        except Exception:
            pass

    return None


# =========================================================================
# RUTAS DE INTERFAZ VISUAL
# =========================================================================

@app.route("/")
def index():
    """Página principal del visor IoPlay-ACS (embebida o directa)"""
    device_id = request.args.get("device_id") or request.args.get("id") or ""
    serial = request.args.get("serial") or request.args.get("onu_id") or ""
    owner_id = request.args.get("owner_id") or request.args.get("customer_id") or ""
    subscriber_name = request.args.get("name") or request.args.get("subscriber_name") or ""

    return render_template(
        "viewer.html",
        device_id=device_id,
        serial=serial,
        owner_id=owner_id,
        subscriber_name=subscriber_name,
        ioacs_server=IOACS_SERVER_URL
    )


# =========================================================================
# RUTAS DE API REST / PROXY TR-069
# =========================================================================

@app.route("/api/status")
def api_status():
    """Verifica la salud de la conexión con el servidor IoACS remoto"""
    start_t = time.time()
    try:
        url = f"{IOACS_SERVER_URL}/api/dashboard-stats.php"
        resp = requests.get(url, headers=get_headers(), timeout=10)
        elapsed = round((time.time() - start_t) * 1000, 2)
        
        if resp.status_code == 200:
            data = resp.json()
            return jsonify({
                "status": "success",
                "connected": True,
                "latency_ms": elapsed,
                "server_url": IOACS_SERVER_URL,
                "stats": data.get("stats", {})
            })
        else:
            return jsonify({
                "status": "error",
                "connected": False,
                "status_code": resp.status_code,
                "latency_ms": elapsed,
                "server_url": IOACS_SERVER_URL,
                "message": f"Servidor respondió con código HTTP {resp.status_code}"
            }), 502
    except Exception as e:
        elapsed = round((time.time() - start_t) * 1000, 2)
        return jsonify({
            "status": "error",
            "connected": False,
            "latency_ms": elapsed,
            "server_url": IOACS_SERVER_URL,
            "message": str(e)
        }), 500


@app.route("/api/devices")
def api_devices():
    """Lista dispositivos registrados en GenieACS con paginación"""
    limit = request.args.get("limit", 20, type=int)
    skip = request.args.get("skip", 0, type=int)
    parser = request.args.get("parser", "fast")

    try:
        url = f"{IOACS_SERVER_URL}/api/get-devices.php"
        params = {"limit": limit, "skip": skip, "parser": parser}
        resp = requests.get(url, headers=get_headers(), params=params, timeout=IOACS_TIMEOUT)
        return jsonify(resp.json()), resp.status_code
    except Exception as e:
        return jsonify({"success": False, "message": f"Error conectando a IoACS: {e}"}), 500


VENDOR_MAP = {
    "opti": "4f505449",
    "hwtc": "48575443",
    "cdtc": "43445443",
    "zteg": "5a544547",
    "rteg": "52544547",
    "zxic": "5a584943",
    "alcl": "414c434c",
    "vsol": "56534f4c",
}
HEX_VENDOR_MAP = {v: k for k, v in VENDOR_MAP.items()}

def generate_serial_variants(q):
    """Genera variantes de búsqueda para números de serie de ONUs (Hex, ASCII, sufijo)"""
    clean = q.replace(":", "").replace("-", "").replace(" ", "").lower()
    variants = {clean}
    
    # 1. Prefijo texto a prefijo hexadecimal (ej: opti41f886e5 -> 4f50544941f886e5)
    for vendor, hex_code in VENDOR_MAP.items():
        if clean.startswith(vendor):
            suffix = clean[len(vendor):]
            variants.add(hex_code + suffix)
            if len(suffix) >= 4:
                variants.add(suffix)

    # 2. Prefijo hexadecimal a prefijo texto (ej: 4f50544964903433 -> opti64903433)
    for hex_code, vendor in HEX_VENDOR_MAP.items():
        if clean.startswith(hex_code):
            suffix = clean[len(hex_code):]
            variants.add(vendor + suffix)
            if len(suffix) >= 4:
                variants.add(suffix)

    return variants


@app.route("/api/device/lookup")
def api_device_lookup():
    """Busca un equipo por Serial Number o Dirección MAC con soporte multi-formato"""
    q = (request.args.get("q") or request.args.get("serial") or request.args.get("mac") or "").strip()
    if not q:
        return jsonify({"success": False, "message": "Parámetro 'q' o 'serial' requerido"}), 400

    variants = generate_serial_variants(q)

    try:
        url = f"{IOACS_SERVER_URL}/api/get-devices.php"
        resp = requests.get(url, headers=get_headers(), params={"limit": 100}, timeout=IOACS_TIMEOUT)
        if resp.status_code != 200:
            return jsonify(resp.json()), resp.status_code

        data = resp.json()
        devices = data.get("devices", [])
        matched = None

        for d in devices:
            sn = str(d.get("serial_number", "")).lower()
            sn2 = str(d.get("serial_number_2", "")).lower()
            mac = str(d.get("mac_address", "")).replace(":", "").replace("-", "").lower()
            dev_id = str(d.get("device_id", "")).lower()

            for var in variants:
                if (var in sn or var in sn2 or var in mac or var in dev_id or 
                    sn in var or sn2 in var or dev_id in var):
                    matched = d
                    break
            if matched:
                break

        if matched:
            # Obtener detalle enriquecido
            real_id = matched.get("device_id")
            detail = fetch_device_detail_from_acs(real_id)
            if detail:
                matched.update(detail)
            
            rx = matched.get("rx_power")
            matched["signal_eval"] = evaluate_signal_quality(rx)
            matched["uptime_formatted"] = format_uptime(matched.get("uptime", 0))

            return jsonify({"success": True, "device": matched})
        else:
            return jsonify({
                "success": False, 
                "message": f"El módem con serial '{q}' no se encuentra registrado en el servidor TR-069 (GenieACS).",
                "searched_serial": q
            }), 404

    except Exception as e:
        return jsonify({"success": False, "message": f"Error en búsqueda: {e}"}), 500


@app.route("/api/device/<path:device_id>")
def api_device_detail(device_id):
    """Obtiene el detalle completo y telemetría de una ONU/CPE"""
    try:
        # 1. Intentar obtener detalle
        device = fetch_device_detail_from_acs(device_id)

        # 2. Si no se encontró por device_id directo, buscar en la lista por serial o coincidencia
        if not device:
            devs_resp = requests.get(f"{IOACS_SERVER_URL}/api/get-devices.php", headers=get_headers(), params={"limit": 100}, timeout=IOACS_TIMEOUT)
            if devs_resp.status_code == 200:
                devices = devs_resp.json().get("devices", [])
                clean_q = device_id.replace(":", "").replace("-", "").lower()
                for d in devices:
                    sn = str(d.get("serial_number", "")).lower()
                    sn2 = str(d.get("serial_number_2", "")).lower()
                    mac = str(d.get("mac_address", "")).replace(":", "").replace("-", "").lower()
                    dev_id = str(d.get("device_id", "")).lower()

                    if clean_q in sn or clean_q in sn2 or clean_q in mac or clean_q in dev_id or device_id.lower() in dev_id:
                        real_id = d.get("device_id")
                        detail = fetch_device_detail_from_acs(real_id)
                        device = detail if detail else d
                        break

        if not device:
            return jsonify({"success": False, "message": f"Dispositivo '{device_id}' no encontrado"}), 404

        # Enriquecer telemetría
        rx = device.get("rx_power")
        device["signal_eval"] = evaluate_signal_quality(rx)
        device["uptime_formatted"] = format_uptime(device.get("uptime", 0))

        return jsonify({
            "success": True,
            "device": device
        })
    except Exception as e:
        return jsonify({"success": False, "message": f"Error obteniendo telemetría: {e}"}), 500


@app.route("/api/device/<path:device_id>/wifi", methods=["POST"])
def api_update_wifi(device_id):
    """Actualiza SSID y contraseña de WiFi para banda 2.4 GHz o 5 GHz"""
    payload = request.get_json(silent=True) or request.form or {}
    
    wifi_ssid = payload.get("wifi_ssid", "").strip()
    wifi_password = payload.get("wifi_password", "").strip()
    wlan_index = str(payload.get("wlan_index", "1")).strip()
    security_mode = payload.get("security_mode", "WPA2PSK").strip()

    if not wifi_ssid:
        return jsonify({"success": False, "message": "El nombre de la red WiFi (SSID) no puede estar vacío"}), 400

    if security_mode != "None" and (len(wifi_password) < 8 or len(wifi_password) > 12):
        return jsonify({"success": False, "message": "La contraseña de WiFi debe tener entre 8 y 12 caracteres"}), 400

    body = {
        "device_id": device_id,
        "wlan_index": wlan_index,
        "wifi_ssid": wifi_ssid,
        "wifi_password": wifi_password,
        "security_mode": security_mode
    }

    try:
        url = f"{IOACS_SERVER_URL}/api/update-wifi-config.php"
        resp = requests.post(url, headers=get_headers(), json=body, timeout=IOACS_TIMEOUT)
        res_data = resp.json() if resp.content else {}

        # Limpiar mensajes técnicos para una respuesta amigable
        msg = res_data.get("message", "Configuración de WiFi enviada al equipo")
        if "Invalid virtual parameter return value" in msg:
            msg = "La orden fue recibida por el router y se aplicará de inmediato."

        return jsonify({
            "success": res_data.get("success", True),
            "message": msg,
            "data": {
                "device_id": device_id,
                "wlan_index": wlan_index,
                "wifi_ssid": wifi_ssid,
                "band": "2.4 GHz" if wlan_index == "1" else "5 GHz"
            }
        }), resp.status_code

    except Exception as e:
        return jsonify({"success": False, "message": f"Error aplicando configuración WiFi: {e}"}), 500


@app.route("/api/device/<path:device_id>/reboot", methods=["POST"])
def api_reboot_device(device_id):
    """Envía la orden de reinicio remoto a la ONU"""
    body = {
        "device_id": device_id,
        "action": "reboot"
    }

    try:
        url = f"{IOACS_SERVER_URL}/api/device-action.php"
        resp = requests.post(url, headers=get_headers(), json=body, timeout=IOACS_TIMEOUT)
        res_data = resp.json() if resp.content else {}

        return jsonify({
            "success": res_data.get("success", True),
            "message": res_data.get("message", "Orden de reinicio enviada correctamente al router. Estará disponible en 1 a 2 minutos."),
            "task_status": res_data.get("task_status", "applied")
        }), resp.status_code
    except Exception as e:
        return jsonify({"success": False, "message": f"Error enviando orden de reinicio: {e}"}), 500


@app.route("/api/device/<path:device_id>/summon", methods=["POST"])
def api_summon_device(device_id):
    """Fuerza un Connection Request inmediato para refrescar datos"""
    body = {"device_id": device_id}
    try:
        url = f"{IOACS_SERVER_URL}/api/summon-device.php"
        resp = requests.post(url, headers=get_headers(), json=body, timeout=IOACS_TIMEOUT)
        res_data = resp.json() if resp.content else {}
        return jsonify(res_data), resp.status_code
    except Exception as e:
        return jsonify({"success": False, "message": f"Error forzando sincronización: {e}"}), 500


@app.route("/api/config", methods=["GET", "POST"])
def api_config():
    """Consulta o actualiza en caliente la configuración del servidor IoACS"""
    global IOACS_SERVER_URL, IOACS_API_TOKEN, IOACS_TIMEOUT

    if request.method == "POST":
        data = request.get_json(silent=True) or request.form or {}
        if "server_url" in data and data["server_url"]:
            IOACS_SERVER_URL = data["server_url"].rstrip("/")
        if "api_token" in data and data["api_token"]:
            IOACS_API_TOKEN = data["api_token"].strip()
        if "timeout" in data and data["timeout"]:
            try:
                IOACS_TIMEOUT = int(data["timeout"])
            except ValueError:
                pass

    return jsonify({
        "status": "success",
        "config": {
            "server_url": IOACS_SERVER_URL,
            "token_masked": IOACS_API_TOKEN[:10] + "..." + IOACS_API_TOKEN[-6:] if len(IOACS_API_TOKEN) > 16 else "***",
            "timeout": IOACS_TIMEOUT
        }
    })


if __name__ == "__main__":
    port = int(os.getenv("PORT", 5542))
    app.run(host="0.0.0.0", port=port, debug=False)
