import yaml
import os
import time
import logging
from typing import Any

logger = logging.getLogger(__name__)


class Configuration:
    """
    Manages the application's settings, loading and saving them to a YAML file.
    -   Automatically loads the settings from file when instantiated.
    -   Settings can be added or modified with set_setting('key', value)
        or by editing the file directly.
    """

    def __init__(self, config_file='config.yml'):
        """Initializes with default settings and loads saved data."""
        self.config_file = config_file
        self.save_in_progress = False  # Persistent flag to prevent infinite recursion
        self._set_default_settings()
        self.manage_config('load')

    def _set_default_settings(self):
        """Set default settings."""
        self.settings = {
            'application_title': 'BrighterTrades',
            'max_data_loaded': 1000,
            # Exchange Data Manager (EDM) defaults
            'edm': {
                'rest_url': 'http://localhost:8080',
                'ws_url': 'ws://localhost:8080/ws',
                'timeout': 30,
                'enabled': True,
                'reconnect_interval': 5.0,
                'max_reconnect_attempts': 10,
            }
        }

    def _generate_config_data(self):
        """Generates a list of settings to be saved to the config file."""
        return self.settings.copy()

    def manage_config(self, cmd: str):
        """
        Loads or saves settings to the config file according to cmd: 'load' | 'save'
        """

        def _apply_loaded_settings(data):
            """Updates settings from loaded data and marks if saving is needed."""
            needs_save = False
            for key, value in data.items():
                if key not in self.settings or self.settings[key] != value:
                    self.settings[key] = value
                    needs_save = True
            return needs_save

        def _load_config_from_file(filepath):
            try:
                with open(filepath, "r") as file_descriptor:
                    return yaml.safe_load(file_descriptor)
            except yaml.YAMLError:
                timestamp = time.strftime("%Y%m%d-%H%M%S")
                backup_path = f"{filepath}.{timestamp}.backup"
                os.rename(filepath, backup_path)
                logging.warning(f"Corrupt YAML file detected. Backup saved to {backup_path}")
                logging.info(f"Recreating the configuration file with default settings.")
                return None

        def _save_config_to_file(filepath, data):
            try:
                with open(filepath, "w") as file_descriptor:
                    yaml.dump(data, file_descriptor)
            except (IOError, OSError) as e:
                logging.error(f"Failed to save configuration to {filepath}: {e}")
                raise ValueError(f"Failed to save configuration to {filepath}: {e}")

        if cmd == 'load':
            if os.path.exists(self.config_file):
                loaded_data = _load_config_from_file(self.config_file)
                if loaded_data is None:  # Corrupt file case, recreate it
                    _save_config_to_file(self.config_file, self._generate_config_data())
                elif _apply_loaded_settings(loaded_data) and not self.save_in_progress:
                    self.save_in_progress = True
                    self.manage_config('save')
                    self.save_in_progress = False
            else:
                logging.info(f"Configuration file not found. Creating a new one at {self.config_file}.")
                _save_config_to_file(self.config_file, self._generate_config_data())
        elif cmd == 'save':
            _save_config_to_file(self.config_file, self._generate_config_data())
        else:
            raise ValueError('manage_config(): Invalid command.')

    def reset_settings_to_defaults(self):
        """Resets settings to default values."""
        self._set_default_settings()
        self.manage_config('save')

    def get_setting(self, key: str) -> Any:
        """
        Returns the value of the specified setting or None if the key is not found.
        """
        return self.settings.get(key, None)

    def set_setting(self, key: str, value: Any):
        """
        Receives a key and value of any setting and saves the configuration.
        """
        self.settings[key] = value
        self.manage_config('save')

    def get_edm_config(self):
        """
        Get EDM configuration as an EdmConfig object.

        Returns:
            EdmConfig object with current settings
        """
        from edm_client import EdmConfig

        edm_settings = self.get_setting('edm') or {}
        return EdmConfig(
            rest_url=edm_settings.get('rest_url', 'http://localhost:8080'),
            ws_url=edm_settings.get('ws_url', 'ws://localhost:8080/ws'),
            timeout=edm_settings.get('timeout', 30.0),
            enabled=edm_settings.get('enabled', True),
            reconnect_interval=edm_settings.get('reconnect_interval', 5.0),
            max_reconnect_attempts=edm_settings.get('max_reconnect_attempts', 10),
        )

    def is_edm_enabled(self) -> bool:
        """Check if EDM is enabled."""
        edm_settings = self.get_setting('edm') or {}
        return edm_settings.get('enabled', True)

# import yaml
# from Signals import Signals
#
#
# class Configuration:
#     """
#     Configuration class manages the application's settings,
#     signals, strategies, and trades. It loads and saves these
#     configurations to a YAML file.
#
#     Attributes:
#         app_data (dict): Default application settings like title and maximum data load.
#         data (object): Database interaction object from the data.
#         config_FN (str): Filename for storing and loading configurations.
#         signals_list (list): List of available signals initialized by the Signals class.
#         strategies_list (list): List of strategies available for the application.
#         trades (list): List of trades managed by the application.
#         saved_data (dict): Data structure for saving/loading configuration data.
#     """
#
#     def __init__(self, data):
#         """
#         Initializes the Configuration object with default values and loads saved data.
#
#         Args:
#             data (object): Cache object with a database attribute to interact with the database.
#         """
#         # ************** Default values **************
#         # Application metadata such as title and maximum data to be loaded.
#         self.app_data = {
#             'application_title': 'BrighterTrades',  # The title of the program.
#             'max_data_loaded': 1000  # Maximum number of candles to store in memory.
#         }
#
#         # The database object for interaction.
#         self.data = data.db
#
#         # Name of the configuration file.
#         self.config_FN = 'config.yml'
#
#         # List of available signals initialized with default values.
#         self.signals_list = Signals.get_signals_defaults()
#
#         # List to hold available strategies.
#         self.strategies_list = []
#
#         # List to hold trades.
#         self.trades = []
#
#         # Placeholder for data loaded from or to be saved to the file.
#         self.saved_data = None
#
#         # Load any saved data from the configuration file.
#         self.config_and_states('load')
#
#     def update_data(self, data_type, data):
#         """
#         Replace the current list of data sets with an updated list.
#
#         Args:
#             data_type (str): Type of data to be updated ('strategies', 'signals', or 'trades').
#             data (list): The new data to replace the old one.
#
#         Raises:
#             ValueError: If the provided data_type is not supported.
#         """
#         if data_type == 'strategies':
#             self.strategies_list = data
#         elif data_type == 'signals':
#             self.signals_list = data
#         elif data_type == 'trades':
#             self.trades = data
#         else:
#             raise ValueError(f'Configuration: update_data(): Unsupported data_type: {data_type}')
#
#         # Save the updated data to the configuration file.
#         self.config_and_states('save')
#
#     def remove(self, what, name):
#         """
#         Removes an item by name from the saved data list.
#
#         Args:
#             what (str): Type of data to remove ('strategies', 'signals', or 'trades').
#             name (str): The name or unique_id of the item to remove.
#
#         Raises:
#             ValueError: If the item with the specified name is not found.
#         """
#         # Determine the property to match based on the type of data.
#         if what == 'trades':
#             prop = 'unique_id'
#         else:
#             prop = 'name'
#
#         # Remove the item from the list if it matches the name or unique_id.
#         for obj in self.saved_data[what]:
#             if obj[prop] == name:
#                 self.saved_data[what].remove(obj)
#                 break
#
#         # Save the updated data to the configuration file.
#         self.config_and_states('save')
#
#     def config_and_states(self, cmd):
#         """
#         Loads or saves configurable data to the file set in self.config_FN.
#
#         Args:
#             cmd (str): Command to either 'load' or 'save' the configuration data.
#
#         Raises:
#             ValueError: If the command is neither 'load' nor 'save'.
#         """
#
#         # Data structure to hold the current state of signals, strategies, and trades.
#         self.saved_data = {
#             'signals': self.signals_list,
#             'strategies': self.strategies_list,
#             'trades': self.trades
#         }
#
#         def set_loaded_values():
#             """Sets the values in the saved_data object to the class attributes."""
#             if 'signals' in self.saved_data:
#                 self.signals_list = self.saved_data['signals']
#
#             if 'strategies' in self.saved_data:
#                 self.strategies_list = self.saved_data['strategies']
#
#             if 'trades' in self.saved_data:
#                 self.trades = self.saved_data['trades']
#
#         def load_configuration(filepath):
#             """
#             Load configuration data from a YAML file.
#
#             Args:
#                 filepath (str): Path to the configuration file.
#
#             Returns:
#                 dict: Loaded configuration data.
#             """
#             with open(filepath, "r") as file_descriptor:
#                 data = yaml.safe_load(file_descriptor)
#             return data
#
#         def save_configuration(filepath, data):
#             """
#             Save configuration data to a YAML file.
#
#             Args:
#                 filepath (str): Path to the configuration file.
#                 data (dict): Data to save.
#             """
#             with open(filepath, "w") as file_descriptor:
#                 yaml.dump(data, file_descriptor)
#
#         if cmd == 'load':
#             try:
#                 # Attempt to load the configuration from the file.
#                 self.saved_data = load_configuration(self.config_FN)
#                 set_loaded_values()
#             except IOError:
#                 # If the file doesn't exist, save the default values.
#                 save_configuration(self.config_FN, self.saved_data)
#
#         elif cmd == 'save':
#             try:
#                 # Save the current state to the configuration file.
#                 save_configuration(self.config_FN, self.saved_data)
#             except IOError:
#                 raise ValueError("save_configuration(): Couldn't save the file.")
#         else:
#             raise ValueError('save_configuration(): Invalid command received.')
