"""
FFEv3.0 Feature Engineering Configuration

Based on FFEv2.0 with significant optimizations
Integrates preprocessing tools for comprehensive feature engineering

Main features:
1. Intelligent scheduling of preprocessing tools
2. Batch processing for large datasets
3. Detailed parameter configuration
"""

import sys
import os

# Import color printer
from myffe.tools.color_printer import ColorPrinter
# Import route visualization
from myffe.tools.visualize_route import visualize_route

# Preprocessing tools order - from basic to advanced
# This list defines the available preprocessing tools in FFE_config.py

# Preprocessing tools
clean_ways = [
    "nan_preprocessing",
    "sample_preprocessing",
    "outlier_preprocessing",
    "str_preprocessing",
    "relative_preprocessing",
    "time_preprocessing",
    "specify_preprocessing",
    "standard_preprocessing",
    "normalized_preprocessing",
]


# Default parameters
default_params = {
    'nan_preprocessing': {
        'nan_processing_col': None,
        'nan_missing_process_choice': None,
        'drop_threshold': 0.75,
        'knn_n_neighbors': 5,
        'fill_values': None,
        'ei_model_types': [],
        'poly_degrees': []
    },
    'sample_preprocessing': {
        'choice1_normal': ['skip'],
        'choice1_advance': ['skip'],
        'balance_method': 'oversample',
        'random_state': 42,
        'high_sample_selection': 'SMOTE',
        'smote_k_neighbors': 5,
        'adasyn_n_neighbors': 5,
        'borderline_smote_k_neighbors': 5,
        'svm_smote_k_neighbors': 5,
        'enn_n_neighbors': 5
    },
    'outlier_preprocessing': {
        'outlier_choice': 'skip',
        'row_drop_thres': 3.0,
        'outlier_action': 'drop',
        'contamination': 0.1,
        'specify_auto_drop_row_extr': None
    },
    'str_preprocessing': {
        'clean_ways': [],
        'str_choice': None,
        'str_cols': [],
        'tfidf_min_df': 1,
        'tfidf_max_df': 1.0,
        'tfidf_max_features': None,
        'lda_min_df': 1,
        'lda_max_df': 1.0,
        'lda_max_features': 1000,
        'lda_n_components': 10,
        'lda_random_state': 42,
        'use_chinese_tokenizer': False,
        'feature_selection': False,
        'k_features': 100
    },
    'relative_preprocessing': {
        'choice3_normal': ['skip'],
        'choice3_advance': ['skip'],
        'linear_cols': None,
        'linear_threshold': 0.75,
        'feature_cols': None,
        'feature_threshold': 0.75,
        'relation_func': 'func1',
        'pca_cols': None,
        'n_components': 10,
        'kernel_selection': 'pca',
        'n_features': 10
    },
    'time_preprocessing': {
        'time_col': None,
        'time_type': 'basic'
    },
    'specify_preprocessing': {
        'drop_col': None,
        'drop_row': None
    },
    'standard_preprocessing': {
        'scaler_method_choice': 'Z-score'
    },
    'normalized_preprocessing': {
        'normalizer_method_choice': 'L2'
    }
}


class FFE_config:
    """
    FFEv3.0 Feature Engineering Configuration
    
    Manages preprocessing tool configuration and initialization
    """
    
    def __init__(self, params_dict=None):
        """
        Initialize FFE configuration
        
        Args:
            params_dict (dict): Custom parameter dictionary {tool_name: {param_name: value}}
        """
        self.params_dict = params_dict if params_dict is not None else {}
        self.tools_dict = {}
        
        ColorPrinter.print_info("FFEv3.0 Feature Engineering Configuration")
        ColorPrinter.print_info("Available preprocessing tools:")
        for way in clean_ways:
            ColorPrinter.print_info(f"  - {way}")
        ColorPrinter.print_any("")
        
        # Visualize parameters if provided
        if self.params_dict:
            ColorPrinter.print_info("Configuration parameters:")
            visualize_route(self.params_dict)
    
    def __call__(self, tools):
        """
        Configure and initialize preprocessing tools
        
        Args:
            tools (list): List of preprocessing tool names or dictionaries
            
        Returns:
            dict: Initialized preprocessing tools dictionary
        """
        if isinstance(tools, list):
            return self.add_tools(tools)
        else:
            ColorPrinter.print_warning("Input must be a list of tools")
            return None
    
    def add_tools(self, tools):
        """
        Add and initialize preprocessing tools
        
        Args:
            tools (list): List of preprocessing tool names or dictionaries
            
        Returns:
            dict: Initialized preprocessing tools dictionary
        """
        self.tools_dict = {}
        tool_count = {}
        
        for tool in tools:
            # Extract tool name and parameters
            if isinstance(tool, dict) and len(tool) == 1:
                tool_name, tool_params = list(tool.items())[0]
            else:
                tool_name = tool
                # Get default parameters for the tool
                tool_params = self._get_tool_params(tool_name)
            
            if tool_name not in clean_ways:
                ColorPrinter.print_warning(f"Warning: {tool_name} is not a valid preprocessing tool")
                continue
            
            # Check if tool should be skipped based on parameters
            skip_tool = False
            
            if tool_name == 'nan_preprocessing':
                if 'nan_missing_process_choice' in tool_params:
                    choices = tool_params['nan_missing_process_choice']
                    if choices is None:
                        skip_tool = True
                    elif isinstance(choices, list) and all('skip' in choice for choice in choices):
                        skip_tool = True
            elif tool_name == 'sample_preprocessing':
                if 'choice1_normal' in tool_params and 'choice1_advance' in tool_params:
                    normal_choice = tool_params['choice1_normal']
                    advance_choice = tool_params['choice1_advance']
                    if (isinstance(normal_choice, list) and 'skip' in normal_choice) and \
                       (isinstance(advance_choice, list) and 'skip' in advance_choice):
                        skip_tool = True
            elif tool_name == 'outlier_preprocessing':
                if 'outlier_choice' in tool_params and tool_params['outlier_choice'] == 'skip':
                    skip_tool = True
            elif tool_name == 'str_preprocessing':
                if ('clean_ways' in tool_params and not tool_params['clean_ways']) and \
                   ('str_choice' not in tool_params or tool_params['str_choice'] is None):
                    skip_tool = True
            elif tool_name == 'relative_preprocessing':
                if 'choice3_normal' in tool_params and 'choice3_advance' in tool_params:
                    normal_choice = tool_params['choice3_normal']
                    advance_choice = tool_params['choice3_advance']
                    if (isinstance(normal_choice, list) and 'skip' in normal_choice) and \
                       (isinstance(advance_choice, list) and 'skip' in advance_choice):
                        skip_tool = True
            elif tool_name == 'time_preprocessing':
                if 'time_col' in tool_params and tool_params['time_col'] is None:
                    skip_tool = True
            elif tool_name == 'standard_preprocessing':
                if 'scaler_method_choice' in tool_params and not tool_params['scaler_method_choice']:
                    skip_tool = True
            elif tool_name == 'normalized_preprocessing':
                if 'normalizer_method_choice' in tool_params and not tool_params['normalizer_method_choice']:
                    skip_tool = True
            elif tool_name == 'specify_preprocessing':
                if 'drop_col' in tool_params and 'drop_row' in tool_params:
                    if not tool_params['drop_col'] and not tool_params['drop_row']:
                        skip_tool = True
            
            if skip_tool:
                ColorPrinter.print_warning(f"Skipping {tool_name} as it is configured to 'skip'")
                continue
            
            # Handle duplicate tool names
            if tool_name in tool_count:
                tool_count[tool_name] += 1
                unique_tool_name = f"{tool_name}_{tool_count[tool_name]}"
            else:
                tool_count[tool_name] = 1
                unique_tool_name = tool_name
            
            # Tool configuration mapping
            tool_config = {
                'nan_preprocessing': ('nan_preprocessing', 'NanPreprocessing', 'shortcut_nan'),
                'sample_preprocessing': ('sample_preprocessing', 'SamplePreprocessing', 'shortcut_sample'),
                'outlier_preprocessing': ('outlier_preprocessing', 'OutlierPreprocessing', 'shortcut_outlier'),
                'str_preprocessing': ('str_preprocessing', 'StrPreprocessing', 'shortcut_str'),
                'relative_preprocessing': ('relative_preprocessing', 'RelativePreprocessing', 'shortcut_relative'),
                'time_preprocessing': ('time_preprocessing', 'TimePreprocessing', 'shortcut_time'),
                'specify_preprocessing': ('specify_preprocessing', 'SpecifyPreprocessing', 'shortcut_specify'),
                'standard_preprocessing': ('standard_preprocessing', 'StandardScaler', 'shortcut_stand'),
                'normalized_preprocessing': ('normalized_preprocessing', 'Normalizer', 'shortcut_norm')
            }
            
            if tool_name in tool_config:
                module_name, class_name, param_name = tool_config[tool_name]
                # Import module
                module_path = f'myffe.preprocessing.{module_name}'
                module = __import__(module_path, fromlist=[class_name])
                # Get class from module
                cls = getattr(module, class_name)
                # Initialize tool with parameters
                self.tools_dict[unique_tool_name] = cls(**{param_name: tool_params})
            else:
                ColorPrinter.print_error(f"Error: Unknown preprocessing tool {tool_name}")
        
        ColorPrinter.print_success(f"OK Initialized {len(self.tools_dict)} preprocessing tools")
        ColorPrinter.print_info(f"Tools: {list(self.tools_dict.keys())}")
        ColorPrinter.print_any("")
        
        # Visualize tool configurations
        if self.tools_dict:
            ColorPrinter.print_info("Tool configurations:")
            # Collect tool configurations
            tool_configs = {}
            for tool_name, tool_instance in self.tools_dict.items():
                # Extract parameters from tool instance
                if hasattr(tool_instance, 'shortcut_nan'):
                    tool_configs[tool_name] = tool_instance.shortcut_nan
                elif hasattr(tool_instance, 'shortcut_sample'):
                    tool_configs[tool_name] = tool_instance.shortcut_sample
                elif hasattr(tool_instance, 'shortcut_outlier'):
                    tool_configs[tool_name] = tool_instance.shortcut_outlier
                elif hasattr(tool_instance, 'shortcut_str'):
                    tool_configs[tool_name] = tool_instance.shortcut_str
                elif hasattr(tool_instance, 'shortcut_relative'):
                    tool_configs[tool_name] = tool_instance.shortcut_relative
                elif hasattr(tool_instance, 'shortcut_time'):
                    tool_configs[tool_name] = tool_instance.shortcut_time
                elif hasattr(tool_instance, 'shortcut_specify'):
                    tool_configs[tool_name] = tool_instance.shortcut_specify
                elif hasattr(tool_instance, 'shortcut_stand'):
                    tool_configs[tool_name] = tool_instance.shortcut_stand
                elif hasattr(tool_instance, 'shortcut_norm'):
                    tool_configs[tool_name] = tool_instance.shortcut_norm
            
            if tool_configs:
                visualize_route(tool_configs)
        
        return self.tools_dict
    
    def _get_tool_params(self, tool_name):
        """
        Get tool parameters with user overrides
        
        Args:
            tool_name (str): Tool name
            
        Returns:
            dict: Tool parameters
        """
        # Get default parameters
        params = default_params.get(tool_name, {}).copy()
        
        # Apply user parameters
        user_params = self.params_dict.get(tool_name, {})
        self._merge_params(params, user_params)
        
        return params
    
    def _merge_params(self, target, source):
        """
        Merge parameter dictionaries
        
        Args:
            target (dict): Target dictionary
            source (dict): Source dictionary
        """
        for key, value in source.items():
            if key in target and isinstance(target[key], dict) and isinstance(value, dict):
                # Recursively merge nested dictionaries
                self._merge_params(target[key], value)
            else:
                # Override value
                target[key] = value
    
    def get_tools_dict(self):
        """
        Get tools dictionary
        
        Returns:
            dict: Preprocessing tools dictionary
        """
        return self.tools_dict
    
    def get_params_dict(self):
        """
        Get parameters dictionary
        
        Returns:
            dict: Configuration parameters
        """
        return self.params_dict
