import os
import pickle
import subprocess
from pathlib import Path

from numpy import array

from GUIBRUSHR.GUI.WIDGET.MyButton import MyButton
from GUIBRUSHR.GUI.WIDGET.MyDropDown import MyDropdown
from GUIBRUSHR.GUI.WIDGET.MyFigure import MyFigure
from GUIBRUSHR.GUI.WIDGET.MyTextField import MyTextField
from GUIBRUSHR.GUI.WIDGET.MyCheckBox import MyCheckBox
from GUIBRUSHR.GUI.WIDGET.MyEntry import MyEntry
from GUIBRUSHR.GUI.LAYOUT.MyPanel import MyPanel
from GUIBRUSHR.GUI.DataInterface.DataInterface import (
    get_target_info,
    get_target_list,
    get_target_nights,
    get_target_instruments
)
from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables
from GUIBRUSHR.General_Constants.FunctionsAndConstants.general_functions import create_path_night
from GUIBRUSHR.General_Constants.Classes.HelpButton import HelpButton


class FrameTellRem(MyPanel):
    """
    A panel for handling telluric removal operations in the GUI.
    
    This class provides a user interface for configuring and executing telluric removal
    operations on astronomical data. It includes controls for target selection,
    instrument configuration, and various telluric removal parameters.
    """

    def __init__(self, parent, row, column, color, path_default, frame_input, window, **kwargs):
        """
        Initialize the FrameTellRem panel.

        Args:
            parent: Parent widget
            row: Row position in parent
            column: Column position in parent
            color: Background color
            path_default: Default path for file operations
            frame_input: Input frame reference
            window: Main window reference
            **kwargs: Additional keyword arguments
        """
        self.dropdown_hr_instrument = None
        self.dropdown_target = None
        self.list_hr_instrument = None
        self.path_folder_targets = frame_input.frame_path.path_folder_targets.get_text()
        self.color = color
        self.global_exc = 0  # Flag to control initialization
        self.frame_input = frame_input
        self.path_default = path_default
        self.window = window
        self.path_night_complete = None

        # Initialize parent class
        super().__init__(parent, color, row, column, **kwargs)

        # Connect to database and get initial target information
        target_list = get_target_list(self.path_folder_targets)
        self.target_information = get_target_info(self.path_folder_targets, target_list[0])

        # Create panel rows for organizing widgets
        self._create_panel_rows()
        
        # Initialize UI components
        self._init_target_selection()
        self._init_rad_mode_selection()
        self._init_hr_instrument_selection()
        self._init_night_selection()
        self._init_telluric_parameters()
        self._init_removal_controls()
        self._init_results_display()

        # Setup exchange directory for temporary files
        self._setup_exchange_directory()

        # Set callbacks and finalize initialization
        self._setup_callbacks()
        self.global_exc = 1
        self.p = None

    def _create_panel_rows(self):
        """Create the panel rows for organizing widgets."""
        self.row1 = MyPanel(self, self.color, 0, 1)
        self.row2 = MyPanel(self, self.color, 1, 1)
        self.row3 = MyPanel(self, self.color, 2, 1)
        self.row4 = MyPanel(self, self.color, 3, 1)
        self.row5 = MyPanel(self, self.color, 4, 1)
        self.row6 = MyPanel(self, self.color, 5, 1)
        self.row7 = MyPanel(self, self.color, 6, 1)

    def _init_target_selection(self):
        """Initialize target selection dropdown and related components."""
        target_list = get_target_list(self.path_folder_targets)
        self.dropdown_target = MyDropdown(
            self.row1, 0, 1, target_list, "Target:", 
            color=self.color, columnspan=2
        )
        self.dropdown_target.set_callback(self.update_target)

    def _init_rad_mode_selection(self):
        """Initialize radiation mode selection dropdown."""
        self.dropdown_rad_mode = MyDropdown(
            self.row1, 0, 3, ConstantVariables.LIST_RAD_MODE,
            "Rad. Transf. Mode:", initial_value=0,
            color=self.color, columnspan=2
        )

    def _init_hr_instrument_selection(self):
        """Initialize high-resolution instrument selection."""
        self.list_hr_instrument = get_target_instruments(
            self.path_folder_targets, 
            self.dropdown_target.get_value(), 
            "HR", 
            "Transmission",
            Path(__file__).name
        )
        self.dropdown_hr_instrument = MyDropdown(
            self.row1, 0, 5, self.list_hr_instrument, 
            "HR Instrument:", color=self.color, columnspan=2
        )

    def _init_night_selection(self):
        """Initialize night selection components."""
        nights = get_target_nights(
            self.path_folder_targets,
            self.dropdown_target.get_value(),
            "Transmission",
            self.list_hr_instrument[0],
            0
        )
        self.textfield_nights = MyTextField(
            self.row1, 0, 7, nights, "Nights:", 
            color=self.color, columnspan=3, height=3
        )
        self.checkbox_nights_sim = MyCheckBox(
            self.row1, 0, 10, "Night sim/syn",
            command=self.update_night_sim
        )

    def _init_telluric_parameters(self):
        """Initialize telluric removal parameters."""
        # Row 2: ordersel, mean columns, tell rm method, nfcoo, save plot, smooth size
        self.textfield_order_selection = MyTextField(
            self.row2, 0, 1, "full", "Order Selection:",
            color=self.color, columnspan=2, width=20
        )

        self.checkboc_mean_column = MyCheckBox(
            self.row2, 0, 3, "Subtraction of the average spectrum",
            initial_value=1
        )

        self.dropdown_tell_rm_method = MyDropdown(
            self.row2, 0, 4, ConstantVariables.LIST_TELL_TABLE,
            "Tell rm method:", color=self.color, columnspan=2
        )

        self.checkbox_nfcoo = MyCheckBox(self.row2, 0, 6, "Use last component selection")

        self.checkbox_save_plot = MyCheckBox(self.row2, 0, 7, "Save plot")

        self.entry_smooth_size = MyEntry(
            self.row2, 0, 8, "40", "Smooth size:",
            color=self.color, columnspan=2
        )

        # Additional parameters
        self._init_additional_parameters()

    def _init_additional_parameters(self):
        """Initialize additional telluric removal parameters."""
        # Row 3: mask, threshold meanspec, cutoff teluric, sigma_coeff
        self.textfield_yesmask = MyTextField(
            self.row3, 0, 1, "1,1,1,1,1,1", "Mask night:",
            color=self.color, columnspan=2, width=20
        )

        self.entry_threshold_mean_spec = MyEntry(
            self.row3, 0, 3, "0.3", "Threshold mean spectrum:",
            color=self.color, columnspan=2
        )

        self.entry_cutoff = MyEntry(
            self.row3, 0, 5, "0.8", "Cutoff telluric:",
            color=self.color, columnspan=2
        )

        self.textfield_sigma_coeff = MyTextField(
            self.row3, 0, 7, "1.5", "Sigma Coefficient:",
            color=self.color, columnspan=2, width=20
        )

        self.checkbox_do_not_fit_continuum = MyCheckBox(
            self.row3, 0, 9, "Do not fit continuum"
        )

        # Row 4: fixed comp, tangent components method, tangent threshold, substitute
        # Min and max component hidden but kept for exchange dictionary
        # self.entry_min_comp = MyEntry(
        #     self.row4, 0, 1, "0", "Min Component:",
        #     color=self.color, columnspan=2
        # )
        #
        # self.entry_max_comp = MyEntry(
        #     self.row4, 0, 3, "0", "Max Component:",
        #     color=self.color, columnspan=2
        # )

        self.entry_fixed_comp = MyEntry(
            self.row4, 0, 1, "0", "Fixed Component:",
            color=self.color, columnspan=2
        )

        self.checkbox_tangent_components_method = MyCheckBox(
            self.row4, 0, 3, "Tangent components method",
            command=self.toggle_fixed_comp_state
        )
        self.entry_tangent_value = MyEntry(
            self.row4, 0, 4, "0.1", "Tangent threshold:",
            color=self.color, columnspan=2
        )
        self.entry_substitute_for_comp = MyEntry(
            self.row4, 0, 6, "4", "N. components substitute:",
            color=self.color, columnspan=2
        )
        self.checkbox_standardize_pca = MyCheckBox(
            self.row4, 0, 8, "Standardize PCA",
            initial_value=0
        )

        # PCA mode dropdown: spatial (loop over orders) or temporal (loop over spectral channels)
        self.dropdown_pca_mode = MyDropdown(
            self.row4, 0, 9, ["spatial", "temporal"],
            "PCA mode:", color=self.color, columnspan=2
        )

    def _init_removal_controls(self):
        """Initialize telluric removal control buttons."""
        # Create help text dictionary
        help_text = {
            "Target:": "Select the exoplanet to process. The list is populated from available targets in the target folder specified in the configuration file.",

            "Rad. Transf. Mode:": "Select the radiative transfer mode for atmospheric modeling. Could be Transmission or Emission.",

            "HR Instrument:": "Select the high-resolution spectrograph used for observations. The list is filtered based on the selected target and only shows instruments with transmission spectroscopy data.",

            "Nights:": "Displays the observing nights available for the selected target and instrument. Multiple nights are shown as a comma-separated list.",

            "Night sim/syn": "When enabled, switches to simulated/synthetic night data instead of real observational data. Useful for testing and validation purposes.",

            "Order Selection": "Specify the name of the folder containing the spectral orders information to process.",

            "Subtraction of the average spectrum": "When enabled, subtracts the mean spectrum from each individual spectrum to remove common spectral features and improve telluric line detection.",

            "Tell rm method": "Select the algorithm for telluric removal. Different methods use various mathematical approaches (PCA, polynomial fitting, etc.) to model and remove telluric lines.",

            "Use last component selection": "When enabled, uses the number of components from the previous run instead of recalculating. Useful for consistency across multiple processing runs.",

            "Save plot": "When enabled, generates and saves diagnostic plots showing the telluric removal process, mean spectra, masks, and PCA components for quality assessment.",

            "Smooth size": "Size of the smoothing kernel applied after PCA removal. Larger values create smoother results but may remove fine spectral features. Set to 0 to disable smoothing.",

            "Mask night": "Controls which nights have masking applied. Provide one value per night separated by commas. Use 1 to enable masking (recommended), 0 to disable for a specific night.",

            "Threshold mean spectrum": "Masks pixels as bad where the channel signal (before continuum correction) is lower than this threshold. Value range: 0 (no signal) to 1 (maximum signal). Recommended: 0.3",

            "Cutoff telluric": "Masks pixels as bad where the channel (after continuum correction) is lower than this threshold. Value range: 0 (line saturated) to 1 (continuum level). Recommended: 0.8",

            "Sigma Coefficient": "Masks pixels as bad where the channel variance exceeds the mean variance multiplied by this coefficient. Higher values are more permissive. Recommended: 1.5",

            "Fixed Component": "Specify a fixed number of PCA components to use for telluric removal. Set to 0 to use automatic component selection. This field is disabled when 'Tangent components method' is enabled.",

            "Tangent components method": "When enabled, automatically determines the optimal number of PCA components using the tangent method, which analyzes the variance reduction curve. This overrides the Fixed Component value.",

            "Tangent threshold": "Threshold for the tangent method's second derivative test. Lower values select fewer components (more conservative), higher values select more components (more aggressive). Recommended: 0.1",

            "N. components substitute": "Fallback number of PCA components to use if the tangent method fails to find an optimal value above the threshold. Recommended: 4",

            "Standardize PCA": "When enabled, applies StandardScaler before PCA to standardize the data (mean=0, std=1). This ensures all features have equal variance before PCA decomposition. The mean-centering is already performed in previous steps, so this option only affects the standard deviation normalization. Enable this if you want all spectral channels to have unit variance before PCA.",

            "PCA mode:": "Select the PCA decomposition mode. 'spatial' (default): loops over spectral orders, with exposures as samples and pixels as features - removes temporal variations of telluric lines. 'temporal': loops over spectral channels, with pixels as samples and exposures as features - removes spatial patterns across pixels.",

            "TELLURIC REMOVAL": "Executes the telluric removal process using the configured parameters. This button spawns a subprocess that applies the selected method to remove telluric contamination from the spectral data. The button is disabled during processing and re-enabled upon completion.",

            "Results:": "Displays the output and status messages from the telluric removal process. Shows information about processed nights, number of components used, number of removed pixels."
        }

        # Create help button
        self.help_button = HelpButton(
            self.row6, 0, 0,
            "Telluric Removal Parameters Help",
            help_text,
            columnspan=1
        )

        # Create removal button
        self.button_Remove = MyButton(
            self.row6, 0, 1, 'TELLURIC REMOVAL', "#9af098",
            command=self.remove_tell, columnspan=3
        )

    def _init_results_display(self):
        """Initialize results display area."""
        self.textfield_results = MyTextField(
            self.row7, 0, 1, "", "Results:",
            color=self.color, columnspan=3, width=100, height=15
        )

    def _setup_exchange_directory(self):
        """Setup directory for temporary file exchange."""
        username = os.environ.get("USER")
        self.path_exchange = str(
            Path(self.path_default, "GUIBRUSHR", "Files", "Temp_Files_GUI",
                 username, "Temp_folder")
        )
        os.system(f"mkdir -p {self.path_exchange}")
        Path(self.path_exchange).mkdir(parents=True, exist_ok=True)

    def _setup_callbacks(self):
        """Setup callback functions for interactive elements."""
        self.dropdown_hr_instrument.set_callback(self.update_hr)
        self.dropdown_rad_mode.set_callback(self.update_from_rad_mode)

    def update_target(self):
        """Update target-related information when target selection changes."""
        if self.global_exc == 0:
            return
            
        target = self.dropdown_target.get_value()
        nights = get_target_nights(
            self.path_folder_targets, target,
            self.dropdown_rad_mode.get_value(),
            self.list_hr_instrument[0],
            self.checkbox_nights_sim.get_value()
        )
        
        self.target_information = get_target_info(
            self.path_folder_targets, target,
            self.dropdown_rad_mode.get_value()
        )
        self.textfield_nights.insert_text(nights)
        
        self.list_hr_instrument = get_target_instruments(
            self.path_folder_targets, target, "HR",
            self.dropdown_rad_mode.get_value(), Path(__file__).name
        )
        self.update_from_rad_mode()

    def update_hr(self):
        """Update high-resolution instrument related information."""
        if self.global_exc == 0:
            return
            
        instrument = self.dropdown_hr_instrument.get_value()
        target_id = self.dropdown_target.get_value()
        nights = get_target_nights(
            self.path_folder_targets, target_id,
            self.dropdown_rad_mode.get_value(),
            instrument, 0
        )
        self.textfield_nights.insert_text(nights)

    def remove_tell(self):
        """Execute telluric removal process."""
        path_instrument = create_path_night(
            self.path_folder_targets, 
            self.dropdown_target.get_value(),
            self.dropdown_rad_mode.get_value(),
            self.dropdown_hr_instrument.get_value()
        )

        # Save parameters to exchange file
        self._save_parameters_to_exchange(path_instrument)
        
        # Disable remove button and start process
        self.button_Remove.button.configure(state="disabled")
        self._start_telluric_removal_process()

    def _save_parameters_to_exchange(self, path_instrument):
        """Save all parameters to exchange file for processing."""
        exchange_dictionary = {
            "path_instrument": path_instrument,
            "order_selection": array(self.textfield_order_selection.get_text().split(",")),
            "nights": array(self.textfield_nights.get_text().split(",")),
            "tell_rm_method": self.dropdown_tell_rm_method.get_value(),
            "use_last_component_selection": self.checkbox_nfcoo.get_value(),
            "sigma_coeff": float(self.textfield_sigma_coeff.get_text()),
            "smooth_size": self.entry_smooth_size.get_value(),
            "yesmask": self.textfield_yesmask.get_text().split(","),
            "min_comp": 0, # self.entry_min_comp.get_value(),
            "max_comp":  0, # self.entry_max_comp.get_value(),
            "fixed_comp": self.entry_fixed_comp.get_value(),
            "save_plot": self.checkbox_save_plot.get_value(),
            "limit_phase_t14": self.target_information.limit_phase_t14,
            "limit_phase_t12": self.target_information.limit_phase_t12,
            "limit_phase_t23": self.target_information.limit_phase_t23,
            "cutoff": float(self.entry_cutoff.get_value()),
            "tangent_components_method": int(self.checkbox_tangent_components_method.get_value()),
            "tangent_value": float(self.entry_tangent_value.get_value()),
            "substitute_for_comp": int(self.entry_substitute_for_comp.get_value()),
            "threshold_mean_spec": float(self.entry_threshold_mean_spec.get_value()),
            "subtraction_of_the_average_spectrum": int(self.checkboc_mean_column.get_value()),
            "do_not_fit_continuum": int(self.checkbox_do_not_fit_continuum.get_value()),
            "apply_standardize": bool(self.checkbox_standardize_pca.get_value()),
            "pca_mode": self.dropdown_pca_mode.get_value()  # "spatial" or "temporal"
        }
        with open(str(Path(self.path_exchange, "exchange.pkl")), "wb") as fo:
            pickle.dump(exchange_dictionary, fo)

    def _start_telluric_removal_process(self):
        """Start the telluric removal subprocess."""
        self.p = subprocess.Popen(
            [
                "python3",
                str(Path(
                    self.path_default, "GUIBRUSHR", "GUI", "Input_Output_Panels",
                    "Input_Panels", "TabPanels", "FrameTellRem",
                    "tell_rem_pca_wl_log_final.py"
                )),
                str(self.path_exchange),
                self.path_default
            ],
            stderr=subprocess.STDOUT,
            universal_newlines=True,
        )
        self.window.after(500, self.checkbox_proc)

    def checkbox_proc(self):
        """Check the status of the telluric removal process."""
        if self.p.poll() is not None:
            self.button_Remove.button.configure(state="normal")
            if os.path.exists(str(Path(self.path_exchange, "error_tell_rm.pkl"))):
                with open(str(Path(self.path_exchange, "error_tell_rm.pkl")), "rb") as fo:
                    message = pickle.load(fo)
                    MyFigure(title="Error in tell rem", message=message)
            else:
                with open(str(Path(self.path_exchange, "string_tell_rem.pkl")), "rb") as fo:
                    string_tell_rem = pickle.load(fo)
                self.textfield_results.insert_text(string_tell_rem)
        else:
            # Not done yet. Check again later.
            self.window.after(500, self.checkbox_proc)

    def modify_target_list(self, target_list):
        """Update the target list dropdown with new targets."""
        self.dropdown_target.clean_widget()
        self.dropdown_target = MyDropdown(
            self.row1, 0, 0, target_list, "Target:",
            color=self.color, columnspan=2
        )
        self.dropdown_target.set_callback(self.update_target)
        self.update_from_rad_mode()

    def update_from_rad_mode(self):
        """Update components based on radiation mode selection."""
        if self.global_exc == 0:
            return

        target_id = self.dropdown_target.get_value()
        rad_mode = self.dropdown_rad_mode.get_value()

        self.list_hr_instrument = get_target_instruments(
            self.path_folder_targets, target_id, "HR",
            rad_mode, Path(__file__).name
        )

        self.dropdown_hr_instrument.clean_widget()
        self.dropdown_hr_instrument = MyDropdown(
            self.row1, 0, 5, self.list_hr_instrument,
            "HR Instrument:", color=self.color, columnspan=2
        )
        self.dropdown_hr_instrument.set_callback(self.update_hr)

        # # Control subtract average spectrum checkbox based on radiation mode
        # if rad_mode == "Emission":
        #     # Emission: uncheck and disable
        #     self.checkboc_mean_column.set_value(0)
        #     self.checkboc_mean_column.checkbox.configure(state="disabled")
        # else:  # Transmission
        #     # Transmission: check and enable
        #     self.checkboc_mean_column.set_value(1)
        #     self.checkboc_mean_column.checkbox.configure(state="normal")

        self.update_hr()

    def update_night_sim(self):
        """Update night selection based on simulation checkbox."""
        if self.global_exc == 0:
            return

        target_id = self.dropdown_target.get_value()
        nights = get_target_nights(
            self.path_folder_targets, target_id,
            self.dropdown_rad_mode.get_value(),
            self.dropdown_hr_instrument.get_value(),
            self.checkbox_nights_sim.get_value()
        )
        self.textfield_nights.insert_text(nights)

    def toggle_fixed_comp_state(self):
        """Toggle fixed component entry state based on tangent method checkbox."""
        if self.checkbox_tangent_components_method.get_value():
            # Disable fixed component entry when tangent method is checked
            self.entry_fixed_comp.entry.configure(state="disabled")
        else:
            # Enable fixed component entry when tangent method is unchecked
            self.entry_fixed_comp.entry.configure(state="normal")
