from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QVBoxLayout, QWidget, QMessageBox, QGroupBox, QCheckBox, QFileDialog, QRadioButton
from PyQt5.QtCore import Qt
import paramiko
import re
import networkx as nx
from pyvis.network import Network
from concurrent.futures import ThreadPoolExecutor, TimeoutError
import sys

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.passthrough_ips = ["10.100.100.113", "172.28.160.1", "172.28.20.1", "172.28.192.1"]  # Pas geçilecek IP adresleri

        self.setWindowTitle("Network Topoloji Çizici")
        self.setGeometry(100, 100, 400, 300)  # Yüksekliği artırıldı

        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout()

        # IP Girişi
        ip_group_box = QGroupBox("Cihaz IP:")
        ip_layout = QVBoxLayout()
        self.ip_input = QLineEdit(self)
        ip_layout.addWidget(self.ip_input)
        ip_group_box.setLayout(ip_layout)
        layout.addWidget(ip_group_box)

        # Kullanıcı Adı Girişi
        username_group_box = QGroupBox("Kullanıcı Adı:")
        username_layout = QVBoxLayout()
        self.username_input = QLineEdit(self)
        username_layout.addWidget(self.username_input)
        username_group_box.setLayout(username_layout)
        layout.addWidget(username_group_box)

        # Şifre Girişi
        password_group_box = QGroupBox("Şifre:")
        password_layout = QVBoxLayout()
        self.password_input = QLineEdit(self)
        self.password_input.setEchoMode(QLineEdit.Password)
        password_layout.addWidget(self.password_input)
        password_group_box.setLayout(password_layout)
        layout.addWidget(password_group_box)

        # Anahtar İsimlerini Gösterme Seçeneği
        self.show_switch_names_checkbox = QCheckBox("Switch İsimlerini Göster (IP Gösterimi için tik'i kaldır)")
        layout.addWidget(self.show_switch_names_checkbox)

        # Yeni seçenekler için radyo düğmeleri
        self.non_nexus_radio = QRadioButton("Cisco Catalyst Model_SW_Topolojisi")
        self.nexus_only_radio = QRadioButton("Cisco Nexus Model_SW_Topolojisi")
        
        #self.all_devices_radio = QRadioButton("Nexus ve Diğerleri")

        # Radyo düğmelerini gruplayın
        radio_group_box = QGroupBox("Cihaz Türü Seçimi:")
        radio_layout = QVBoxLayout()
        radio_layout.addWidget(self.non_nexus_radio)
        radio_layout.addWidget(self.nexus_only_radio)
        #radio_layout.addWidget(self.all_devices_radio)
        radio_group_box.setLayout(radio_layout)
        layout.addWidget(radio_group_box)

        # Oluştur Butonu
        self.submit_button = QPushButton("Oluştur", self)
        self.submit_button.clicked.connect(self.generate_topology)
        layout.addWidget(self.submit_button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # Stil Düzenlemeleri
        self.setStyleSheet("QGroupBox { padding-top: 10px; margin-top: 5px; }")

        # Buton Boyutları
        self.submit_button.setFixedHeight(40)

        # Giriş Alanlarının Genişlikleri
        self.ip_input.setMinimumWidth(200)
        self.username_input.setMinimumWidth(200)
        self.password_input.setMinimumWidth(200)

        # Radyo düğmelerini öntanımlı olarak seçilmemiş olarak ayarlayın
        self.non_nexus_radio.setChecked(True)
        self.show_switch_names_checkbox.setChecked(True)

    def ssh_connect(self, ip, username, password, timeout=2):
        try:
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            client.connect(ip, username=username, password=password, timeout=timeout)
            return client
        except paramiko.AuthenticationException:
            print(f"SSH authentication failed for {ip}")
        except paramiko.SSHException as e:
            print(f"SSH connection failed for {ip}: {str(e)}")
        except Exception as e:
            print(f"Error connecting to {ip}: {str(e)}")
        return None

    def get_neighbors_info(self, ssh_client, option):
        try:
            stdin, stdout, stderr = ssh_client.exec_command("sh cdp ne detail")
            output = stdout.read().decode('utf-8')

            if option == "Nexus_only":
                # Sadece Nexus cihazlarını filtrele
                neighbor_info = re.findall(r"Device ID:(.+?)\n\s+IPv4 Address:\s+(\d+\.\d+\.\d+\.\d+)", output, re.DOTALL)
            elif option == "Non_nexus":
                # Nexus olmayan cihazları filtrele
                neighbor_info = re.findall(r"Device ID: (.+?)\n\s+IP address: (\d+\.\d+\.\d+\.\d+)", output, re.DOTALL)
            elif option == "All_devices":
                # Tüm cihazları kullan
                neighbor_info = re.findall(r"Device ID:(.+?)\n\s+IP(?:v4)? Address:\s+(\d+\.\d+\.\d+\.\d+)", output, re.DOTALL)
            else:
                print("Invalid option")
                return []

            return neighbor_info
        except Exception as e:
            print(f"Error getting neighbors' info: {str(e)}")
            return []

    def get_switch_name(self, ssh_client):
        try:
            stdin, stdout, stderr = ssh_client.exec_command("sh run | inc hostname")
            output = stdout.read().decode('utf-8')
            match = re.search(r'hostname\s+(.*)', output)
            if match:
                return match.group(1)
            else:
                return None
        except Exception as e:
            print(f"Error getting switch name: {str(e)}")
            return None

    def get_switch_vlans(self, output):
        vlans = []

        vlan_pattern = re.compile(r"(\d+)\s+(\S+)\s+(?:active|suspend)\s+\S+")
        vlan_matches = vlan_pattern.findall(output)

        for number, _ in vlan_matches:
            vlans.append(f"VLAN {number}")  # Yalnızca VLAN numarasını ekleyin

        if not vlans:
            print("No VLAN numbers found in the output:", output)

        return vlans

    def explore_neighbors(self, device_ip, username, password, G, depth, visited_devices, switch_names, option):
        if device_ip in self.passthrough_ips:  # Eğer pas geçilecek IP'lerden biriyse
            return  # Pas geç

        if depth <= 0:
            return

        with ThreadPoolExecutor(max_workers=5) as executor:
            futures = []
            device_client = self.ssh_connect(device_ip, username, password)
            if device_client:
                print(f"Successfully connected to device {device_ip}.")

                neighbor_info = self.get_neighbors_info(device_client, option)
                print(f"Neighbor info of {device_ip}:", neighbor_info)

                for name, ip in neighbor_info:
                    if ip not in self.passthrough_ips:  # Eğer komşu IP'si pas geçilecek IP'lerden biri değilse
                        G.add_node(ip, label=name)
                        G.add_edge(device_ip, ip)
                        if ip not in visited_devices:
                            visited_devices.add(ip)
                            switch_names[ip] = self.get_switch_name(self.ssh_connect(ip, username, password))
                            futures.append(executor.submit(self.explore_neighbors, ip, username, password, G, depth - 1, visited_devices, switch_names, option))

                for future in futures:
                    future.result()

                device_client.close()
            else:
                print(f"Unable to connect to device {device_ip}.")

    def show_message_box(self, title, message, icon=QMessageBox.Information):
        msg_box = QMessageBox()
        msg_box.setIcon(icon)
        msg_box.setWindowTitle(title)
        msg_box.setText(message)
        msg_box.exec_()

    def generate_topology(self):
        self.submit_button.setEnabled(False)

        primary_device_ip = self.ip_input.text()
        username = self.username_input.text()
        password = self.password_input.text()
        show_switch_names = self.show_switch_names_checkbox.isChecked()

        if not primary_device_ip or not username or not password:
            self.show_message_box("Hata", "Lütfen tüm alanları doldurun.", QMessageBox.Warning)
            self.submit_button.setEnabled(True)
            return

        self.show_message_box("Bilgi", "Topoloji oluşturma işlemi başladı.")

        G = nx.Graph()
        visited_devices = set()
        switch_names = {}

        visited_devices.add(primary_device_ip)
        switch_names[primary_device_ip] = self.get_switch_name(self.ssh_connect(primary_device_ip, username, password))

        option = ""
        if self.nexus_only_radio.isChecked():
            option = "Nexus_only"
        elif self.non_nexus_radio.isChecked():
            option = "Non_nexus"
        elif self.all_devices_radio.isChecked():
            option = "All_devices"

        self.explore_neighbors(primary_device_ip, username, password, G, 3, visited_devices, switch_names, option)

        all_vlans = set()

        net = Network()
        for node in G.nodes():
            try:
                device_client = self.ssh_connect(node, username, password, timeout=3)
                if device_client:
                    stdin, stdout, stderr = device_client.exec_command("sh vlan | inc Gi0|Gi1|Gi2|Gi3|Gi4|Gi5|Gi6|Gi7|Gi8|Te0|Te1|Te2|Te3|Te4|Te5|Te6|Te7|Eth1|Eth2|Eth3|Eth4|Eth5|Eth6|Eth7|Eth8|Po1")
                    output = stdout.read().decode()
                    vlans = self.get_switch_vlans(output)
                    vlan_str = "\n".join(vlans)
                    if show_switch_names and node in switch_names:
                        net.add_node(node, label=switch_names[node] + "\n" + vlan_str)
                    else:
                        net.add_node(node, label=node + "\n" + vlan_str)
                    device_client.close()
                    all_vlans.update(vlans)
                else:
                    if show_switch_names and node in switch_names:
                        net.add_node(node, label=switch_names[node], color="red")
                    else:
                        net.add_node(node, label=node, color="red")
            except TimeoutError:
                print(f"Connection to device {node} timed out.")
                if show_switch_names and node in switch_names:
                    net.add_node(node, label=switch_names[node], color="red")
                else:
                    net.add_node(node, label=node, color="red")

        print("All VLANs used across devices:", all_vlans)

        for edge in G.edges():
            net.add_edge(edge[0], edge[1])

        net.show_buttons(filter_=['physics', 'layout','manipulation'])
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        file_name, _ = QFileDialog.getSaveFileName(self, "HTML Dosyasını Kaydet", "", "HTML Dosyası (*.html)", options=options)
        if file_name:
            if not file_name.endswith(".html"):
                file_name += ".html"
            net.write_html(file_name)

        self.show_message_box("Bilgi", "Topoloji oluşturma işlemi tamamlandı.", QMessageBox.Information)
        self.submit_button.setEnabled(True)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())