import tkinter as tk
from tkinter import Frame

from GUIBRUSHR.GUI.LAYOUT.ScaleManager import ScaleManager
from GUIBRUSHR.GUI.WIDGET.MyEntry import MyEntry
from GUIBRUSHR.GUI.WIDGET.MyLabel import MyLabel
from GUIBRUSHR.GUI.WIDGET.MyButton import MyButton


def _round_to_nearest(value, step=1.0):
    """
    Round value to the nearest multiple of step using half-up rule.
    
    Args:
        value: The numeric value to round
        step: The step size to round to (default: 1.0)
        
    Returns:
        The rounded value as a multiple of step
    """
    return round(value / step) * step


class StellarDialog(tk.Toplevel):
    """
    Custom dialog for downloading stellar spectrum parameters.
    
    This dialog asks whether to download a stellar spectrum and collects
    stellar parameters (temperature, log gravity, metallicity) if the user
    confirms. The parameters are validated, rounded to appropriate precision,
    and returned in a formatted tuple.
    
    Attributes:
        stellar_teff: The initial stellar temperature value
        result: Tuple containing the validated parameters or (None, None, None)
        _entries: Dictionary mapping parameter labels to MyEntry widgets
        _message_label: MyLabel widget displaying the main message
        _no_button: MyButton widget for "No" action
        _ok_button: MyButton widget for "OK" action
    """
    
    # Constants for dialog styling
    DIALOG_COLOR = "#FFFFFF"
    BUTTON_COLOR = "#E0E0E0"
    
    # Parameter configuration: (label, default_value, rounding_step)
    PARAMETER_CONFIG = [
        ("Stellar Temperature [K]", None, 100),    # default set dynamically
        ("Log Gravity [cm s⁻²]", "4.5", 0.5),
        ("Metallicity [Fe/H]", "0.0", 0.2),
    ]

    def __init__(self, parent, stellar_teff):
        """
        Initialize the stellar parameter dialog.
        
        Args:
            parent: The parent widget
            stellar_teff: Initial stellar temperature value
        """
        super().__init__(parent)
        self.stellar_teff = stellar_teff
        self.result = (None, None, None)
        self._entries = {}
        
        self._setup_dialog_properties()
        self._create_widgets()
        self._setup_bindings()
        self._setup_modal_behavior(parent)

    def _setup_dialog_properties(self):
        """Configure basic dialog properties."""
        self.title("Download stellar spectrum?")
        self.resizable(False, False)
        self.configure(bg=self.DIALOG_COLOR)

    def _create_widgets(self):
        """Create and arrange all dialog widgets."""
        self._create_message_label()
        self._create_parameter_entries()
        self._create_buttons()

    def _create_message_label(self):
        """Create the main message label."""
        self._message_label = MyLabel(
            parent=self,
            row=0,
            column=0,
            color=self.DIALOG_COLOR,
            label_text="Stellar spectrum not present.\nDo you want to download it?",
            font=ScaleManager.get().font_label_normal if ScaleManager.get() else ("Sans", 10, "normal"),
            columnspan=2
        )

    def _create_parameter_entries(self):
        """Create entry fields for stellar parameters."""
        for i, (label, default_value, _) in enumerate(self.PARAMETER_CONFIG, start=1):
            # Set default value for temperature dynamically
            if "Temperature" in label:
                default_value = str(self.stellar_teff)
            
            # Create entry with label
            entry_widget = MyEntry(
                parent=self,
                row=i,
                column=0,
                text=default_value,
                label_text=label,
                color=self.DIALOG_COLOR,
                columnspan=2,
                entry_width=12
            )
            
            # Store reference to the entry widget for later access
            self._entries[label] = entry_widget

    def _create_buttons(self):
        """Create and arrange dialog buttons."""
        # Create a frame to hold the buttons
        button_frame = Frame(self, bg=self.DIALOG_COLOR)
        button_frame.grid(row=4, column=0, columnspan=2, pady=(8, 10))
        
        # Create "No" button
        self._no_button = MyButton(
            parent=button_frame,
            row=0,
            column=0,
            text="No",
            bg=self.BUTTON_COLOR,
            command=self._handle_no_action,
            color_panel=self.DIALOG_COLOR,
            size_text=10
        )
        
        # Create "OK" button
        self._ok_button = MyButton(
            parent=button_frame,
            row=0,
            column=1,
            text="OK",
            bg=self.BUTTON_COLOR,
            command=self._handle_ok_action,
            color_panel=self.DIALOG_COLOR,
            size_text=10
        )

    def _setup_bindings(self):
        """Setup keyboard bindings for the dialog."""
        self.bind("<Return>", lambda event: self._handle_ok_action())
        self.bind("<Escape>", lambda event: self._handle_no_action())

    def _setup_modal_behavior(self, parent):
        """Configure modal dialog behavior."""
        self.transient(parent)
        self.grab_set()
        parent.wait_window(self)

    def _handle_no_action(self):
        """
        Handle the "No" button click or Escape key press.
        
        Sets result to (None, None, None) and closes the dialog.
        """
        self.result = (None, None, None)
        self.destroy()

    def _handle_ok_action(self):
        """
        Handle the "OK" button click or Enter key press.
        
        Validates and processes the entered parameters, then closes the dialog.
        If validation fails, behaves like "No" action.
        """
        try:
            # Process each parameter according to its configuration
            processed_params = []
            
            for label, _, rounding_step in self.PARAMETER_CONFIG:
                raw_value = self._entries[label].get_value()
                processed_value = self._process_parameter(
                    raw_value, label, rounding_step
                )
                processed_params.append(processed_value)
            
            self.result = tuple(processed_params)
            
        except ValueError:
            # Invalid input → behave like "No" action
            self.result = (None, None, None)
        finally:
            self.destroy()

    def _process_parameter(self, raw_value, label, rounding_step):
        """
        Process and format a single parameter value.
        
        Args:
            raw_value: The raw string value from the entry field
            label: The parameter label for identification
            rounding_step: The step size for rounding
            
        Returns:
            The processed parameter value
            
        Raises:
            ValueError: If the input cannot be converted to float
        """
        numeric_value = float(raw_value)
        rounded_value = _round_to_nearest(numeric_value, rounding_step)
        
        # Special formatting for temperature (5-digit, zero-padded string)
        if "Temperature" in label:
            return f"{int(rounded_value):05d}"

        if "Metallicity" in label:
            if rounded_value > 0:
                rounded_value = f"+{abs(rounded_value)}"
            else:
                rounded_value = f"-{abs(rounded_value)}"
        
        # Return as float for other parameters
        return rounded_value