"""
Frame Table Module for Retrieval Analysis

This module contains the FrameTable class which provides a GUI table interface
for displaying and managing frame retrieval analysis data from a SQLite database.
"""

import traceback
import pandas as pd
from numpy import array
from pathlib import Path
from tkinter import messagebox

from GUIBRUSHR.GUI.DataInterface.DBSQLite3 import DBSQLite3
from GUIBRUSHR.GUI.WIDGET.MyTable import MyTable
from GUIBRUSHR.GUI.LAYOUT.MyPanel import MyPanel
from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables
from GUIBRUSHR.General_Constants.FunctionsAndConstants.general_functions import get_csv_value


class FrameTable(MyPanel):
    """
    A GUI table component for displaying frame retrieval analysis data.
    
    This class extends MyPanel to provide a table interface that displays
    retrieval analysis results from a SQLite database. It allows filtering
    and viewing of various retrieval parameters and results.
    """

    def __init__(self, parent, row, column, color, width_GUI, path_default, **kwargs):
        """
        Initialize the FrameTable component.
        
        Args:
            parent: Parent widget
            row: Row position in the parent grid
            column: Column position in the parent grid
            color: Background color for the panel
            width_GUI: Width of the GUI component
            path_default: Default path for database operations
            **kwargs: Additional keyword arguments passed to parent class
        """
        self.path_default = path_default
        super().__init__(parent, color, row, column, width=width_GUI, **kwargs)
        self.parent = parent
        
        # Calculate column widths based on GUI width
        minimum_block = int((width_GUI - 200) / 26)
        width_columns = [
            minimum_block * 3,  # Date column
            minimum_block * 4,  # Best parameters column
            minimum_block * 2,  # Priors column
            minimum_block * 2,  # Order selection column
            minimum_block * 8,  # Nights column
            minimum_block * 3,  # Tell removal method column
            minimum_block * 2,  # Rad mode column
            minimum_block * 1,  # File exists column
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0  # Hidden columns
        ]
        
        # Initialize the table widget
        self.table = MyTable(
            self, 
            ConstantVariables.LIST_FILTER_TABLE_COLUMNS, 
            width_columns,
            ConstantVariables.LIST_FILTER_TABLE_COLUMN_TYPES, 
            height=ConstantVariables.HEIGHT_TABLE_FILTER
        )
        
        # Global exception counter (purpose unclear from context)
        self.global_exc = 1

    def _clear_table(self):
        """Clear all items from the table."""
        for item in self.table.get_children():
            self.table.delete(item)

    def _get_filter_values(self):
        """
        Extract filter values from the parent frame filter components.
        
        Returns:
            tuple: Contains all filter values (target, chemistry, t_p_profile, 
                  scatt, ecc, resolution, username, retrieval_model, rad_mode, instruments)
        """
        target = self.parent.frame_filter.dropdown_target.get_value()
        chemistry = self.parent.frame_filter.dropdown_chemistry.get_value()
        t_p_profile = self.parent.frame_filter.dropdown_t_p_profile.get_value()
        scatt = self.parent.frame_filter.dropdown_scattering.get_value()
        ecc = self.parent.frame_filter.dropdown_ecc_opi.get_value()
        resolution = self.parent.frame_filter.dropdown_resolution.get_value()
        username = self.parent.frame_filter.username_t.get_text()
        retrieval_model = self.parent.frame_filter.dropdown_retrieval_mode.get_value()
        rad_mode = self.parent.frame_filter.dropdown_rad_mode.get_value()
        instruments = self.parent.frame_filter.textfield_instruments.get_text()
        
        # Sort and join instruments with underscore
        instruments = "_".join(sorted(instruments.split(',')))
        
        return (target, chemistry, t_p_profile, scatt, ecc, resolution, 
                username, retrieval_model, rad_mode, instruments)

    def _check_file_existence(self, path_target, target_name, retrieval_date):
        """
        Check if the retrieval result files exist for a given target and date.
        
        Args:
            path_target: Base path to targets folder
            target_name: Name of the target
            retrieval_date: Date of the retrieval
            
        Returns:
            tuple: (file_exist_status, df_general_info)
                  file_exist_status: "YES" if files exist, "NO" otherwise
                  df_general_info: DataFrame with general info or None
        """
        curr_path = Path(path_target, target_name, ConstantVariables.RESULTS_FOLDER, retrieval_date)
        
        if (curr_path.exists() and 
            Path(curr_path, "partial_prob_and_chain_burnin.pkl").exists()):
            file_exist = "YES"
            df_general_info = pd.read_csv(Path(curr_path, "df_general_info.csv"))
        else:
            file_exist = "NO"
            df_general_info = None
            
        return file_exist, df_general_info

    def _process_bestpars_parameters(self, bestpars_params, molecules):
        """
        Process best parameters and molecules to create parameter names.
        
        Args:
            bestpars_params: String containing best parameters
            molecules: String containing molecule information
            
        Returns:
            str: Comma-separated string of parameter names
        """
        # Extract parameter names from bestpars_params
        bestpars_parameters_list_only = bestpars_params.split(",")
        bestpars_parameters_list_only = array([item.split('=')[0] for item in bestpars_parameters_list_only])
        
        # Create molecules dictionary
        molecules_list_only = molecules.split(",")
        molecules_dict = dict()
        for mol in molecules_list_only:
            mol = mol.split("=")
            molecules_dict[mol[1]] = mol[0]
        
        # Replace molecule complete names with short names
        for mol_complete in molecules_dict.keys():
            bestpars_parameters_list_only[bestpars_parameters_list_only == mol_complete] = molecules_dict[mol_complete]
        
        # Create comma-separated string of parameter names
        bestpars_names = ""
        for pars in bestpars_parameters_list_only:
            bestpars_names += pars + ","
        bestpars_names = bestpars_names[:-1]  # Remove trailing comma
        
        return bestpars_names

    def _process_sigma_priors(self, sigma_priors):
        """
        Process sigma priors to create prior names string.
        
        Args:
            sigma_priors: String containing sigma prior information
            
        Returns:
            str: Comma-separated string of prior names
        """
        priors_name = ""
        sigma_priors_only = sigma_priors.split(",")
        sigma_priors_only = array([item.split('=')[0] for item in sigma_priors_only])
        
        for pars in sigma_priors_only:
            priors_name += pars + ","
        priors_name = priors_name[:-1]  # Remove trailing comma
        
        return priors_name

    def _get_molecules_equilibrium(self, chemistry, molecules):
        """
        Get molecules for equilibrium chemistry.
        
        Args:
            chemistry: Chemistry type
            molecules: Molecules string
            
        Returns:
            str: Molecules string if equilibrium, empty string otherwise
        """
        if chemistry == "Equilibrium":
            molecules_equilibrium = molecules
        else:
            molecules_equilibrium = ""
        
        return molecules_equilibrium

    def _process_instruments(self, instruments, df_general_info):
        """
        Process instruments information, extracting from CSV if needed.
        
        Args:
            instruments: Instruments string from database
            df_general_info: DataFrame with general information
            
        Returns:
            str: Processed instruments string
        """
        if instruments == "" and df_general_info is not None:
            # Extract instruments from CSV data
            instrument_hr_str = get_csv_value(df_general_info, "instruments_hr_list")
            list_instruments = instrument_hr_str.split("|")
            instrument_var = ""
            
            for curr_instrument in list_instruments:
                curr_instrument = curr_instrument.split("&")
                if curr_instrument[0] not in instrument_var:
                    instrument_var += curr_instrument[0] + "_"
            
            instrument_var = ",".join(sorted(instrument_var[:-1].split('_')))
        else:
            instrument_var = instruments
        
        return instrument_var

    def _insert_table_row(self, index, retrieval_date, bestpars_names, priors_name, 
                         order_selection, nights, tell_rm_method, rad_mode, file_exist,
                         chemistry, t_p_profile, scattering, ecc_opi, resolution,
                         bestpars_params, fixed_params, molecules_equilibrium, 
                         sigma_priors, instrument_var):
        """
        Insert a row into the table with the provided data.
        
        Args:
            index: Row index
            retrieval_date: Date of retrieval
            bestpars_names: Best parameter names
            priors_name: Prior names
            order_selection: Order selection value
            nights: Nights information
            tell_rm_method: Telluric removal method
            rad_mode: Radiation mode
            file_exist: File existence status
            chemistry: Chemistry type
            t_p_profile: Temperature-pressure profile
            scattering: Scattering information
            ecc_opi: Eccentricity OPI
            resolution: Resolution value
            bestpars_params: Best parameters
            fixed_params: Fixed parameters
            molecules_equilibrium: Equilibrium molecules
            sigma_priors: Sigma priors
            instrument_var: Instruments variable
        """
        self.table.insert(
            parent="",
            index="end",
            iid=index,
            text="",
            values=(
                retrieval_date,
                bestpars_names,
                priors_name,
                order_selection,
                nights,
                tell_rm_method,
                rad_mode,
                file_exist,
                chemistry,
                t_p_profile,
                scattering,
                ecc_opi,
                resolution,
                bestpars_params,
                fixed_params,
                molecules_equilibrium,
                sigma_priors,
                instrument_var,
            ),
        )

    def update_table(self):
        """
        Update the table with data from the database based on current filter values.
        
        This method:
        1. Clears the existing table
        2. Gets filter values from the GUI
        3. Queries the database for matching records
        4. Processes each record and adds it to the table
        5. Handles errors gracefully with user feedback
        """
        try:
            # Clear existing table data
            self._clear_table()
            
            # Get filter values from GUI components
            (target, chemistry, t_p_profile, scatt, ecc, resolution, 
             username, retrieval_model, rad_mode, instruments) = self._get_filter_values()
            
            # Query database for retrieval data
            DB = DBSQLite3(self.path_default)
            (
                retrieval_date, target, nights, chemistry, t_p_profile, scattering,
                ecc_opi, order_selection, retrieval_model, resolution, tell_rm_method, 
                rad_mode, instruments, user_id, bestpars_params, fixed_params, 
                molecules, sigma_priors
            ) = DB.select_retrieval_by_target(
                username, target, chemistry=chemistry, t_p_profile=t_p_profile,
                scattering=scatt, ecc_opi=ecc, resolution=resolution,
                retrieval_model=retrieval_model, rad_mode=rad_mode, instruments=instruments
            )
            DB.close_DB()
            
            # Check if data was found
            if retrieval_date is None:
                messagebox.showerror("No data in the DB", "No data in the DB")
            else:
                # Get target path for file existence checking
                path_target = self.parent.parent.frame_input.frame_path.path_folder_targets.get_text()
                
                # Process each retrieval record
                for i in range(len(retrieval_date)):
                    # Check if result files exist
                    file_exist, df_general_info = self._check_file_existence(
                        path_target, target[i], retrieval_date[i]
                    )
                    
                    # Process best parameters and molecules
                    bestpars_names = self._process_bestpars_parameters(
                        bestpars_params[i], molecules[i]
                    )
                    
                    # Process sigma priors
                    priors_name = self._process_sigma_priors(sigma_priors[i])
                    
                    # Get equilibrium molecules
                    molecules_equilibrium = self._get_molecules_equilibrium(
                        chemistry[i], molecules[i]
                    )
                    
                    # Process instruments information
                    instrument_var = self._process_instruments(instruments[i], df_general_info)
                    
                    # Insert row into table
                    self._insert_table_row(
                        i, retrieval_date[i], bestpars_names, priors_name,
                        order_selection[i], nights[i], tell_rm_method[i], rad_mode[i],
                        file_exist, chemistry[i], t_p_profile[i], scattering[i],
                        ecc_opi[i], resolution[i], bestpars_params[i], fixed_params[i],
                        molecules_equilibrium, sigma_priors[i], instrument_var
                    )
                    
        except Exception as e:
            # Handle any errors that occur during table update
            print("Generic error: " + str(e))
            print(traceback.format_exc())
            messagebox.showerror("No data in the DB", "No data in the DB")

    def get_arr_values(self):
        """
        Get the values of the currently selected table row.
        
        Returns:
            tuple: Values from the currently focused table row, or None if no row is selected
        """
        cur_item = self.table.focus()
        item = self.table.item(cur_item)
        return item.get("values")
