import urequests
import os
import hashlib
import json
import machine
import network

WIFI_SSID     = "casa"
WIFI_PASSWORD = "lgdlg123"
SERVER   = "http://tv999.ddns.net/updater"
AUTH_KEY = "lgdlg123"
DEVICE   = "nano-esp32-01"

# Ficheros que NUNCA se actualizan por seguridad
PROTECTED = {"updater.py", "boot.py"}

HEADERS = {
    "X-Auth-Key": AUTH_KEY,
    "Content-Type": "application/json"
}

def md5(data: str) -> str:
    """Calcula MD5 de un string."""
    h = hashlib.md5()
    h.update(data.encode('utf-8'))
    # Convertir bytes a hex string
    return ''.join('{:02x}'.format(b) for b in h.digest())

def fetch_upgrade_list() -> list:
    """
    Descarga upgrade.txt y retorna lista de (fichero, hash_esperado).
    Formato de cada línea: nombre.py|md5hash
    """
    try:
        r = urequests.get(SERVER + "/get_file.php?f=upgrade.txt",
                          headers=HEADERS, timeout=10)
        if r.status_code == 401:
            print("[updater] Auth error fetching upgrade.txt")
            r.close()
            return []
        if r.status_code != 200:
            print("[updater] upgrade.txt not available:", r.status_code)
            r.close()
            return []

        lines = r.text.strip().split('\n')
        r.close()

        result = []
        for line in lines:
            line = line.strip()
            print(line)
            if not line or '|' not in line:
                continue
            parts = line.split('|', 1)
            filename, expected_md5 = parts[0].strip(), parts[1].strip()

            if filename in PROTECTED:
                print("[updater] Skipping protected file:", filename)
                continue

            result.append((filename, expected_md5))

        return result

    except Exception as e:
        print("[updater] Error fetching upgrade list:", e)
        return []


def download_file(filename: str, expected_md5: str) -> tuple:
    """
    Descarga un fichero, verifica MD5 y lo guarda de forma segura.
    Retorna (True, None) si ok, (False, motivo) si error.
    """
    tmp_path = "/tmp_" + filename

    try:
        r = urequests.get(
            SERVER + "/get_file.php?f=" + filename,
            headers=HEADERS,
            timeout=20
        )

        if r.status_code == 401:
            r.close()
            return False, "auth_error"
        if r.status_code != 200:
            r.close()
            return False, "http_" + str(r.status_code)

        content = r.text
        r.close()

        # Verificar MD5
        actual_md5 = md5(content)
        if actual_md5 != expected_md5:
            print("[updater] MD5 mismatch for", filename)
            print("  expected:", expected_md5)
            print("  actual:  ", actual_md5)
            return False, "md5_mismatch"

        # Guardar en fichero temporal
        with open(tmp_path, 'w') as f:
            f.write(content)

        # Reemplazar fichero definitivo de forma atómica
        try:
            os.remove(filename)
        except OSError:
            pass  # No existía, no pasa nada

        os.rename(tmp_path, filename)
        print("[updater] Updated:", filename)
        return True, None

    except Exception as e:
        # Limpiar temporal si quedó a medias
        try:
            os.remove(tmp_path)
        except:
            pass
        return False, str(e)


def send_confirmation(updated: list, errors: list):
    """Notifica al servidor el resultado de la actualización."""
    status = "ok" if not errors else ("partial" if updated else "failed")
    payload = json.dumps({
        "device":  DEVICE,
        "status":  status,
        "files":   updated,
        "errors":  errors
    })
    try:
        r = urequests.post(
            SERVER + "/confirm.php",
            data=payload,
            headers=HEADERS,
            timeout=10
        )
        print("[updater] Confirmation sent, server response:", r.status_code)
        r.close()
    except Exception as e:
        print("[updater] Error sending confirmation:", e)


def check_and_update() -> bool:
    """
    Punto de entrada principal.
    Retorna True si se actualizó algo y hay que reiniciar.
    """
    upgrade_list = fetch_upgrade_list()

    if not upgrade_list:
        print("[updater] Nothing to update.")
        return False

    print("[updater] Files to update:", [f for f, _ in upgrade_list])

    updated = []
    errors  = []

    for filename, expected_md5 in upgrade_list:
        ok, reason = download_file(filename, expected_md5)
        if ok:
            updated.append(filename)
        else:
            errors.append(filename + ":" + reason)
            print("[updater] Failed:", filename, "-", reason)

    send_confirmation(updated, errors)

    if updated:
        print("[updater] Update complete. Rebooting in 3s...")
        return True

    return False

def connect_wifi(wlan):
    wlan.active(True)
    # Power saving mode → reduce consumo WiFi ~30%
#    wlan.config(pm=0xa11140)
#    wlan.ifconfig(('192.168.1.10', '255.255.255.0', '192.168.1.1', '8.8.8.8'))
    wlan.connect(WIFI_SSID, WIFI_PASSWORD)
    
# no funciona, pero probar de nuevo
# bsddi
# channel
# txpower=8

# no funciona
#    wlan.connect(WIFI_SSID, WIFI_PASSWORD, listen_interval=5)  # medium value
#    wlan.connect(WIFI_SSID, WIFI_PASSWORD, listen_interval=30) # optimum value
#wlan.connect('ssid', 'password', listen_interval=5)
    print("Conectando a WiFi")
    tries=0
    connect=False
    while tries <= 1 and connect == False:
        print("Intento ", tries)
        try:
            for _ in range(40):
#        led.on()
                if wlan.isconnected():
                    break
                print(".", end="")
#        led.off()
                time.sleep(0.2)

            if not wlan.isconnected():
                raise RuntimeError("=====> No se pudo conectar a WiFi")
#                led.off()
        
            ip = wlan.ifconfig()[0]
            print(f"\n✓ Conectado! IP: {ip}")
            connect=True
        except:
            print("=====> Error conectando a WiFi")
            wlan.disconnect()
            wlan.active(False)
#            wlan.deinit()
        tries=tries+1
#    led.on()
    return ip

wlan = network.WLAN(network.STA_IF)

connect_wifi(wlan)

check_and_update()
