"""
Synthetic High-Resolution Spectroscopy Data Generator

This module generates synthetic high-resolution spectroscopic data for exoplanet
observations. It simulates the complete observing process including:
- Stellar spectra with proper normalization
- Planetary atmospheric signatures
- Telluric absorption
- Instrumental effects and noise
- Orbital motion and Doppler shifts

The main function synthetic_HR() orchestrates the entire simulation process.
"""
import shutil
import sys
import pickle
import traceback
import json
import os
import re
from datetime import datetime
from pathlib import Path
from astropy.io import fits
from scipy.interpolate import splrep, splev
from astropy.coordinates import SkyCoord, EarthLocation, AltAz
from scipy.interpolate import UnivariateSpline, interp1d
from astropy.time import Time
import astropy.units as u
import matplotlib
matplotlib.rcParams['agg.path.chunksize'] = 10000
if not os.environ.get('SPHINX_BUILD'):
    matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages

# Imports moved to main() to avoid execution during module import
# Module-level imports for functions are at the bottom of the file
from GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.MathematicalFunctions import \
    MathematicalFunctions
from GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.plot import \
    generate_aligned_matrix_pdf
from GUIBRUSHR.General_Constants.Classes.Night import Night
from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables
from GUIBRUSHR.General_Constants.FunctionsAndConstants.general_functions import (
    create_path_night,
    read_fits, read_depth_fits,
    convolve_resolution, generate_star_model_hr, spectral_convolution_ptr, rv_planet_and_star
)


def compute_spc_at_given_pwv(fit_spc: np.ndarray, mask: np.ndarray, pwv: float, airmass: float) -> np.ndarray:
    """
    Compute telluric spectrum at given precipitable water vapor (PWV) value.
    
    This function takes a precomputed grid of telluric spectra and interpolates
    to compute the telluric transmission at any given PWV and airmass using
    polynomial interpolation in logarithmic space.
    
    Parameters
    ----------
    fit_spc : np.ndarray
        Polynomial fit coefficients for PWV interpolation, shape (2, n_wavelengths)
        First row contains linear coefficients, second row contains constant terms
    mask : np.ndarray
        Boolean mask for invalid wavelength regions where transmission is unreliable
    pwv : float
        Precipitable water vapor value in mm
    airmass : float
        Airmass value for scaling atmospheric absorption (1.0 at zenith)
        
    Returns
    -------
    np.ndarray
        Telluric transmission spectrum at given PWV and airmass, values between 0-1
        where 1 = no absorption, 0 = complete absorption
        
    Notes
    -----
    The interpolation is performed in log-space to handle the exponential nature
    of atmospheric absorption. Masked regions are set to 0 transmission.
    """
    # Linear interpolation in PWV using polynomial coefficients
    pwvFit = np.array([pwv, 1.0]) @ fit_spc
    
    # Scale by airmass (Beer-Lambert law in log space)
    logTelFit = pwvFit * airmass
    
    # Transform back to linear transmission space
    tell_spc = np.exp(logTelFit)
    
    # Set masked (invalid) regions to zero transmission
    tell_spc[np.sum(mask, axis=0) > 0] = 0
    
    return tell_spc


def gen_pwv(size: int, dt: float = 1, pace: float = 6e5, s: float = 0.69598, scale: float = 3.0862) -> np.ndarray:
    """
    Generate realistic time series of precipitable water vapor (PWV) values.
    
    Creates a PWV time series using an autoregressive AR(1) model with exponential
    transformation to ensure positive values. The model captures the temporal
    correlation typical of atmospheric water vapor variations.
    
    Parameters
    ----------
    size : int
        Number of PWV values to generate (time points)
    dt : float, optional
        Time step between observations in seconds, by default 1
    pace : float, optional
        PWV correlation timescale parameter in seconds, by default 6e5 (about 7 days)
        Controls how quickly PWV values decorrelate over time
    s : float, optional
        Scale parameter for the random component, by default 0.69598
        Controls the amplitude of PWV variations
    scale : float, optional
        Final scaling factor to convert to physical PWV units, by default 3.0862
        Typical scaling to get PWV values in mm
        
    Returns
    -------
    np.ndarray
        Array of PWV values in mm, shape (size,)
        Values are always positive due to exponential transformation
        
    Notes
    -----
    The AR(1) model follows: X[t] = p*X[t-1] + eps[t]
    where p is the autocorrelation parameter and eps is white noise.
    The exponential transformation ensures realistic PWV statistics.

    """
    # Calculate autocorrelation parameter from pace and time step
    pace /= dt
    p = pace / (pace + 1)  # Autocorrelation coefficient (0 < p < 1)
    
    # Generate white noise sequence
    random = np.random.normal(size=size)
    
    # Initialize AR(1) process with proper scaling for stationarity
    v = random[0] / np.sqrt(1 - p ** 2)
    sequence = np.empty(size)
    sequence[0] = v
    
    # Generate AR(1) time series
    for i in range(1, size):
        v = v * p + random[i]  # AR(1) recursion
        sequence[i] = v
    
    # Ensure proper variance scaling for stationary process
    sequence *= np.sqrt(1 - p ** 2)
    sequence *= s  # Apply amplitude scaling
    
    # Transform to positive PWV values via exponential
    pwv = np.exp(sequence)
    pwv *= scale  # Final scaling to physical units (mm)
    
    return pwv


def prepare_fit(path_default: str, include_0_1=False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Prepare polynomial fit for PWV interpolation of telluric spectra.
    
    Loads precomputed telluric transmission grids and creates polynomial
    fits for interpolating transmission as a function of PWV. The interpolation
    is performed in logarithmic space for better numerical stability.
    
    Parameters
    ----------
    path_default : str
        Path to default directory containing telluric grid files
        Expected structure: path_default/GUIBRUSHR/Files/Telluric_grids/
    include_0_1 : bool, optional
        Whether to include PWV values of 0.0 and 1.0 mm in the interpolation grid.
        If True, extends the PWV grid to include very dry conditions (0.0 mm)
        and modifies the 1.0 mm point for better sampling, by default False
        
    Returns
    -------
    tuple[np.ndarray, np.ndarray, np.ndarray]
        - fit_spc: Polynomial fit coefficients for PWV interpolation, shape (2, n_wavelengths)
        - telluric_wl: Wavelength array for telluric grid in nm, shape (n_wavelengths,)
        - mask: Boolean mask marking invalid spectral regions, shape (n_PWV, n_wavelengths)
        
    Notes
    -----
    The telluric grid covers PWV values with higher sampling at low PWV values 
    where transmission varies most rapidly. The polynomial fit is linear in 
    PWV vs log(transmission) space. The include_0_1 parameter allows extending
    the grid to include extreme dry conditions for specialized observations.
    
    Raises
    ------
    FileNotFoundError
        If telluric grid files are not found in the expected location
    """
    # Load precomputed telluric transmission grids
    path_grids = Path(path_default, "GUIBRUSHR", "Files", "Telluric_grids")

    # Define PWV grid points (mm) used for telluric model computation
    if include_0_1:
        # Extended PWV grid including very dry conditions (0.0 mm) and modified 1.0 mm sampling
        pwvGrid = [0.0, 0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 2.5, 3.5, 5.0, 7.5, 10.0, 20.0, 30.0]
        # Load transmission grid: shape (n_PWV, n_wavelengths)
        trGrid = np.load(Path(path_grids, '12_trGrid_pwvonly.npy'))
    else:
        # Standard PWV grid starting from 0.05 mm
        pwvGrid = [0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 2.5, 3.5, 5.0, 7.5, 10.0, 20.0, 30.0]
        # Load transmission grid: shape (n_PWV, n_wavelengths)
        trGrid = np.load(Path(path_grids, '13_trGrid_pwvonly.npy'))

    # Load corresponding wavelength array in nm
    telluric_wl = np.load(Path(path_grids, 'wlMod_pwvonly.npy'))
    
    # === Prepare PWV interpolation fit ===
    # Identify problematic regions with extremely low transmission
    mask = trGrid < 1E-80
    
    # Avoid log(-inf) by setting minimum transmission threshold
    trGrid[mask] = 1E-80
    
    # Transform to log space for linear interpolation
    grid = np.log(trGrid)
    
    # Fit linear polynomial: log(transmission) = a*PWV + b
    # Returns coefficients [a, b] for each wavelength
    fit_spc = np.polyfit(pwvGrid, grid, 1)
    
    return fit_spc, telluric_wl, mask


def plot_spectra(n_orders_tot, n_imgs, path_synthetic, flux_order_image, wavelength,
                telluric_spectrum, telluric_spectrum_times_flux, star_spectrum,
                continuum_star, star_spectrum_normalized, spectrum,
                synthetic_model, error_synthetic, synthetic_plus_error_model):
    """
    Generate comprehensive spectral plots for synthetic HR data analysis.
    
    Creates multi-panel plots showing all components of the synthetic spectrum
    generation process for each order and image. Saves plots as PDF files.
    
    Args:
        n_orders_tot: Number of spectral orders
        n_imgs: Number of images (time points)
        path_synthetic: Path to save plot files
        flux_order_image: Mean flux values per order and img
        wavelength: Wavelength arrays
        telluric_spectrum: Telluric absorption spectrum
        telluric_spectrum_times_flux: Telluric spectrum scaled by flux
        star_spectrum: Stellar spectrum
        continuum_star: Stellar continuum
        star_spectrum_normalized: Normalized stellar spectrum
        spectrum: Planetary spectrum
        synthetic_model: Complete synthetic model (no noise)
        error_synthetic: Noise component
        synthetic_plus_error_model: Final synthetic spectrum with noise
    """
    print("=== Starting Spectral Plot Generation ===")
    print(f"Total images to process: {n_imgs}")
    print(f"Total orders per image: {n_orders_tot}")
    print(f"Total plots to generate: {n_imgs * n_orders_tot}")
    
    # Define plot configuration
    plot_config = [
        {
            'title': 'Telluric',
            'xlabel': 'Wavelength [nm]',
            'ylabel': 'Normalized flux',
            'data': telluric_spectrum,
            'label': 'Telluric spectrum'
        },
        {
            'title': f'Telluric multiplied by flux',
            'xlabel': 'Wavelength [nm]', 
            'ylabel': 'Normalized * continuum flux',
            'data': telluric_spectrum_times_flux,
            'label': 'Telluric * continuum flux spectrum'
        },
        {
            'title': 'Stellar',
            'xlabel': 'Wavelength [nm]',
            'ylabel': 'Flux',
            'data': star_spectrum,
            'data2': continuum_star,
            'label': 'Star spectrum',
            'label2': 'Continuum star'
        },
        {
            'title': 'Normalized stellar at continuum',
            'xlabel': 'Wavelength [nm]',
            'ylabel': 'Normalized flux',
            'data': star_spectrum_normalized,
            'label': 'Star spectrum'
        },
        {
            'title': 'Planet',
            'xlabel': 'Wavelength [nm]',
            'ylabel': 'Normalized planet',
            'data': spectrum,
            'label': 'Planet spectrum'
        },
        {
            'title': 'Synthetic without errors',
            'xlabel': 'Wavelength [nm]',
            'ylabel': 'Flux',
            'data': synthetic_model,
            'label': 'Synthetic spectrum no error'
        },
        {
            'title': 'Generated errors',
            'xlabel': 'Wavelength [nm]',
            'ylabel': 'Flux',
            'data': error_synthetic,
            'label': 'Errors'
        },
        {
            'title': 'Synthetic full (telluric * stellar * planet + error)',
            'xlabel': 'Wavelength [nm]',
            'ylabel': 'Flux',
            'data': synthetic_plus_error_model,
            'label': 'Synthetic spectrum + errors'
        }
    ]
    
    # Generate plots for each image
    for img in range(n_imgs):
        print(f"\n  Processing image {img+1}/{n_imgs}")
        pdf_path = Path(path_synthetic, f"spc_{img}", "plot.pdf")
        print(f"  Saving plots to: {pdf_path}")
        
        with PdfPages(pdf_path) as pdf:
            for order in range(n_orders_tot):
                if order % 10 == 0 or order == n_orders_tot - 1:  # Print progress every 10 orders and last order
                    print(f"    Creating plot for order {order+1}/{n_orders_tot}")
                
                # Create figure with grid layout
                fig = plt.figure(figsize=(12, 10))
                gs = gridspec.GridSpec(4, 2, figure=fig)
                
                # Update flux title with actual value
                if flux_order_image.ndim == 2:
                    plot_config[1]['title'] = f'Telluric multiplied by flux {flux_order_image[order, img]:.2e}'
                
                # Create subplots
                for i, config in enumerate(plot_config):
                    row = i // 2
                    col = i % 2
                    ax = fig.add_subplot(gs[row, col])
                    
                    # Set labels and title
                    ax.set_title(config['title'])
                    ax.set_xlabel(config['xlabel'])
                    ax.set_ylabel(config['ylabel'])
                    
                    # Plot primary data
                    current_wavelength = wavelength[:, order, img]
                    current_data = config['data'][:, order, img]
                    ax.plot(current_wavelength, current_data, label=config['label'])
                    
                    # Plot secondary data if present (for stellar plot)
                    if 'data2' in config:
                        current_data2 = config['data2'][:, order, img]
                        ax.plot(current_wavelength, current_data2, label=config['label2'])
                        ax.legend()
                
                # Save plot to PDF
                plt.tight_layout()
                pdf.savefig(fig, bbox_inches='tight')
                fig.clear()
                plt.close(fig)
            
            print(f"    Completed all {n_orders_tot} orders for image {img+1}")
    
    print(f"=== Spectral Plot Generation Completed ===")
    print(f"Generated {n_imgs} PDF files with {n_orders_tot} plots each")


def save_fits_file(data, output_path, transpose_axes=(1, 2, 0)):
    """
    Save a numpy array to a FITS file with optional axis transposition.
    
    Args:
        data: Numpy array to save
        output_path: Path where to save the FITS file
        transpose_axes: Tuple specifying axis order for transposition
    """
    transposed_data = np.transpose(data, transpose_axes)
    hdu = fits.PrimaryHDU(transposed_data)
    hdul = fits.HDUList([hdu])
    hdul.writeto(str(output_path), overwrite=True)


def find_continuum_points(wavelength, flux, n_segments=20, method='max'):
    """
    Find continuum points by dividing spectrum into segments.
    
    This function divides the input spectrum into segments and finds
    representative continuum points in each segment using either the
    maximum value or a high percentile.
    
    Args:
        wavelength: Array of wavelength values
        flux: Array of flux values
        n_segments: Number of segments to divide spectrum into
        method: Method for finding continuum ('max' or 'percentile')
        
    Returns:
        cont_wave: Array of wavelength points for continuum
        cont_flux: Array of flux values for continuum points
    """
    # Calculate segment length
    segment_length = len(flux) // n_segments
    
    cont_wave = []
    cont_flux = []
    
    for i in range(n_segments):
        start_idx = i * segment_length
        
        # Last segment includes remainder
        if i == n_segments - 1:
            end_idx = len(flux)
        else:
            end_idx = (i + 1) * segment_length
        
        segment_flux = flux[start_idx:end_idx]
        segment_wave = wavelength[start_idx:end_idx]
        
        if method == 'max':
            # Find maximum in segment
            max_idx = np.argmax(segment_flux)
        elif method == 'percentile':
            # Use 90th percentile for robustness
            threshold = np.percentile(segment_flux, 90)
            candidates = np.where(segment_flux >= threshold)[0]
            if len(candidates) > 0:
                max_idx = candidates[len(candidates) // 2]
            else:
                max_idx = np.argmax(segment_flux)
        
        cont_wave.append(segment_wave[max_idx])
        cont_flux.append(segment_flux[max_idx])
    
    return np.array(cont_wave), np.array(cont_flux)


def fit_continuum(wavelength, flux, n_segments=20, method='spline', degree=3):
    """
    Fit continuum to spectrum using various methods.
    
    Args:
        wavelength: Array of wavelength values
        flux: Array of flux values  
        n_segments: Number of segments for finding continuum points
        method: Fitting method ('spline', 'polynomial', 'linear')
        degree: Polynomial degree or spline parameter
        
    Returns:
        continuum: Fitted continuum array
        cont_wave: Wavelength points used for fitting
        cont_flux: Flux points used for fitting
    """
    # Find continuum anchor points
    cont_wave, cont_flux = find_continuum_points(wavelength, flux, n_segments)
    
    if method == 'spline':
        # Spline with smoothing parameter
        spline = UnivariateSpline(
            cont_wave, cont_flux, 
            s=len(cont_wave) * 0.1, 
            k=min(3, len(cont_wave) - 1)
        )
        continuum = spline(wavelength)
    
    elif method == 'polynomial':
        # Polynomial fit
        coeffs = np.polyfit(cont_wave, cont_flux, degree)
        continuum = np.polyval(coeffs, wavelength)
    
    elif method == 'linear':
        # Linear interpolation with extrapolation
        f_interp = interp1d(
            cont_wave, cont_flux, kind='linear',
            bounds_error=False, fill_value='extrapolate'
        )
        continuum = f_interp(wavelength)
    
    return continuum, cont_wave, cont_flux


def normalize_spectrum(wavelength, flux, n_segments=20, method='spline', degree=3):
    """
    Normalize spectrum by dividing by fitted continuum.
    
    Args:
        wavelength: Array of wavelength values
        flux: Array of flux values
        n_segments: Number of segments for continuum fitting
        method: Continuum fitting method
        degree: Polynomial degree for fitting
        
    Returns:
        normalized_flux: Flux normalized by continuum
        continuum: Fitted continuum
        cont_points: Tuple of (wavelength, flux) points used for fitting
    """
    inverted_order = False
    if np.array_equal(wavelength[::-1], sorted(wavelength)):
        wavelength = wavelength[::-1]
        flux = flux[::-1]
        inverted_order = True
    continuum, cont_wave, cont_flux = fit_continuum(
        wavelength, flux, n_segments, method, degree
    )
    if inverted_order:
        continuum = continuum[::-1]
        cont_wave = cont_wave[::-1]
        cont_flux = cont_flux[::-1]
    
    # Normalize by dividing flux by continuum
    normalized_flux = flux / continuum
    
    return normalized_flux, continuum, (cont_wave, cont_flux), inverted_order


def analyze_skycalc_file(filename):
    """
    Analyze SkyCalc output file for airmass and validation errors.
    
    This function parses the SkyCalc output to extract the airmass value
    and check for any parameter validation errors.
    
    Args:
        filename: Path to SkyCalc output file
        
    Returns:
        dict: Dictionary containing:
            - airmass: Extracted airmass value or None
            - has_validation_error: Boolean indicating if validation error occurred
            - error_message: Error message string or None
    """
    with open(filename, 'r') as file:
        content = file.read()
    
    # Extract airmass from SkyCalc section
    pattern = r'Data submitted to SkyCalc:.*?airmass:\s*([0-9.]+)'
    match = re.search(pattern, content, re.DOTALL)
    airmass = float(match.group(1)) if match else None
    
    # Check for validation error and extract full message
    error_pattern = r'parameter validation error[:\s]*(.*)$'
    error_match = re.search(error_pattern, content, re.MULTILINE | re.IGNORECASE)
    
    has_error = error_match is not None
    error_message = error_match.group(1).strip() if error_match else None
    
    return {
        'airmass': airmass,
        'has_validation_error': has_error,
        'error_message': error_message
    }


def calculate_airmass(ra, dec, observatory_lat, observatory_lon,
                     observatory_height, obs_time_iso):
    """
    Calculate airmass for astronomical observations.
    
    Computes the airmass (secant of zenith angle) for a target at given
    coordinates observed from a specified location and time.
    
    Args:
        ra: Right ascension in degrees
        dec: Declination in degrees
        observatory_lat: Observatory latitude in degrees
        observatory_lon: Observatory longitude in degrees  
        observatory_height: Observatory height in meters
        obs_time_iso: Observation time in ISO format 'YYYY-MM-DDTHH:mm:ss'
        
    Returns:
        float: Airmass value (secant of zenith angle)
    """
    # Create coordinate objects
    target = SkyCoord(ra=ra * u.deg, dec=dec * u.deg, frame='icrs')
    observatory = EarthLocation(
        lat=observatory_lat * u.deg,
        lon=observatory_lon * u.deg,
        height=observatory_height * u.m
    )
    
    # Parse observation time
    obs_time = Time(obs_time_iso)
    
    # Transform to horizontal coordinates
    altaz = target.transform_to(AltAz(obstime=obs_time, location=observatory))
    print(f"Estimated airmass: {altaz.secz.value}; Time: {obs_time_iso}")
    return altaz.secz.value


def hjd_to_datetime_string(hjd):
    """
    Convert Heliocentric Julian Date to formatted datetime string.
    
    Args:
        hjd: Heliocentric Julian Date
        
    Returns:
        str: Date string in format 'YYYY-MM-DDTHH:MM:SS'
    """
    t = Time(hjd, format='jd', scale='utc')
    dt = t.to_datetime()
    return dt.strftime('%Y-%m-%dT%H:%M:%S')


def date_string_to_hjd(date_str):
    """
    Convert date string to Heliocentric Julian Date.
    
    Supports both 'YYYYMMDD' and 'YYYY-MM-DD' formats.
    
    Args:
        date_str: Date string in format 'YYYYMMDD' or 'YYYY-MM-DD'
        
    Returns:
        float: Heliocentric Julian Date
    """
    # Handle both date formats
    date_str = ''.join(filter(str.isdigit, date_str))
    if len(date_str) == 8 and '-' not in date_str:
        # Format: YYYYMMDD
        dt = datetime.strptime(date_str, '%Y%m%d')
    else:
        # Format: YYYY-MM-DD
        dt = datetime.strptime(date_str, '%Y-%m-%d')
    
    t = Time(dt, scale='utc')
    return t.jd


def find_nearest_hjd(hjd0, period, target_date):
    """
    Find the nearest HJD to a target date given reference HJD and period.
    
    This function finds the closest transit time to a specified date
    using the ephemeris defined by hjd0 and period.
    
    Args:
        hjd0: Reference Heliocentric Julian Date (epoch)
        period: Orbital period in days
        target_date: Target date string in format 'YYYYMMDD'
        
    Returns:
        dict: Dictionary containing:
            - hjd: Nearest HJD to target date
            - date: Formatted date string of nearest HJD
            - n_periods: Number of periods from hjd0
            - time_diff_hours: Time difference in hours from target
            - target_date: Original target date string
            - target_hjd: Target date converted to HJD
    """
    # Convert target date to HJD
    target_hjd = date_string_to_hjd(target_date)
    
    # Estimate number of periods from reference
    n_estimate = (target_hjd - hjd0) / period
    n_start = int(np.floor(n_estimate)) - 1  # Search range start
    n_end = int(np.ceil(n_estimate)) + 2     # Search range end
    
    # Initialize search variables
    best_hjd = None
    best_n = None
    min_diff = float('inf')
    best_date_str = None
    
    # Search for closest transit time
    for n in range(n_start, n_end):
        candidate_hjd = hjd0 + n * period
        candidate_date_str = hjd_to_datetime_string(candidate_hjd)
        
        # Calculate time difference in days
        time_diff = abs(candidate_hjd - target_hjd)
        
        if time_diff < min_diff:
            min_diff = time_diff
            best_hjd = candidate_hjd
            best_n = n
            best_date_str = candidate_date_str
    
    # Convert time difference to hours for readability
    time_diff_hours = min_diff * 24
    
    return {
        'hjd': best_hjd,
        'date': best_date_str,
        'n_periods': best_n,
        'time_diff_hours': time_diff_hours,
        'target_date': target_date,
        'target_hjd': target_hjd
    }


def _setup_paths_and_read_model(target_obj, path_target, rad_mode, 
                               instrument_obj, model_CC):
    """
    Setup file paths and read model data for synthetic HR generation.
    
    Args:
        target_obj: Target object with parameters
        path_target: Base path for target data
        rad_mode: Radiation mode (e.g., 'Transmission')
        instrument_obj: Instrument configuration object
        model_CC: Cross-correlation model name
        
    Returns:
        tuple: (path_instrument, transit_depth, wavelength)
            - path_instrument: Path to instrument data directory
            - transit_depth: Model transit depth data array
            - wavelength: Model wavelength data array
    """
    # Create instrument and model paths
    path_instrument = create_path_night(
        path_target, target_obj.name, rad_mode, instrument_obj.name
    )
    path_model_CC = str(Path(
        path_target, target_obj.name, ConstantVariables.CC_FOLDER, 
        rad_mode, model_CC
    ))
    
    print(f"Model path: {path_model_CC}")
    
    # Read model data
    print("Reading model data...")
    transit_depth = read_depth_fits(path_model_CC, rad_mode)
    wavelength = read_fits(str(Path(path_model_CC, "wavelength.fits")))
    print(f"Model wavelength range: {np.min(wavelength):.2f} - {np.max(wavelength):.2f} nm")
    
    return path_instrument, transit_depth, wavelength


def _create_model_splines(wavelength, transit_depth, instrument_obj, 
                         path_stellar_spectrum):
    """
    Create spline interpolations for planetary and stellar models.
    
    Args:
        wavelength: Model wavelength array
        transit_depth: Model transit depth array
        instrument_obj: Instrument configuration object
        path_stellar_spectrum: Path to stellar spectrum file
        
    Returns:
        tuple: (spline_planetary_model, spline_stellar_model)
    """
    # Convolve planetary model with instrument resolution
    print(f"Convolving model with instrument resolution (HWHM: {instrument_obj.hwhm_km_s} km/s)...")
    wl_convolved_resolution, model_convolved_resolution = convolve_resolution(
        wavelength, transit_depth, instrument_obj.hwhm_km_s, 0, 0.1
    )
    
    # Create planetary spline
    spline_planetary_model = splrep(wl_convolved_resolution, model_convolved_resolution)
    print("Planetary model spline created")
    
    # Create stellar spline
    print("Generating stellar model...")
    star_wavelength_cm, star_spectrum, spline_stellar_model = generate_star_model_hr(path_stellar_spectrum, instrument_obj.hwhm_km_s)
    print("Stellar model spline created")
    
    return spline_planetary_model, spline_stellar_model


def _calculate_velocities(target_obj, night_obj, eccentricity=False):
    """
    Calculate orbital velocities for planet and star.
    
    Args:
        target_obj: Target object with orbital parameters
        night_obj: Night object with observational data
        eccentricity: Whether to use eccentric orbit calculations
        
    Returns:
        tuple: (vtot, vtot_star) - planet and stellar velocities
    """
    print("Calculating orbital motion...")

    vtot, vtot_star, _ = rv_planet_and_star(
        eccentricity, target_obj, 0, 0, 0,
        night_obj,"Synthetic",0, None, 0
    )
    print(f"Planet velocity range: {np.min(vtot):.2f} to {np.max(vtot):.2f} km/s")
    print(f"Stellar velocity range: {np.min(vtot_star):.2f} to {np.max(vtot_star):.2f} km/s")

    return vtot, vtot_star


def _calculate_pwv_values(atmospheric_params, night_obj):
    """
    Calculate precipitable water vapor values for each observation.
    
    Uses mathematical functions to generate PWV time series based on
    specified function type and parameters.
    
    Args:
        atmospheric_params: Dictionary with atmospheric parameters including:
            - pwv: PWV function name (e.g., 'constant', 'linear', 'sine')
            - initial_x: Initial value for PWV function
            - final_x: Final value for PWV function  
        night_obj: Night object with observational data containing n_tot_phases
        
    Returns:
        dict: PWV values dictionary with 'y_values' key containing numpy array
              of PWV values for each observation phase
    """
    print(f"Calculating PWV values using function: {atmospheric_params['pwv']}")
    pwv_function = MathematicalFunctions()
    pwv_values = pwv_function.get(
        atmospheric_params["pwv"], 
        atmospheric_params["initial_x"], 
        atmospheric_params["final_x"],
        night_obj.n_tot_phases
    )
    print(f"PWV range: {np.min(pwv_values['y_values']):.2f} to {np.max(pwv_values['y_values']):.2f}")
    return pwv_values


def _save_all_synthetic_results(path_night_syn, path_synthetic_syn, 
                               synthetic_plus_error_model, telluric_spectrum,
                               telluric_spectrum_times_flux, star_spectrum,
                               star_spectrum_normalized, spectrum, 
                               synthetic_model, error_synthetic):
    """
    Save all synthetic spectral results to FITS files.
    
    Args:
        path_night_syn: Path to simulation night directory
        path_synthetic_syn: Path to synthetic telluric directory
        synthetic_plus_error_model: Final synthetic spectrum with noise
        telluric_spectrum: Telluric absorption spectrum
        telluric_spectrum_times_flux: Telluric spectrum scaled by flux
        star_spectrum: Stellar spectrum
        star_spectrum_normalized: Normalized stellar spectrum
        spectrum: Planetary spectrum
        synthetic_model: Complete synthetic model (no noise)
        error_synthetic: Noise component
    """
    print("Saving results...")
    
    # Save main aligned spectrum (final result)
    save_fits_file(
        synthetic_plus_error_model, 
        Path(path_night_syn, 'aligned.fits')
    )
    
    # Save individual components
    fits_files = [
        (telluric_spectrum, 'telluric_spectrum.fits'),
        (telluric_spectrum_times_flux, 'telluric_spectrum_times_flux.fits'),
        (star_spectrum, 'star_spectrum.fits'),
        (star_spectrum_normalized, 'normalized_star_spectrum.fits'),
        (spectrum, 'signal_spectrum.fits'),
        (synthetic_model, 'synthetic_model.fits'),
        (error_synthetic, 'error_synthetic.fits'),
        (synthetic_plus_error_model, 'synthetic_spectrum_with_error.fits')
    ]
    
    for data, filename in fits_files:
        save_fits_file(data, Path(path_synthetic_syn, filename))



def synthetic_HR(
    path_default: str, 
    target_obj,
    path_target: str, 
    rad_mode: str, 
    instrument_obj, 
    string_nights: str,
    model_CC: str, 
    order_sel: str, 
    tell_rm_method: str, 
    path_stellar_spectrum: str = "",
    atmospheric_params: dict = None, 
    almanac_params: dict = None, 
    pwv_params: dict = None, 
    discontinuous: bool = False,
    save_plots: bool = False,
    eccentricity: bool = False,
    noise_factor: float = 0.5,
    gain: float = 1.0,
    include_0_1: bool = False,
    seed: int = 42,
    error_type: str = "Synthetic error"
) -> None: 
    """
    Generate comprehensive synthetic high-resolution spectroscopic data for exoplanet observations.

    This function orchestrates the complete simulation pipeline for high-resolution 
    exoplanet spectroscopy, modeling all physical processes that affect the observed
    spectra. The simulation includes:
    
    **Physical Components:**
    - Planetary atmospheric signatures with Doppler shifts due to orbital motion
    - Stellar spectra with proper continuum normalization and radial velocity variations
    - Telluric absorption computed using SkyCalc atmospheric models
    - Instrumental effects (spectral resolution, noise characteristics)
    - Time-variable atmospheric conditions (PWV, airmass)
    
    **Output Products:**
    - Aligned spectral matrices compatible with standard analysis pipelines
    - Individual spectral components for diagnostic purposes
    - Comprehensive diagnostic plots showing all simulation steps
    - Realistic noise models preserving instrument characteristics

    Parameters
    ----------
    path_default : str
        Root path to the GUIBRUSHR directory structure containing telluric grids,
        stellar models, and planetary atmosphere models
    target_obj : Target
        Target object containing orbital parameters (period, K_p, mass, etc.),
        stellar properties, and observational constraints
    path_target : str
        Base directory path for target-specific data and output files
    rad_mode : str
        Radiation transfer mode - either 'Transmission' for transmission spectroscopy
        or 'Emission' for thermal emission observations
    instrument_obj : Instrument
        Instrument configuration object containing spectral resolution, wavelength
        coverage, noise characteristics, and detector properties
    string_nights : str
        Comma-separated string of observation night identifiers to simulate
        (e.g., "2023-01-15,2023-01-16,2023-01-17")
    model_CC : str
        Cross-correlation model identifier specifying which planetary atmosphere
        model to use (must exist in planetary model database)
    order_sel : str
        Selected spectral order identifier for processing (instrument-specific)
    tell_rm_method : str
        Telluric removal method identifier used for directory structure organization
    path_stellar_spectrum : str, optional
        Path to custom stellar spectrum template file. If empty, uses default
        stellar model based on target parameters, by default ""
    atmospheric_params : dict, optional
        Atmospheric modeling parameters, by default None
        
        Required keys:
            - 'airmass' : float - Target airmass (0 for automatic calculation)
            - 'pwv' : str or float - PWV function name or constant value
            - 'observatory' : str - Observatory identifier for SkyCalc
            - 'wres_mul' : float - Wavelength resolution multiplier
            
        For discontinuous=False mode:
            - 'pwv_func' : str - PWV function name ('constant', 'linear', 'sine')
            - 'initial_x' : float - Initial PWV function parameter
            - 'final_x' : float - Final PWV function parameter
    almanac_params : dict, optional
        Observational coordinate parameters, by default None
        
        Keys (with defaults):
            - 'ra' : float - Right ascension in degrees (145.6013)
            - 'dec' : float - Declination in degrees (-10.33297)
            - 'obs_lat' : float - Observatory latitude in degrees (28.7539999)
            - 'obs_long' : float - Observatory longitude in degrees (-17.88905555)
            - 'obs_alt' : float - Observatory altitude in meters (2387.19999)
    pwv_params : dict, optional
        PWV time series generation parameters, by default None
        
        Keys (with defaults):
            - 'dt' : float - Time step in seconds (1)
            - 'pace' : float - PWV correlation timescale in seconds (6e5)
            - 's' : float - PWV amplitude scale factor (0.69598)
            - 'scale' : float - PWV units scale factor (3.0862)
            - 'tExp' : float - Exposure time per frame in seconds (for discontinuous=False)
    discontinuous : bool, optional
        PWV calculation mode, by default False
        
        If True: Calculate PWV independently for each observation using atmospheric_params
        If False: Generate continuous AR(1) PWV time series using pwv_params
    eccentricity : bool, optional
        Whether to include orbital eccentricity effects in velocity calculations,
        by default False. If False, assumes circular orbit
    save_plots : bool, optional
        Whether to generate and save diagnostic plots showing all spectral components
        and processing steps, by default False
    noise_factor : float, optional
        Noise scaling factor applied to the synthetic noise model. The noise is 
        calculated as sqrt(synthetic_model) * noise_factor * gaussian_matrix.
        Lower values reduce noise amplitude, higher values increase it.
        By default 0.5 (equivalent to the previous /2 factor)
    gain : float, optional
        Gain factor applied to the synthetic model before noise addition.
        This parameter allows scaling of the overall signal amplitude.
        Values > 1 increase signal strength, values < 1 decrease it.
        By default 1.0 (no scaling)
    include_0_1 : bool, optional
        Whether to include PWV values of 0.0 and 1.0 mm in the telluric interpolation grid.
        If True, extends the PWV grid to include very dry atmospheric conditions (0.0 mm)
        which may be relevant for high-altitude observatory sites or specialized
        atmospheric modeling scenarios, by default False
    seed : int, optional
        Random seed for reproducible random number generation. Used for both PWV time series
        generation and synthetic noise creation. Setting a specific seed ensures identical
        results across multiple runs with the same parameters, by default 42
    error_type : str, optional
        Type of error generation method to use. Options are:
        - "Error from reduction": Use errors from actual data reduction pipeline
        - "Synthetic error": Generate synthetic errors using noise model
        By default "Synthetic error"

    Returns
    -------
    None
        Results are saved as FITS files and plots in target-specific directories:
        
        **Main Outputs:**
        - `{night}_syn/aligned.fits` - Final synthetic aligned spectral matrix
        - `{night}_syn/synthetic_telluric/` - Individual spectral components
        
        **Diagnostic Files:**
        - Telluric transmission spectra
        - Stellar spectra (raw and normalized)
        - Planetary absorption/emission signatures
        - Noise models and final synthetic spectra
        - PDF plots showing all processing steps (if save_plots=True)

    Raises
    ------
    FileNotFoundError
        If required model files, telluric grids, or stellar spectra are not found
    ValueError
        If orbital parameters are invalid or atmospheric parameters are malformed
    KeyError
        If required parameters are missing from input dictionaries

    Notes
    -----
    The simulation follows this processing pipeline:
    
    1. **Setup Phase:** Load planetary and stellar models, initialize night objects
    2. **Velocity Calculation:** Compute Doppler shifts from orbital motion
    3. **PWV Generation:** Create time-variable atmospheric water vapor
    4. **Spectral Synthesis:** Generate all spectral components
    5. **Noise Addition:** Apply realistic instrument noise models
    6. **Output Generation:** Save FITS files and diagnostic plots
    
    The output files are organized to be compatible with standard HR analysis
    pipelines used for real observations.

    """
    print("=== Starting Synthetic HR Data Processing ===")
    
    # Set random seed for reproducible results
    np.random.seed(seed)
    print(f"Random seed set to: {seed}")
    
    # Setup and initialization
    list_nights = string_nights.split(',')
    print(f"Processing {len(list_nights)} nights: {list_nights}")
    
    # Setup paths and read model data
    path_instrument, transit_depth, wavelength = _setup_paths_and_read_model(
        target_obj, path_target, rad_mode, instrument_obj, model_CC
    )
    
    # Create interpolation splines for models
    spline_planetary_model, spline_stellar_model = _create_model_splines(
        wavelength, transit_depth, instrument_obj, path_stellar_spectrum
    )

    # Extract and prepare atmospheric parameters
    wres_mul = atmospheric_params.pop("wres_mul")
    obs_lat = almanac_params.pop("obs_lat")
    obs_long = almanac_params.pop("obs_long")
    obs_alt = almanac_params.pop("obs_alt")
    atmospheric_params.pop('pwv_func')
    atmospheric_params.pop('initial_x')
    atmospheric_params.pop('final_x')

    # Process each observation night
    for night in list_nights:
        print(f"\n--- Processing night: {night} ---")
        
        # Setup night-specific paths and objects
        path_night = str(Path(path_instrument, night))
        path_order = Path(path_instrument, night, "orders_selection",  order_sel)
        path_order_tell = path_order / tell_rm_method
        path_order_test = str(Path(path_order_tell, "test"))
        path_good_order = str(path_order / f"good_order_{order_sel}.pkl")

        # Initialize night observation object
        print("Initializing night object...")
        night_obj = Night(
            target_obj.limit_phase_t14, path_instrument, night, rad_mode, 
            path_good_order, path_order_tell, path_order_test,
            table_output_file=None, CC=False, simulation=False, synthetic=True, retrieval_standardize="From pkl"
        )
        print(f"Night initialized: {night_obj.n_tot_phases} phases, {night_obj.n_orders_tot} orders")
        
        # Setup PWV calculation (moved after night_obj initialization)
        if discontinuous:
            # Calculate PWV (precipitable water vapor) time series
            pwv_values = _calculate_pwv_values(atmospheric_params, night_obj)
        else:
            timespan = pwv_params["tExp"] * night_obj.n_tot_phases  # This is the total timespan of the simulated sequence
            fit_spc_tell, wavelength_telluric, mask_telluric = prepare_fit(path_default, include_0_1)
            pwv_values = gen_pwv(night_obj.n_tot_phases, dt=timespan / night_obj.n_tot_phases,
                         pace=pwv_params["pace"], s=pwv_params["s"], scale=pwv_params["scale"])
        
        # Calculate orbital motion and velocities
        vtot, vtot_star = _calculate_velocities(target_obj, night_obj, eccentricity)
        
        # Calculate observation times from hjd_ck (works for both Transmission and Emission)
        # # Old transit-centric logic (commented out):
        # print("Calculating transit times...")
        # transit_datetime_dict = find_nearest_hjd(target_obj.hjd0, target_obj.period, night)
        # times_during_transit = (transit_datetime_dict["hjd"] +
        #                        night_obj.phase_new_range * target_obj.period)
        # print(f"Transit HJD: {transit_datetime_dict['hjd']:.6f}, Date: {transit_datetime_dict['date']}")
        if -999 in night_obj.hjd_ck:
            print("WARNING: hjd_ck contains sentinel values (-999). "
                  "Reconstructing times from ephemeris.")
            nearest = find_nearest_hjd(target_obj.hjd0, target_obj.period, night)
            times_of_observation = nearest["hjd"] + night_obj.phases * target_obj.period
        else:
            times_of_observation = night_obj.hjd_ck
        print(f"Observation HJD range: {times_of_observation[0]:.6f} - {times_of_observation[-1]:.6f}")

        # Generate synthetic telluric spectra for each observation
        print("Processing synthetic telluric spectra...")
        mean_wl_global_data = np.mean(night_obj.complete_wavelength)
        telluric_spline_list = []
        path_synthetic = Path(path_night, "synthetic_telluric")
        
        for img in range(night_obj.n_tot_phases):
            # Progress reporting
            if img % 10 == 0:
                print(f"  Processing telluric spectrum {img+1}/{night_obj.n_tot_phases}")

            # Setup output paths for current image
            path_synthetic_order = Path(path_synthetic, f"spc_{img}")
            path_synthetic_order.mkdir(parents=True, exist_ok=True)
            path_input_file = path_synthetic_order / "input_file.json"
            path_almanac = path_synthetic_order / "almanac.json"
            path_results_synthetic = path_synthetic_order / "output.fits"
            path_summary_skycalc = path_synthetic_order / "summary.txt"

            # Time-dependent parameters
            date_complete_current_img = hjd_to_datetime_string(times_of_observation[img])

            if discontinuous:
                # Configure atmospheric parameters for current image
                # Wavelength range
                min_wl = np.min(night_obj.complete_wavelength[:, :, img])
                max_wl = np.max(night_obj.complete_wavelength[:, :, img])
                atmospheric_params["wmin"] = min_wl - 0.1 * mean_wl_global_data
                atmospheric_params["wmax"] = max_wl + 0.1 * mean_wl_global_data
                atmospheric_params["wres"] = (wres_mul *
                                             (ConstantVariables.CLIGHT / (instrument_obj.hwhm_km_s * 2)))

                # Airmass calculation (automatic if set to 0)
                if "airmass" in atmospheric_params.keys():
                    if atmospheric_params["airmass"] == 0:
                        calculated_airmass = calculate_airmass(
                            almanac_params["ra"], almanac_params["dec"],
                            obs_lat, obs_long, obs_alt, date_complete_current_img
                        )
                        atmospheric_params["airmass"] = calculated_airmass
                        if img == 0:
                            print(f"  Calculated airmass (shown only for img 0): {calculated_airmass:.3f}")

                # PWV for current image
                atmospheric_params["pwv"] = pwv_values["y_values"][img]

                # Setup almanac parameters
                almanac_params["observatory"] = atmospheric_params["observatory"]
                almanac_params["date"] = date_complete_current_img

                # Generate telluric spectrum with validation loop
                configurations_file_not_ok = True
                while configurations_file_not_ok:
                    configurations_file_not_ok = False

                    # Save configuration files
                    with open(path_input_file, 'w') as f:
                        json.dump(atmospheric_params, f, indent=2)
                    with open(path_almanac, 'w') as f:
                        json.dump(almanac_params, f, indent=2)

                    # Execute SkyCalc atmospheric modeling
                    os.system(
                        f"skycalc_cli -i {path_input_file} -o {path_results_synthetic} "
                        f"-a {path_almanac} --verbose > {path_summary_skycalc}"
                    )

                    # Validate SkyCalc output and handle errors
                    summary_dict = analyze_skycalc_file(path_summary_skycalc)

                    # Handle excessive airmass
                    if summary_dict["airmass"] > 3:
                        atmospheric_params["airmass"] = 3
                        configurations_file_not_ok = True
                        print(summary_dict["error_message"])
                # Process SkyCalc results
                if summary_dict["has_validation_error"]:
                    print(summary_dict["error_message"])
                    print("USING SPLINE VALID FOR PREVIOUS IMAGE")
                    telluric_spline_list.append(telluric_spline_list[-1])
                else:
                    # Read and process successful SkyCalc output
                    with fits.open(path_results_synthetic) as hdul:
                        data = hdul[1].data
                    wavelength_telluric = data.field(0)
                    transit_depth_telluric = data.field(4)
            else:
                summary_dict = {"has_validation_error": False}
                calculated_airmass = calculate_airmass(
                    almanac_params["ra"], almanac_params["dec"],
                    obs_lat, obs_long, obs_alt, date_complete_current_img
                )
                transit_depth_telluric = compute_spc_at_given_pwv(
                    fit_spc_tell, mask_telluric, pwv_values[img], calculated_airmass
                )
                # Create columns for the binary table
                col0 = fits.Column(name='WAVELENGTH', format='E', array=wavelength_telluric)
                col1 = fits.Column(name='FIELD1', format='E', array=np.zeros_like(wavelength_telluric))
                col2 = fits.Column(name='FIELD2', format='E', array=np.zeros_like(wavelength_telluric))
                col3 = fits.Column(name='FIELD3', format='E', array=np.zeros_like(wavelength_telluric))
                col4 = fits.Column(name='TRANSIT_DEPTH', format='E', array=transit_depth_telluric)
                # Create the binary table HDU
                cols = fits.ColDefs([col0, col1, col2, col3, col4])
                table_hdu = fits.BinTableHDU.from_columns(cols)
                # Create HDU list with primary (empty) and table extension
                primary_hdu = fits.PrimaryHDU()
                hdul = fits.HDUList([primary_hdu, table_hdu])
                # Write to file
                hdul.writeto(path_results_synthetic, overwrite=True)
                hdul.close()
            # Convolve telluric spectrum with instrument resolution
            transit_depth_telluric_convolved = spectral_convolution_ptr(
                wavelength_telluric * 1e-7, transit_depth_telluric, instrument_obj.hwhm_km_s  # needed in cm
            )
            telluric_spline_list.append(
                splrep(wavelength_telluric, transit_depth_telluric_convolved)
            )

        # Generate synthetic spectra for all components
        print("Generating synthetic spectra...")
        
        # Initialize spectral arrays
        spectrum = np.zeros_like(night_obj.complete_wavelength)
        star_spectrum = np.zeros_like(night_obj.complete_wavelength)
        star_spectrum_normalized = np.zeros_like(night_obj.complete_wavelength)
        continuum_star = np.zeros_like(night_obj.complete_wavelength)
        telluric_spectrum = np.zeros_like(night_obj.complete_wavelength)
        continuum_flux = np.zeros([night_obj.n_pixels, night_obj.n_orders_tot, night_obj.n_tot_phases])
        
        # Process each spectral order
        for order in range(night_obj.n_orders_tot):
            if order % 5 == 0:
                print(f"  Processing order {order+1}/{night_obj.n_orders_tot}")
            
            # Current order wavelength grid
            wl_current_order = night_obj.complete_wavelength[:, order, :]
            
            # Calculate Doppler shifts for planet and star
            dl_planet = wl_current_order * vtot / ConstantVariables.CLIGHT
            wlen_shifted_planet = dl_planet + wl_current_order
            dl_star = wl_current_order * vtot_star / ConstantVariables.CLIGHT
            wlen_shifted_star = (dl_star + wl_current_order) * 1e-7

            # Evaluate planetary spectrum at Doppler-shifted wavelengths
            spectrum[:, order, :] = splev(wlen_shifted_planet, spline_planetary_model, der=0)

            # Evaluate stellar spectrum at Doppler-shifted wavelengths
            star_spectrum[:, order, :] = splev(wlen_shifted_star, spline_stellar_model)

            # Process each image for stellar normalization and telluric evaluation
            for img in range(night_obj.n_tot_phases):
                # Find continuum for flux scaling
                wl_continuum_order, temp_flux_cont = find_continuum_points(
                    night_obj.complete_wavelength[:, order, img],
                    night_obj.aligned[:, order, img]
                )
                coeff = np.polyfit(wl_continuum_order, temp_flux_cont, 2)
                poly_cont = np.poly1d(coeff)
                continuum_flux[:, order, img] = np.array(poly_cont(wl_current_order[:, img]))
                if error_type == "Error from reduction":
                    continuum_flux[:, order, img] = continuum_flux[:, order, img] / np.max(continuum_flux[:, order, img])

                # Normalize stellar spectrum
                (star_spectrum_normalized[:, order, img],
                 continuum_star[:, order, img], _, inverted_order_x) = normalize_spectrum(
                    wlen_shifted_star[:, img], star_spectrum[:, order, img]
                )

                # Evaluate telluric spectrum
                telluric_spectrum[:, order, img] = splev(
                    wl_current_order[:, img], telluric_spline_list[img]
                )
            
            # TODO: Implement emission mode calculations if needed
        temp_value = night_obj.mean_aligned if error_type == "Error from reduction" else 1

        telluric_spectrum[telluric_spectrum < 0] = 0
        telluric_spectrum_times_flux = (
            telluric_spectrum * continuum_flux * gain * temp_value
        )
        if rad_mode == 'Transmission':
            spectrum[:, :, night_obj.no_intransit] = 1
        # Combine all spectral components
        synthetic_model = (
            telluric_spectrum_times_flux * star_spectrum_normalized * spectrum
        )

        # Generate error based on selected type
        if error_type == "Error from reduction":
            # Use errors from actual data reduction pipeline
            print("Using errors from reduction pipeline...")
            # For now, we'll use the existing aligned data errors if available
            # This is a placeholder - in a real implementation, you would load
            # the actual error arrays from the reduction pipeline
            if night_obj.real_error_matrix is not None:
                synthetic_model[night_obj.mask_error_pixels] = 0
                error_synthetic_pre = (synthetic_model / night_obj.snr_total).filled(1)
                print("Using existing aligned error data")
            else:
                print("Warning: No aligned error data available, falling back to synthetic errors")
                # Fall back to synthetic error generation
                error_synthetic_pre = np.sqrt(synthetic_model)
        else:  # "Synthetic error"
            # Generate synthetic errors using noise model
            print("Generating synthetic errors...")
            error_synthetic_pre = np.sqrt(synthetic_model)
        gaussian_matrix = np.random.normal(loc=0.0, scale=1.0, size=night_obj.aligned.shape)
        error_synthetic = error_synthetic_pre * noise_factor * gaussian_matrix

        synthetic_plus_error_model = synthetic_model + error_synthetic
        synthetic_plus_error_model[synthetic_plus_error_model < 1] = 1

        snr_current_order = synthetic_model / error_synthetic_pre
        # Calculate flux statistics and scaling
        mean_snr_order = np.mean(snr_current_order, axis=0)
        print(f"Mean SNR range: {np.min(mean_snr_order):.1f} to {np.max(mean_snr_order):.1f}")
        # Generate final synthetic model with noise
        print(f"Generating noise using error type: {error_type}")
        print(f"Synthetic model flux range: {np.min(synthetic_plus_error_model):.2e} to "
              f"{np.max(synthetic_plus_error_model):.2e}")

        # Setup output directories and copy structure
        print("Saving results...")
        path_night_syn = f"{path_night}syn"  # Put the model name here
        path_night += os.sep + "*"
        os.system(f"mkdir -p {path_night_syn}")
        os.system(f"cp -r {path_night} {path_night_syn}")
        
        # Clean up temporary telluric directory
        path_synthetic_syn = Path(path_night_syn, "synthetic_telluric")
        os.system(f"rm -r {path_synthetic}")
        path_cc = str(Path(path_night_syn, "cross_correlation")) + os.sep + "*"
        os.system(f"rm -r {path_cc}")
        path_order = Path(path_night_syn, "orders_selection", order_sel)

        # Itera su tutti gli elementi nella cartella
        for item in path_order.iterdir():
            if item.is_dir():  # Se è una cartella
                shutil.rmtree(item)

        # Save all synthetic results to FITS files
        _save_all_synthetic_results(
            path_night_syn, path_synthetic_syn, synthetic_plus_error_model,
            telluric_spectrum, telluric_spectrum_times_flux, star_spectrum,
            star_spectrum_normalized, spectrum, synthetic_model, error_synthetic
        )

        if save_plots:
            # Generate diagnostic plots
            plot_spectra(
                night_obj.n_orders_tot, night_obj.n_tot_phases, path_synthetic_syn,
                continuum_flux, night_obj.complete_wavelength, telluric_spectrum,
                telluric_spectrum_times_flux, star_spectrum, continuum_star, 
                star_spectrum_normalized, spectrum, synthetic_model, 
                error_synthetic, synthetic_plus_error_model
            )
        
        # Generate aligned matrix PDF
        generate_aligned_matrix_pdf(
            synthetic_plus_error_model, night_obj.complete_wavelength,
            Path(path_night_syn, "aligned_matrix.pdf"), target_obj.name, inverted_order_x,
            original_aligned_data=night_obj.aligned
        )
        
        print(f"Night {night} completed.")
    
    print("=== Synthetic HR Data Processing Completed ===\n")


def main():
    """
    Main function for command-line execution of synthetic HR simulation.
    
    Loads simulation parameters from a pickle file containing a single dictionary
    with all simulation parameters and executes the synthetic_HR function. 
    Handles command-line arguments, error reporting, and cleanup.
    
    Expected command line usage:
        python3 synthetic_HR.py <pickle_path> <path_default>
    
    Args (from command line):
        pickle_path: Path to pickle file containing simulation parameters dictionary
        path_default: Working directory for simulation
        
    Returns:
        System exit code (0 for success, 1 for error)
        
    Notes:
        The pickle file contains a single dictionary with all simulation parameters
        organized into logical groups (core framework, observation, physical model,
        and processing options).
    """
    
    try:
        # Validate command line arguments
        if len(sys.argv) != 3:
            print("Usage: python3 synthetic_HR.py <pickle_path> <path_default>")
            print("  pickle_path: Path to file containing simulation parameters")
            print("  path_default: Working directory for simulation")
            sys.exit(1)
            
        pickle_path = sys.argv[1]
        path_default = sys.argv[2]
        
        # Setup working environment
        sys.path.append(path_default)
        os.chdir(path_default)
        
        print("=== Background Synthetic HR Simulation ===")
        print(f"Loading parameters from: {pickle_path}")
        print(f"Working directory: {path_default}")
        
        # Load simulation parameters from pickle file
        with open(pickle_path, "rb") as fo:
            simulation_params = pickle.load(fo)
        
        # Extract parameters from the simulation dictionary
        path_default = simulation_params['path_default']
        target_obj = simulation_params['target_obj']
        path_folder_targets = simulation_params['path_folder_targets']
        rad_mode = simulation_params['rad_mode']
        instrument_obj = simulation_params['instrument_obj']
        nights_text = simulation_params['nights_text']
        cross_corr_model = simulation_params['cross_corr_model']
        order_selection = simulation_params['order_selection']
        tell_rm_method = simulation_params['tell_rm_method']
        path_phoenixf = simulation_params['path_stellar_spectrum']
        atmospheric_params = simulation_params['atmospheric_params']
        almanac_params = simulation_params['almanac_params']
        pwv_params = simulation_params['pwv_params']
        discontinuous = simulation_params['discontinuous']
        save_plots = simulation_params['save_plots']
        noise_factor = simulation_params['noise_factor']
        gain = simulation_params['gain']
        include_0_1 = simulation_params['include_0_1']
        eccentricity = simulation_params['eccentricity']

        seed = pwv_params["seed"]
        error_type = simulation_params['error_type']
        
        # Display loaded parameters for verification
        print("\n--- Simulation Configuration ---")
        print(f"Target: {target_obj.name}")
        print(f"Radiation Transfer Mode: {rad_mode}")
        print(f"HR Instrument: {getattr(instrument_obj, 'key', 'Unknown')}")
        print(f"Observation Nights: {nights_text}")
        print(f"Cross-correlation Model: {cross_corr_model}")
        print(f"Order Selection: {order_selection}")
        print(f"Telluric Removal Method: {tell_rm_method}")
        print(f"Stellar Spectrum Path: {path_phoenixf}")
        print(f"Atmospheric Parameters: {len(atmospheric_params)} items")
        print(f"Almanac Parameters: {len(almanac_params)} items")
        print(f"PWV Parameters: {len(pwv_params)} items")
        print(f"Discontinuous PWV: {discontinuous}")
        print(f"Save Plots: {save_plots}")
        print(f"Noise Factor: {noise_factor}")
        print(f"Gain: {gain}")
        print(f"Include 0,1 PWV: {include_0_1}")
        print(f"Eccentricity: {eccentricity}")
        print(f"Seed: {seed}")
        print(f"Error Type: {error_type}")
        print(f"Total Parameters in Dictionary: {len(simulation_params)}")
        print("--- End Configuration ---\n")
        
        print("Starting synthetic HR simulation...")
        
        # Execute the main simulation function
        synthetic_HR(
            path_default,
            target_obj, 
            path_folder_targets, 
            rad_mode, 
            instrument_obj,
            nights_text, 
            cross_corr_model, 
            order_selection,
            tell_rm_method,
            path_phoenixf,
            atmospheric_params,
            almanac_params,
            pwv_params,
            discontinuous,
            save_plots,
            eccentricity=eccentricity,
            noise_factor=noise_factor,
            gain=gain,
            include_0_1=include_0_1,
            seed=seed,
            error_type=error_type
        )
        
        print("=== Synthetic HR Simulation Completed Successfully ===")
        
        # Clean up temporary files
        try:
            os.remove(pickle_path)
            print(f"Cleaned up temporary file: {pickle_path}")
        except Exception as e:
            print(f"Warning: Could not remove temporary file {pickle_path}: {e}")
        
        # Exit with success code
        sys.exit(0)
        
    except FileNotFoundError as e:
        print("=== ERROR: File Not Found ===")
        print(f"Could not find required file: {e}")
        print("Please check that all paths are correct and files exist.")
        sys.exit(1)
        
    except pickle.PickleError as e:
        print("=== ERROR: Pickle File Error ===")
        print(f"Error loading parameters from pickle file: {e}")
        print("The parameter file may be corrupted or incompatible.")
        sys.exit(1)
        
    except Exception as e:
        print("=== ERROR in Background Synthetic HR Simulation ===")
        print(f"Unexpected error: {str(e)}")
        print("\nFull traceback:")
        print(traceback.format_exc())
        print("\nPlease check your input parameters and try again.")
        
        # Exit with error code
        sys.exit(1)


if __name__ == "__main__":
    main()