import customtkinter as ctk
from tkinter import filedialog
from core import reporting
import os
import json

class ReportsFrame(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 # For consistency, though not directly used for setting sessions here
        self.master = master # Store reference to the main app for accessing current_session_name
        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(1, weight=1)
        self.grid_rowconfigure(1, weight=1)

        # ---- Título ----
        title = ctk.CTkLabel(self, text="Gestor de Reportes", font=self.heading_font)
        title.grid(row=0, column=0, columnspan=2, padx=20, pady=(20, 10), sticky="ew")

        # ---- Panel Izquierdo (Lista de Sesiones) ----
        sessions_frame = ctk.CTkFrame(self)
        sessions_frame.grid(row=1, column=0, padx=20, pady=10, sticky="ns")
        sessions_frame.grid_rowconfigure(1, weight=1)

        sessions_label = ctk.CTkLabel(sessions_frame, text="Sesiones Guardadas", font=self.text_font)
        sessions_label.grid(row=0, column=0, padx=10, pady=10)

        self.sessions_listbox = ctk.CTkTextbox(sessions_frame, width=250, font=self.text_font)
        self.sessions_listbox.grid(row=1, column=0, padx=10, pady=10, sticky="ns")
        self.sessions_listbox.configure(state="disabled")
        # Usamos un Textbox en lugar de Listbox para poder seleccionar y copiar texto.
        # Asociaremos un evento de clic para simular la selección.
        self.sessions_listbox.bind("<Button-1>", self.on_session_select)

        # ---- Panel Derecho (Visor de Contenido) ----
        content_frame = ctk.CTkFrame(self)
        content_frame.grid(row=1, column=1, padx=(0, 20), pady=10, sticky="nsew")
        content_frame.grid_rowconfigure(1, weight=1)
        content_frame.grid_columnconfigure(0, weight=1)

        self.content_label = ctk.CTkLabel(content_frame, text="Contenido de la Sesión", font=self.text_font)
        self.content_label.grid(row=0, column=0, padx=10, pady=10, sticky="w") # Added sticky="w"

        self.nmap_summary_display_frame = ctk.CTkScrollableFrame(content_frame, label_text="Nmap Summary") # New scrollable frame
        self.nmap_summary_display_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
        self.nmap_summary_display_frame.grid_columnconfigure(0, weight=1)
        self.nmap_summary_display_frame.grid_remove() # Hide initially

        self.content_textbox = ctk.CTkTextbox(content_frame)
        self.content_textbox.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
        self.content_textbox.configure(state="disabled", font=self.monospace_font)

        # ---- Panel de Botones ----
        button_frame = ctk.CTkFrame(self)
        button_frame.grid(row=2, column=0, columnspan=2, padx=20, pady=20, sticky="ew")

        refresh_button = ctk.CTkButton(button_frame, text="Actualizar Lista", command=self.refresh_sessions_list)
        refresh_button.pack(side="left", padx=10)

        export_button = ctk.CTkButton(button_frame, text="Exportar a Markdown", command=self.export_session_to_markdown)
        export_button.pack(side="left", padx=10)

        export_html_button = ctk.CTkButton(button_frame, text="Exportar a HTML", command=self.export_session_to_html)
        export_html_button.pack(side="left", padx=10)
        
        # Cargar sesiones al iniciar
        self.refresh_sessions_list()
        self.selected_session = None

    def refresh_sessions_list(self):
        """Carga y muestra la lista de sesiones guardadas."""
        self.sessions_listbox.configure(state="normal")
        self.sessions_listbox.delete("1.0", "end")
        
        sessions = reporting.get_sessions()
        if sessions:
            for session in sessions:
                self.sessions_listbox.insert("end", session + "\n")
        else:
            self.sessions_listbox.insert("1.0", "No hay sesiones guardadas.")
        
        self.sessions_listbox.configure(state="disabled")

    def on_session_select(self, event=None):
        """Maneja la selección de una sesión de la lista."""
        line = self.sessions_listbox.index(f"@{event.x},{event.y} linestart").split('.')[0]
        selected_text = self.sessions_listbox.get(f"{line}.0", f"{line}.end").strip()
        
        if selected_text and "No hay sesiones" not in selected_text:
            self.selected_session = selected_text
            self.load_session_content(selected_text)

    def load_session_content(self, session_name):
        """
        Carga y muestra el contenido de la sesión seleccionada.
        Muestra un resumen interactivo de Nmap si está disponible,
        o el contenido normal de texto/JSON en el cuadro de texto principal.
        """
        # Clear previous dynamic Nmap content
        for widget in self.nmap_summary_display_frame.winfo_children():
            widget.destroy()
        
        # Always hide both initially, then show the appropriate one
        self.content_textbox.grid_remove()
        self.nmap_summary_display_frame.grid_remove()

        self.content_textbox.configure(state="normal")
        self.content_textbox.delete("1.0", "end")
        
        metadata, data = reporting.get_session_data(session_name)
        
        if not metadata:
            self.content_textbox.grid() # Show textbox for error message
            self.content_textbox.insert("1.0", f"Error al cargar la sesión '{session_name}'.")
            self.content_textbox.configure(state="disabled")
            return

        self.content_label.configure(text=f"Contenido de: {metadata.get('target', session_name)}")

        # Muestra primero los metadatos
        self.content_textbox.insert("end", "--- METADATOS DE LA SESIÓN ---\\n")
        if "tools_used" in metadata:
             self.content_textbox.insert("end", f"Herramientas Utilizadas: {', '.join(metadata['tools_used'])}\\n")
        if "tools_output_formats" in metadata:
            self.content_textbox.insert("end", "Formatos de Salida:\\n")
            for tool, formats in metadata["tools_output_formats"].items():
                self.content_textbox.insert("end", f"  {tool}: {', '.join(formats)}\\n")

        self.content_textbox.insert("end", "\n" + "="*40 + "\n\n")

        # Flag to check if we displayed any interactive Nmap summary
        nmap_summary_displayed_interactively = False

        # --- Check for and display interactive Nmap Summary ---
        session_path = os.path.join(reporting.REPORTS_DIR, session_name)
        summary_filename = f"{reporting.sanitize_filename('Nmap_Scan_Summary')}.json"
        summary_path = os.path.join(session_path, summary_filename)

        if os.path.exists(summary_path):
            try:
                with open(summary_path, 'r', encoding='utf-8') as f_summary:
                    parsed_nmap_summary = json.load(f_summary)
                
                if parsed_nmap_summary and parsed_nmap_summary.get("hosts"):
                    self.nmap_summary_display_frame.grid() # Show Nmap frame
                    self.nmap_summary_display_frame.grid_columnconfigure(0, weight=1) # Ensure it expands

                    # Create header row
                    headers = ["Host", "IP Address", "Status", "Port", "Protocol", "Service", "Version"]
                    for col, header_text in enumerate(headers):
                        header_label = ctk.CTkLabel(self.nmap_summary_display_frame, text=header_text, font=self.text_font.copy_with_font_traits(weight="bold"))
                        header_label.grid(row=0, column=col, padx=5, pady=5, sticky="ew")
                    
                    row_idx = 1
                    for host in parsed_nmap_summary["hosts"]:
                        host_addrs = ", ".join(host.get('addresses', []))
                        host_name = host.get('hostname', 'N/A')
                        host_status = host.get('status', 'N/A')

                        if host.get("ports"):
                            for port in host["ports"]:
                                ctk.CTkLabel(self.nmap_summary_display_frame, text=host_name, font=self.text_font).grid(row=row_idx, column=0, padx=5, pady=2, sticky="w")
                                ctk.CTkLabel(self.nmap_summary_display_frame, text=host_addrs, font=self.text_font).grid(row=row_idx, column=1, padx=5, pady=2, sticky="w")
                                ctk.CTkLabel(self.nmap_summary_display_frame, text=host_status, font=self.text_font).grid(row=row_idx, column=2, padx=5, pady=2, sticky="w")
                                ctk.CTkLabel(self.nmap_summary_display_frame, text=port.get('portid', 'N/A'), font=self.text_font).grid(row=row_idx, column=3, padx=5, pady=2, sticky="w")
                                ctk.CTkLabel(self.nmap_summary_display_frame, text=port.get('protocol', 'N/A'), font=self.text_font).grid(row=row_idx, column=4, padx=5, pady=2, sticky="w")
                                ctk.CTkLabel(self.nmap_summary_display_frame, text=port.get('service', 'N/A'), font=self.text_font).grid(row=row_idx, column=5, padx=5, pady=2, sticky="w")
                                ctk.CTkLabel(self.nmap_summary_display_frame, text=port.get('version', 'N/A'), font=self.text_font).grid(row=row_idx, column=6, padx=5, pady=2, sticky="w")
                                row_idx += 1
                        else:
                            # Display host info even if no open ports
                            ctk.CTkLabel(self.nmap_summary_display_frame, text=host_name, font=self.text_font).grid(row=row_idx, column=0, padx=5, pady=2, sticky="w")
                            ctk.CTkLabel(self.nmap_summary_display_frame, text=host_addrs, font=self.text_font).grid(row=row_idx, column=1, padx=5, pady=2, sticky="w")
                            ctk.CTkLabel(self.nmap_summary_display_frame, text=host_status, font=self.text_font).grid(row=row_idx, column=2, padx=5, pady=2, sticky="w")
                            ctk.CTkLabel(self.nmap_summary_display_frame, text="No open ports", font=self.text_font).grid(row=row_idx, column=3, columnspan=4, padx=5, pady=2, sticky="w")
                            row_idx += 1
                    nmap_summary_displayed_interactively = True
            except Exception as e:
                self.content_textbox.grid() # Show main textbox again if error
                self.nmap_summary_display_frame.grid_remove()
                self.content_textbox.insert("end", f"Error al cargar el resumen interactivo de Nmap: {e}\\n\\n")
        
        # --- Display other outputs or raw Nmap if interactive was not displayed ---
        if not nmap_summary_displayed_interactively:
            self.content_textbox.grid() # Ensure main textbox is visible

        for tool_name, outputs in data.items():
            # Skip the Nmap_Scan_Summary.json itself if it's found in data (it's handled interactively)
            if tool_name == reporting.sanitize_filename('Nmap_Scan_Summary') and "json" in outputs:
                continue

            self.content_textbox.insert("end", f"--- RESULTADOS DE: {tool_name} ---\\n")
            for output_format, content in outputs.items():
                self.content_textbox.insert("end", f"Formato ({output_format.upper()}):\\n")
                
                # Check for Nmap XML and just display raw XML if interactive was not shown
                if tool_name == "Nmap Scan (XML)" and output_format == "xml" and nmap_summary_displayed_interactively:
                    # If interactive summary was shown, we don't need to show raw XML here
                    self.content_textbox.insert("end", "Raw Nmap XML output displayed in interactive table above.\\n\\n")
                    continue
                
                if output_format == "json":
                    try:
                        self.content_textbox.insert("end", json.dumps(json.loads(content), indent=2) + "\\n\\n")
                    except json.JSONDecodeError:
                        self.content_textbox.insert("end", "Error al parsear JSON. Mostrando como texto plano:\\n" + content + "\\n\\n")
                else:
                    self.content_textbox.insert("end", content + "\\n\\n")
        
        self.content_textbox.configure(state="disabled")

    def export_session_to_markdown(self):
        """Exporta la sesión seleccionada a un archivo Markdown."""
        session_to_export = self.selected_session
        if not session_to_export and self.master.current_session_name:
            session_to_export = self.master.current_session_name
        
        if not session_to_export:
            print("No hay ninguna sesión seleccionada o activa para exportar.")
            # ctk.CTkMessagebox(title="Error", message="No hay ninguna sesión seleccionada o activa para exportar.")
            return

        initial_filename = f"{session_to_export}_report.md"
        filepath = filedialog.asksaveasfilename(
            defaultextension=".md",
            filetypes=[("Markdown", "*.md"), ("All Files", "*.*")],
            initialfile=initial_filename,
            title="Guardar Reporte Markdown Como..."
        )

        if filepath:
            success = reporting.export_session_to_markdown(session_to_export, filepath)
            if success:
                print(f"Reporte Markdown guardado exitosamente en: {filepath}")
                # ctk.CTkMessagebox(title="Éxito", message=f"Reporte Markdown guardado exitosamente en:\n{filepath}")
            else:
                print(f"Error al guardar el reporte Markdown para la sesión: {session_to_export}")
                # ctk.CTkMessagebox(title="Error", message=f"Error al guardar el reporte Markdown para la sesión: {session_to_export}")

    def export_session_to_html(self):
        """Exporta la sesión seleccionada o activa a un archivo HTML."""
        session_to_export = self.selected_session
        if not session_to_export and self.master.current_session_name:
            session_to_export = self.master.current_session_name
        
        if not session_to_export:
            print("No hay ninguna sesión seleccionada o activa para exportar.")
            # ctk.CTkMessagebox(title="Error", message="No hay ninguna sesión seleccionada o activa para exportar.")
            return

        initial_filename = f"{session_to_export}_report.html"
        filepath = filedialog.asksaveasfilename(
            defaultextension=".html",
            filetypes=[("HTML Files", "*.html"), ("All Files", "*.*")],
            initialfile=initial_filename,
            title="Guardar Reporte HTML Como..."
        )

        if filepath:
            success = reporting.export_session_to_html(session_to_export, filepath)
            if success:
                print(f"Reporte HTML guardado exitosamente en: {filepath}")
                # ctk.CTkMessagebox(title="Éxito", message=f"Reporte HTML guardado exitosamente en:\n{filepath}")
            else:
                print(f"Error al guardar el reporte HTML para la sesión: {session_to_export}")
                # ctk.CTkMessagebox(title="Error", message=f"Error al guardar el reporte HTML para la sesión: {session_to_export}")