#!/usr/bin/env python3
import dbus
import dbus.mainloop.glib
from gi.repository import AppIndicator3
from gi.repository import Gtk, GLib, GObject
import gi
import subprocess
import threading
import time
import sys
# Especificar las versiones antes de importar
gi.require_version('Gtk', '3.0')


class NetworkMenu(Gtk.Window):
    def __init__(self):
        super().__init__(title="Menú de Red")  # Título estático
        self.set_default_size(300, 200)
        self.set_border_width(10)

        # Crear una caja vertical para organizar los widgets
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        self.add(vbox)

        # Etiqueta para mostrar el estado de la red
        self.status_label = Gtk.Label(label="Estado de la red: Desconocido")
        vbox.pack_start(self.status_label, False, False, 0)

        # Construir el menú
        self.build_menu(vbox)

        # Inicializar D-Bus
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        self.bus = dbus.SystemBus()

        try:
            self.network_manager = self.bus.get_object(
                'org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager')
            self.network_manager_props = dbus.Interface(
                self.network_manager, 'org.freedesktop.DBus.Properties')
        except dbus.exceptions.DBusException as e:
            print(
                f"Error al conectar con NetworkManager a través de D-Bus: {e}", file=sys.stderr)
            sys.exit(1)

        # Suscribirse a las señales de cambio de propiedades
        self.bus.add_signal_receiver(
            self.on_properties_changed,
            dbus_interface='org.freedesktop.DBus.Properties',
            signal_name='PropertiesChanged',
            arg0='org.freedesktop.NetworkManager',
            path='/org/freedesktop/NetworkManager',
        )

        # Obtener estado inicial
        self.update_status_initial()

    def build_menu(self, vbox):
        # Botón Conectar WiFi
        self.item_connect = Gtk.Button(label='Conectar WiFi')
        self.item_connect.connect('clicked', self.connect_wifi)
        vbox.pack_start(self.item_connect, True, True, 0)

        # Botón Desconectar WiFi
        self.item_disconnect = Gtk.Button(label='Desconectar WiFi')
        self.item_disconnect.connect('clicked', self.disconnect_wifi)
        vbox.pack_start(self.item_disconnect, True, True, 0)

        # Botón Ver Redes Disponibles
        self.item_view_networks = Gtk.Button(label='Ver Redes Disponibles')
        self.item_view_networks.connect('clicked', self.show_network_list)
        vbox.pack_start(self.item_view_networks, True, True, 0)

        # Botón Olvidar Red
        self.item_forget_network = Gtk.Button(label='Olvidar Red')
        self.item_forget_network.connect('clicked', self.forget_network)
        vbox.pack_start(self.item_forget_network, True, True, 0)

        # Botón Crear AP
        self.item_create_ap = Gtk.Button(label='Crear AP')
        self.item_create_ap.connect('clicked', self.create_ap)
        vbox.pack_start(self.item_create_ap, True, True, 0)

        # Separador
        separator = Gtk.Separator()
        vbox.pack_start(separator, False, False, 10)

        # Botón Salir
        close_button = Gtk.Button(label='Cerrar')
        close_button.connect('clicked', self.quit)
        vbox.pack_start(close_button, False, False, 0)

    def create_ap(self, _):
        """Ejecuta el comando para abrir wihotspot-gui."""
        try:
            subprocess.run(['/usr/bin/wihotspot-gui'], check=True)
        except subprocess.CalledProcessError:
            self.show_notification("Error al iniciar wihotspot-gui")

    def update_status_initial(self):
        """Inicializa el estado de la conexión WiFi al abrir la aplicación."""
        try:
            wifi_state = self.get_wifi_state()
            ssid = self.get_connected_ssid()
            self.set_status(wifi_state, ssid)
        except dbus.exceptions.DBusException as e:
            print(f"Error al obtener el estado inicial: {e}", file=sys.stderr)

    def forget_network(self, _):
        """Función para olvidar una red guardada"""
        # Obtener la lista de redes WiFi guardadas de manera confiable
        saved_networks = subprocess.getoutput(
            "nmcli -t -f NAME,TYPE connection show | grep ':802-11-wireless$' | cut -d: -f1").splitlines()

        if not saved_networks:
            self.show_notification("No hay redes guardadas para olvidar.")
            return

        # Crear una ventana para seleccionar la red a olvidar
        window = Gtk.Window(title="Olvidar Red Guardada")
        window.set_default_size(300, 200)
        window.set_border_width(10)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)

        # Añadir botones para cada red guardada
        for network in saved_networks:
            button = Gtk.Button(label=network)
            button.connect("clicked", self.delete_network_profile, network)
            vbox.pack_start(button, True, True, 0)

        # Botón para cerrar la ventana
        close_button = Gtk.Button(label="Cerrar")
        close_button.connect("clicked", lambda x: window.destroy())
        vbox.pack_start(close_button, False, False, 0)

        window.add(vbox)
        window.show_all()

    def delete_network_profile(self, button, network):
        """Elimina el perfil de conexión guardado para la red especificada"""
        try:
            subprocess.run(
                ['nmcli', 'connection', 'delete', network], check=True)
            self.show_notification(f"Red '{network}' olvidada exitosamente.")
        except subprocess.CalledProcessError:
            self.show_notification(f"Error al olvidar la red '{network}'.")

    def connect_wifi(self, _):
        """Función llamada al hacer clic en el botón 'Conectar WiFi'.
           Muestra la lista de redes y permite al usuario seleccionar una."""
        self.show_network_list(None)

    def get_available_networks_list(self):
        """Obtiene la lista de redes WiFi junto con la intensidad de la señal y la banda (2.4 GHz o 5 GHz), sin duplicados y sin entradas vacías"""
        try:
            # Obtener SSID, señal y frecuencia
            output = subprocess.getoutput(
                "nmcli -t -f SSID,SIGNAL,FREQ dev wifi list")
            networks = {}
            for line in output.splitlines():
                if line:
                    # Separar el SSID, señal y frecuencia en tres variables
                    parts = line.split(":")

                    # Validación para evitar entradas vacías en SSID
                    ssid = parts[0].strip()
                    if not ssid:
                        continue  # Saltamos esta entrada si el SSID está vacío

                    signal_strength = int(parts[1].strip()) if len(
                        parts) > 1 and parts[1].isdigit() else 0
                    frequency = int(parts[2].strip().split()[0]) if len(
                        parts) > 2 and parts[2].strip().split()[0].isdigit() else 0

                    # Determinar la banda en función de la frecuencia
                    band = "5 GHz" if frequency >= 5000 else "2.4 GHz"

                    # Evitar duplicados: guardar solo la señal más alta para cada SSID
                    if ssid in networks:
                        if signal_strength > networks[ssid][0]:
                            networks[ssid] = (signal_strength, band)
                    else:
                        networks[ssid] = (signal_strength, band)

            # Convertir el diccionario a una lista de tuplas (SSID, señal, banda)
            return [(ssid, data[0], data[1]) for ssid, data in networks.items()]
        except Exception as e:
            print(f"Error al obtener la lista de redes disponibles: {
                  e}", file=sys.stderr)
            return []

    def show_network_list(self, _):
        # Llamar a la función para obtener la lista de redes con señal y banda
        networks = self.get_available_networks_list()

        if not networks:
            self.show_notification("No se encontraron redes disponibles.")
            return

        # Crear una ventana de lista de redes
        window = Gtk.Window(title="Redes Disponibles")
        window.set_default_size(300, 200)
        window.set_border_width(10)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)

        # Añadir botones para cada red disponible con la intensidad de señal y la banda
        for network, signal_strength, band in networks:
            # Mostramos el nombre de la red junto con su intensidad de señal y la banda (2.4 GHz o 5 GHz)
            button_label = f"{network} ({signal_strength}%, {band})"
            button = Gtk.Button(label=button_label)
            button.connect(
                "clicked", self.prompt_password_and_connect, network)
            vbox.pack_start(button, True, True, 0)

        # Botón para cerrar la ventana
        close_button = Gtk.Button(label="Cerrar")
        close_button.connect("clicked", lambda x: window.destroy())
        vbox.pack_start(close_button, False, False, 0)

        window.add(vbox)
        window.show_all()

    def prompt_password_and_connect(self, button, network):
        # Verificar si el perfil de la conexión ya existe
        existing_profile = subprocess.getoutput(
            f"nmcli -t -f NAME connection show | grep '^{network}$'")

        if existing_profile:
            # Si el perfil ya existe, intentar conectarse sin solicitar la contraseña
            self.show_notification(f"Conectando a {network}...")
            # None para indicar que no se requiere contraseña
            self.connect_to_network_with_nmcli(network, None)
        else:
            # Si no existe el perfil, solicitar la contraseña
            dialog = Gtk.Dialog(
                title=f"Conectar a {network}",
                transient_for=self,
                flags=0
            )
            dialog.add_buttons(
                Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                Gtk.STOCK_OK, Gtk.ResponseType.OK
            )

            # Crear un campo de entrada de texto para la contraseña
            box = dialog.get_content_area()
            label = Gtk.Label(label="Introduce la contraseña:")
            box.add(label)

            password_entry = Gtk.Entry()
            # Ocultar la contraseña al escribir
            password_entry.set_visibility(False)
            box.add(password_entry)
            dialog.show_all()

            # Ejecutar el diálogo y obtener la respuesta del usuario
            response = dialog.run()
            if response == Gtk.ResponseType.OK:
                password = password_entry.get_text()
                dialog.destroy()
                # Intentar conectar a la red con nmcli y configuración de seguridad
                self.connect_to_network_with_nmcli(network, password)
            else:
                dialog.destroy()
                print("Conexión cancelada por el usuario.")

    def connect_to_network_with_nmcli(self, ssid, password):
        # Detectar la interfaz de red WiFi principal
        try:
            interface = subprocess.getoutput(
                "nmcli device status | grep wifi | grep -v 'p2p' | awk '{print $1}'").strip()
            if not interface:
                self.show_notification(
                    "No se encontró una interfaz WiFi válida.")
                return
        except subprocess.CalledProcessError:
            self.show_notification("Error al obtener la interfaz de red.")
            return

        try:
            # Verificar si ya existe un perfil de conexión con el SSID
            existing_profile = subprocess.getoutput(
                f"nmcli -t -f NAME connection show | grep '^{ssid}$'")

            if existing_profile:
                # Si el perfil existe y se proporciona una contraseña, actualizarlo
                if password:
                    subprocess.run([
                        'nmcli', 'connection', 'modify', ssid,
                        'wifi-sec.key-mgmt', 'wpa-psk', 'wifi-sec.psk', password,
                        'connection.interface-name', interface
                    ], check=True)
                    self.show_notification(f"Perfil actualizado: {ssid}")
            else:
                # Si el perfil no existe, crearlo
                subprocess.run([
                    'nmcli', 'connection', 'add', 'type', 'wifi',
                    'ifname', interface, 'con-name', ssid, 'ssid', ssid,
                    'wifi-sec.key-mgmt', 'wpa-psk', 'wifi-sec.psk', password
                ], check=True)
                self.show_notification(f"Perfil creado: {ssid}")

            # Intentar conectar usando el perfil existente o actualizado
            subprocess.run(['nmcli', 'connection', 'up', ssid], check=True)
            self.show_notification(f"Conectado a {ssid}")
        except subprocess.CalledProcessError:
            self.show_notification(f"Error al intentar conectar a {ssid}")

    def disconnect_wifi(self, _):
        """Función para desconectar la red WiFi actual."""
        try:
            # Intentar desconectar la red activa
            subprocess.run(['nmcli', 'networking', 'off'], check=True)
            # Reactivar después de desconectar
            subprocess.run(['nmcli', 'networking', 'on'], check=True)
            self.show_notification("WiFi Desconectado.")
        except subprocess.CalledProcessError:
            self.show_notification("Error al desconectar WiFi.")

    def quit(self, _):
        Gtk.main_quit()

    def get_available_networks_list(self):
        """Obtiene la lista de redes WiFi junto con la intensidad de la señal y la banda (2.4 GHz o 5 GHz), sin duplicados y sin entradas vacías"""
        try:
            # Obtener SSID, señal y frecuencia
            output = subprocess.getoutput(
                "nmcli -t -f SSID,SIGNAL,FREQ dev wifi list")
            networks = {}
            for line in output.splitlines():
                if line:
                    # Separar el SSID, señal y frecuencia en tres variables
                    parts = line.split(":")

                    # Validación para evitar entradas vacías en SSID
                    ssid = parts[0].strip()
                    if not ssid:
                        continue  # Saltamos esta entrada si el SSID está vacío

                    signal_strength = int(parts[1].strip()) if len(
                        parts) > 1 and parts[1].isdigit() else 0
                    frequency = int(parts[2].strip().split()[0]) if len(
                        parts) > 2 and parts[2].strip().split()[0].isdigit() else 0

                    # Determinar la banda en función de la frecuencia
                    band = "5 GHz" if frequency >= 5000 else "2.4 GHz"

                    # Evitar duplicados: guardar solo la señal más alta para cada SSID
                    if ssid in networks:
                        if signal_strength > networks[ssid][0]:
                            networks[ssid] = (signal_strength, band)
                    else:
                        networks[ssid] = (signal_strength, band)

            # Convertir el diccionario a una lista de tuplas (SSID, señal, banda)
            return [(ssid, data[0], data[1]) for ssid, data in networks.items()]
        except Exception as e:
            print(f"Error al obtener la lista de redes disponibles: {
                  e}", file=sys.stderr)
            return []

    def show_notification(self, message):
        """Muestra una notificación en el sistema"""
        subprocess.run(['notify-send', message])

    def on_properties_changed(self, interface, changed_properties, invalidated_properties):
        if 'State' in changed_properties:
            wifi_state = self.get_wifi_state()
            ssid = self.get_connected_ssid()
            self.set_status(wifi_state, ssid)

    def set_status(self, wifi_state, ssid):
        if wifi_state == 'disabled':
            status_text = "WiFi Apagado"
            # Deshabilitar el botón de Desconectar WiFi y habilitar Conectar WiFi
            self.item_disconnect.set_sensitive(False)
            self.item_connect.set_sensitive(True)
        elif wifi_state == 'enabled' and not ssid:
            status_text = "WiFi Encendido (No Conectado)"
            # Habilitar ambos botones
            self.item_disconnect.set_sensitive(True)
            self.item_connect.set_sensitive(True)
        elif wifi_state == 'enabled' and ssid:
            status_text = f"Conectado a: {ssid}"
            # Deshabilitar el botón de Conectar WiFi y habilitar Desconectar WiFi
            self.item_connect.set_sensitive(False)
            self.item_disconnect.set_sensitive(True)
        else:
            status_text = "WiFi Estado Desconocido"
            # Habilitar ambos botones por defecto
            self.item_connect.set_sensitive(True)
            self.item_disconnect.set_sensitive(True)

        print(f"Actualizando estado: '{status_text}'")
        self.status_label.set_text(f"Estado de la red: {status_text}")

    def get_wifi_state(self):
        try:
            output = subprocess.getoutput("nmcli radio wifi")
            print(f"Estado WiFi: {output}")
            return output.lower()
        except Exception as e:
            print(f"Error al obtener el estado de WiFi: {e}", file=sys.stderr)
            return "unknown"

    def get_connected_ssid(self):
        try:
            # Usar iwgetid para obtener el SSID conectado
            ssid = subprocess.getoutput("iwgetid -r").strip()
            if ssid:
                print(f"SSID conectado obtenido con iwgetid: {ssid}")
                return ssid
            else:
                # Fallback a nmcli si iwgetid no retorna SSID
                output = subprocess.getoutput(
                    "nmcli -t -f ACTIVE,SSID dev wifi | grep '^yes' | cut -d':' -f2").strip()
                if output:
                    print(f"SSID conectado obtenido con nmcli: {output}")
                    return output
                else:
                    print(f"SSID conectado obtenido: None")
                    return None
        except Exception as e:
            print(f"Error al obtener SSID conectado: {e}", file=sys.stderr)
            return None


def main():
    # Para evitar múltiples instancias, usar una lock con D-Bus o similar
    # Aquí, simplemente se muestra la ventana
    window = NetworkMenu()
    window.connect("destroy", Gtk.main_quit)
    window.show_all()
    Gtk.main()


if __name__ == "__main__":
    main()
