from machine import Pin, PWM
import time
import network
from umqtt.simple import MQTTClient

#   Configuración de Wi-Fi
WIFI_SSID = "Wokwi-GUEST" # Nombre de la red Wi-Fi para Wokwi
WIFI_PASSWORD = "" # Sin contraseña para Wokwi-GUEST

#   Configuración de MQTT (Adafruit IO)
MQTT_BROKER = "io.adafruit.com"
MQTT_PORT = 1883 # Puerto estándar de MQTT (para SSL sería 8833, pero es más complejo)
MQTT_CLIENT_ID = b"Nahuel_lorelli" # ID único de cliente MQTT. Usando  username como ID.
MQTT_USERNAME = "Nahuel_lorelli" # USERNAME DE ADAFRUIT IO!
MQTT_PASSWORD = "aio_jOMD29XbjfjxfoRdk6Lfadb0MFXq" #   AIO KEY   DE ADAFRUIT IO

# Tópicos MQTT para la cerradura (basados en Feeds de Adafruit IO)
# El formato es "USERNAME/feeds/NOMBRE_DEL_FEED"
TOPIC_STATUS = b"Nahuel_lorelli/feeds/cerradura-estado" #
TOPIC_DOOR_SENSOR = b"Nahuel_lorelli/feeds/puerta-estado" #
TOPIC_COMMAND = b"Nahuel_lorelli/feeds/cerradura-comando" #
TOPIC_EVENTS = b"Nahuel_lorelli/feeds/cerradura-eventos" # Nuevo tópico para eventos

# --- Pines GPIO del ESP32 (según tu diagrama de Wokwi) ---
# Keypad
ROWS_PINS = [Pin(21, Pin.IN, Pin.PULL_UP), Pin(19, Pin.IN, Pin.PULL_UP), Pin(18, Pin.IN, Pin.PULL_UP), Pin(5, Pin.IN, Pin.PULL_UP)]
COLS_PINS = [Pin(17, Pin.OUT), Pin(16, Pin.OUT), Pin(4, Pin.OUT), Pin(2, Pin.OUT)]

# Servo
SERVO_PIN = Pin(13)
servo = PWM(SERVO_PIN, freq=50)

# Slide Switch (Sensor de Contacto de Puerta)
DOOR_SENSOR_PIN = Pin(23, Pin.IN, Pin.PULL_UP)

# RGB LED (Configurado para Cátodo Común: Pin común a GND)
LED_R_PIN = Pin(25, Pin.OUT)
LED_G_PIN = Pin(26, Pin.OUT)
LED_B_PIN = Pin(27, Pin.OUT)

#   Teclas del Keypad //
KEYPAD_KEYS = [
    ['1', '2', '3', 'A'],
    ['4', '5', '6', 'B'],
    ['7', '8', '9', 'C'],
    ['*', '0', '#', 'D']
]

# --- Variables de la Cerradura ---
CORRECT_PASSWORD = "1234"
current_password_input = ""
MAX_PASSWORD_LENGTH = 4
is_door_locked = True

# --- Cliente MQTT global ---
client = None
last_ping_time = 0
PING_INTERVAL_MS = 30000 # Enviar un ping cada 30 segundos para mantener la conexión

# --- Funciones de Conectividad ---
def connect_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print(f"Conectando a red Wi-Fi: {ssid}...")
        wlan.connect(ssid, password)
        max_attempts = 10
        while not wlan.isconnected() and max_attempts > 0:
            print(".", end="")
            time.sleep(1)
            max_attempts -= 1
        if wlan.isconnected():
            print("\n¡Conectado a Wi-Fi!")
            print("Dirección IP:", wlan.ifconfig()[0])
            return True
        else:
            print("\n¡ERROR! No se pudo conectar a Wi-Fi.")
            return False
    return True

def mqtt_callback(topic, msg):
    print(f"Mensaje MQTT recibido en tópico '{topic.decode()}': '{msg.decode()}'")
    if topic.decode() == TOPIC_COMMAND.decode():
        command = msg.decode().strip().upper()
        if command == "LOCK":
            if not is_door_locked:
                lock_door()
                publish_event("Comando MQTT: Cerradura BLOQUEADA") ### NUEVO: Eventos de LOG
        elif command == "UNLOCK":
            if is_door_locked:
                unlock_door()
                publish_event("Comando MQTT: Cerradura DESBLOQUEADA") ### NUEVO: Eventos de LOG
        else:
            print(f"Comando desconocido: {command}")
            publish_event(f"Comando MQTT desconocido: {command}") ### NUEVO: Eventos de LOG

def connect_mqtt():
    global client, last_ping_time
    try:
        # Asegúrate de que MQTT_CLIENT_ID sea un byte string
        client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER, port=MQTT_PORT, user=MQTT_USERNAME, password=MQTT_PASSWORD, keepalive=60)
        client.set_callback(mqtt_callback)
        client.connect()
        print(f"¡Conectado al broker MQTT: {MQTT_BROKER}!")
        client.subscribe(TOPIC_COMMAND)
        print(f"Suscrito a tópico de comandos: {TOPIC_COMMAND.decode()}")
        last_ping_time = time.ticks_ms() # Reinicia el tiempo del último ping al conectar
        return True
    except Exception as e:
        print(f"¡ERROR! No se pudo conectar al broker MQTT: {e}")
        return False

# Funciones de publicación
def publish_status():
    global client
    status_msg = "BLOQUEADA" if is_door_locked else "DESBLOQUEADA"
    try:
        if client:
            client.publish(TOPIC_STATUS, status_msg.encode())
            print(f"MQTT: Publicado estado '{status_msg}' en '{TOPIC_STATUS.decode()}'")
    except Exception as e:
        print(f"ERROR al publicar estado MQTT: {e}")
        # Si falla al publicar, intenta reconectar
        reconnect_mqtt_safe()

def publish_door_state(state):
    global client
    # --- MODIFICACIÓN: Lógica corregida para el sensor de puerta ---
    # Si el pin es 0 (LOW con PULL_UP), significa que el contacto está CERRADO.
    # Si el pin es 1 (HIGH con PULL_UP), significa que el contacto está ABIERTO.
    door_state_msg = "CERRADA" if state == 0 else "ABIERTA"
    try:
        if client:
            client.publish(TOPIC_DOOR_SENSOR, door_state_msg.encode())
            print(f"MQTT: Publicado estado puerta '{door_state_msg}' en '{TOPIC_DOOR_SENSOR.decode()}'")
            publish_event(f"Puerta: {door_state_msg}") # Nuevo: Publica evento de cambio de estado de puerta
    except Exception as e:
        print(f"ERROR al publicar estado puerta MQTT: {e}")
        # Si falla al publicar, intenta reconectar
        reconnect_mqtt_safe()

def publish_event(event_message): ### NUEVA FUNCIÓN PARA EVENTOS
    global client
    try:
        if client:
            client.publish(TOPIC_EVENTS, event_message.encode())
            print(f"MQTT: Publicado evento '{event_message}' en '{TOPIC_EVENTS.decode()}'")
    except Exception as e:
        print(f"ERROR al publicar evento MQTT: {e}")
        reconnect_mqtt_safe()

def reconnect_mqtt_safe():
    global client
    print("Intentando reconectar MQTT de forma segura...")
    try:
        client.disconnect() # Desconecta limpiamente si estaba conectado
    except:
        pass # Ignora si ya estaba desconectado o hubo un error
    if not connect_mqtt(): # Intenta reconectar
        print("Reconexión MQTT fallida.")

# --- Funciones de Control ---
def set_rgb_color(r, g, b):
    LED_R_PIN.value(r)
    LED_G_PIN.value(g)
    LED_B_PIN.value(b)

def turn_off_rgb():
    set_rgb_color(0, 0, 0)

def set_servo_angle(angle):
    duty = int(25 + (angle / 180.0) * 100)
    servo.duty(duty)
    time.sleep_ms(500)

def lock_door():
    global is_door_locked
    set_servo_angle(0)
    is_door_locked = True
    print("Cerradura: BLOQUEADA")
    set_rgb_color(1, 0, 0) # Enciende LED Rojo
    time.sleep(10) # <--- DURACIÓN DE LUZ ROJA: 10 segundos
    turn_off_rgb()
    publish_status()
    publish_event("Cerradura: BLOQUEADA") # Nuevo: Publica evento de bloqueo

def unlock_door():
    global is_door_locked
    set_servo_angle(90)
    is_door_locked = False
    print("Cerradura: DESBLOQUEADA")
    set_rgb_color(0, 1, 0) # Enciende LED Verde
    time.sleep(5) # <--- DURACIÓN DE LUZ VERDE: 5 segundos
    turn_off_rgb()
    publish_status()
    publish_event("Cerradura: DESBLOQUEADA") # Nuevo: Publica evento de desbloqueo

# --- Lógica de Inicio ---
print("Cargando sistema de cerradura...")
turn_off_rgb()
time.sleep_ms(1000)

# 1. Conectar a Wi-Fi
if not connect_wifi(WIFI_SSID, WIFI_PASSWORD):
    print("No se pudo conectar a Wi-Fi, abortando.")
    while True:
        set_rgb_color(1,0,0)
        time.sleep_ms(500)
        turn_off_rgb()
        time.sleep_ms(500)

# 2. Conectar a MQTT (AHORA HABILITADO para Adafruit IO)
if not connect_mqtt():
    print("No se pudo conectar a MQTT, abortando.")
    while True:
        set_rgb_color(1,1,0) # Amarillo intermitente si no conecta MQTT
        time.sleep_ms(500)
        turn_off_rgb()
        time.sleep_ms(500)

lock_door()
time.sleep_ms(500)

last_door_state = DOOR_SENSOR_PIN.value()
publish_door_state(last_door_state)


# --- Bucle Principal del Programa ---
print("Iniciando bucle principal. Intenta presionar teclas del keypad.")
while True:
    # --- Manejar mensajes MQTT entrantes y mantener conexión ---
    try:
        if client:
            client.check_msg() # Procesa mensajes MQTT que llegan al ESP32
            # Envía un ping periódico para mantener la conexión viva
            if time.ticks_diff(time.ticks_ms(), last_ping_time) > PING_INTERVAL_MS:
                client.ping()
                last_ping_time = time.ticks_ms()
                print("MQTT: Enviando PING para mantener conexión.")
    except Exception as e:
        print(f"Error al verificar o mantener mensajes MQTT: {e}")
        reconnect_mqtt_safe()

    # --- Lectura del Keypad ---
    key_pressed = None
    for col_idx, col_pin in enumerate(COLS_PINS):
        col_pin.value(0) # Pone la columna actual en LOW
        for row_idx, row_pin in enumerate(ROWS_PINS):
            if not row_pin.value(): # Si la fila es LOW (significa que una tecla está siendo presionada)
                print(f"DEBUG Keypad: Tecla detectada en Fila {row_idx} y Columna {col_idx}.")
                key_pressed = KEYPAD_KEYS[row_idx][col_idx]
                while not row_pin.value(): # Espera a que la tecla sea liberada (debounce)
                    time.sleep_ms(20)
                time.sleep_ms(200) # <--- AJUSTE PARA KEYPAD: Retardo después de la liberación de tecla para mejor lectura
                break # Sale del bucle de filas
        col_pin.value(1) # Pone la columna actual en HIGH de nuevo
        if key_pressed: # Si ya encontró una tecla, sale del bucle de columnas
            break

    if key_pressed: # Si se detectó una tecla presionada
        print(f"Tecla presionada: {key_pressed}")
        if key_pressed == '*':
            current_password_input = ""
            print("Entrada de contraseña reiniciada.")
            set_rgb_color(0, 0, 1) # Azul para reinicio
            time.sleep(5) # <--- DURACIÓN DE LUZ AZUL: 5 segundos
            turn_off_rgb()
            publish_event("Keypad: Entrada de contraseña reiniciada") # Nuevo: Publica evento
        elif key_pressed == '#':
            print(f"Contraseña ingresada: {current_password_input}")
            if current_password_input == CORRECT_PASSWORD:
                print("Contraseña CORRECTA.")
                set_rgb_color(0, 1, 0) # Verde
                time.sleep_ms(1000) # LED verde para confirmación de contraseña (1 segundo)
                unlock_door() # Esta función tiene su propio delay de 5 segundos para el LED verde
                time.sleep_ms(5000) # Espera 5 segundos después de desbloquear, antes de intentar bloquear
                lock_door() # Esta función tiene su propio delay de 10 segundos para el LED rojo
                turn_off_rgb()
                publish_event("Keypad: Acceso Concedido") # Nuevo: Publica evento
            else:
                print("Contraseña INCORRECTA.")
                set_rgb_color(1, 0, 0) # Rojo
                time.sleep_ms(10000) # LED rojo para contraseña incorrecta (10 segundos)
                turn_off_rgb()
                publish_event(f"Keypad: Acceso Denegado (Contraseña: {current_password_input})") # Nuevo: Publica evento con contraseña
            current_password_input = "" # Reinicia la entrada después de la verificación
        else: # Si es un número o letra para la contraseña
            if len(current_password_input) < MAX_PASSWORD_LENGTH:
                current_password_input += key_pressed
                print(f"Entrada actual: {current_password_input}")
            else:
                print(f"Límite de caracteres alcanzado ({MAX_PASSWORD_LENGTH}). Presiona '*' para reiniciar o '#' para verificar.")
    
    #   Sensor de Puerta (1 o 0) / Cerrada o abierta
    current_door_physical_state = DOOR_SENSOR_PIN.value()
    if current_door_physical_state != last_door_state:
        print(f"Cambio en el estado de la puerta detectado: {current_door_physical_state}")
        publish_door_state(current_door_physical_state)
        last_door_state = current_door_physical_state

    # --- MODIFICACIÓN: Lógica de Alerta corregida ---
    # Si la cerradura está BLOQUEADA y la puerta está ABIERTA (current_door_physical_state == 1)
    if is_door_locked and current_door_physical_state == 1:
        print("¡ALERTA! Puerta abierta mientras la cerradura está BLOQUEADA.")
        set_rgb_color(1, 0, 0) # Rojo para alerta
        time.sleep_ms(1000) # Mantén la luz de alerta por un segundo
        turn_off_rgb()
        publish_event("ALERTA: Puerta abierta estando BLOQUEADA") # Nuevo: Publica evento de alerta
    # elif not is_door_locked and current_door_physical_state == 1: # Esta condición ya no es necesaria aquí, o podría ser para otra lógica específica.
    #     print("Puerta CERRADA mientras la cerradura está DESBLOQUEADA.") # Comentario incorrecto, 1 es ABIERTA
    #     pass

    time.sleep_ms(200) # Retardo principal del bucle, crucial para MQTT y otros eventos