import logging
import os
from typing import Any, Dict, List

import yaml
from dotenv import load_dotenv

from bubobot_mcp.src.utils.custom_error import CustomError
from bubobot_mcp.src.constants.error_codes import ConfigErrorCodes
from bubobot_mcp.src.utils.singleton import BaseSingleton
from bubobot_mcp.src.utils.utils import str2bool


class Config(BaseSingleton):
    def __init__(
        self,
        base_config_path: str,
        env_file_path: str,
    ):
        """
        Initialize the configuration manager.
        
        Args:
            base_config_path: Path to the base YAML configuration file
            env_file_path: Path to the environment variables file (.env)
            
        Raises:
            CustomError: If base configuration cannot be loaded
        """
        self.logger = logging.getLogger(self.__class__.__name__)

        # Load base configuration from YAML file
        self._load_base_config(base_config_path)
        
        # Load environment variables from .env file
        load_dotenv(dotenv_path=env_file_path)

        # Set configuration attributes
        self._set_config_attributes()

    def _load_base_config(self, base_config_path: str) -> None:
        """
        Load base configuration from YAML file.
        
        Args:
            base_config_path: Path to the YAML configuration file
            
        Raises:
            CustomError: If configuration file cannot be loaded or parsed
        """
        try:
            with open(base_config_path, "r", encoding="utf-8") as file:
                self._base_config = yaml.safe_load(file) or {}

        except Exception as e:
            error_message = f"Error loading base configuration | {e}"
            self.logger.error(error_message)
            raise CustomError(
                code=ConfigErrorCodes.LOAD_BASE_CONFIG_ERROR,
                message=error_message
            )

    @staticmethod
    def _deep_merge(base: Dict[str, Any], additional: Dict[str, Any]) -> Dict[str, Any]:
        """
        Deep merge two dictionaries recursively.

        Args:
            base (Dict[str, Any]): Base dictionary to merge into
            additional (Dict[str, Any]): Additional dictionary to merge from

        Returns:
            Dict[str, Any]: Merged dictionary
        """
        result = base.copy()
        for key, value in additional.items():
            # Recursively merge nested dictionaries
            if (
                isinstance(value, dict)
                and key in result
                and isinstance(result[key], dict)
            ):
                result[key] = Config._deep_merge(result[key], value)
            else:
                # Overwrite with new value
                result[key] = value
        return result

    def update_config(self, additional_config_paths: List[str]) -> None:
        """
        Merge additional configuration files into the base configuration.
        
        Args:
            additional_config_paths: List of paths to additional YAML config files
            
        Raises:
            CustomError: If any additional configuration file cannot be loaded
        """
        if additional_config_paths:
            for config_path in additional_config_paths:
                if os.path.exists(config_path):
                    try:
                        with open(config_path, "r", encoding="utf-8") as file:
                            additional_config = yaml.safe_load(file) or {}
                            # Merge additional config with base config
                            self._merged_config = self._deep_merge(
                                self._base_config, additional_config
                            )

                    except Exception as e:
                        error_message = f"Error merging additional configuration from {config_path}: {e}"
                        self.logger.error(error_message)
                        raise CustomError(
                            code=ConfigErrorCodes.UPDATE_CONFIG_ERROR,
                            message=error_message
                        )
                    
            # Re-set configuration attributes after merging
            self._set_config_attributes()

        else:
            self.logger.warning(
                "No additional configuration paths provided. Using base configuration only."
            )

    def _set_config_attributes(self) -> None:
        """
        Set configuration attributes from the merged configuration.
        """
        try:
            # Get the configuration to use (merged or base)
            config_dict = self.get_config_dict()
            
            # Set logging configuration attributes
            logging_config: dict = config_dict.get("logging", {})
            self.save_log = str2bool(os.getenv("SAVE_LOG", "true"))
            self.log_dir = logging_config.get("log_dir", "./logs")

            # Set MCP server configuration attributes
            mcp_server_config: dict = config_dict.get("mcp_server", {})
            self.mcp_server_name = mcp_server_config.get("name", "BuboBotMCP")
            self.mcp_server_version = mcp_server_config.get("version", "1.0.0")

            # Set HTTP server configuration attributes
            self.http_host = os.getenv("HTTP_HOST", "localhost")
            self.http_port = os.getenv("HTTP_PORT", 8000)

            self.openapi_schema_endpoint = os.getenv("OPENAPI_SCHEMA_ENDPOINT", "/docs-json")

            # Set Bubobot API configuration attributes
            self.base_bubobot_url = os.getenv("BASE_BUBOBOT_URL")

            # Set TLS/SSL configuration attributes
            self.ssl_keyfile = os.getenv("SSL_KEYFILE")
            self.ssl_certfile = os.getenv("SSL_CERTFILE")
            self.ssl_keyfile_password = os.getenv("SSL_KEYFILE_PASSWORD")
            self.ssl_version = os.getenv("SSL_VERSION")
            self.ssl_cert_reqs = os.getenv("SSL_CERT_REQS")
            self.ssl_ca_certs = os.getenv("SSL_CA_CERTS")
            self.ssl_ciphers = os.getenv("SSL_CIPHERS")

            # Set other configuration attributes as needed
            # Add more configuration sections here as the application grows
            
        except Exception as e:
            error_message = f"Config Init Error | {e}"
            self.logger.error(error_message)
            raise CustomError(
                code=ConfigErrorCodes.INIT_CONFIG_ERROR,
                message=error_message
            )

    def get_config_dict(self) -> Dict[str, Any]:
        """
        Get the current configuration as a dictionary.
        
        Returns:
            Dict[str, Any]: Current configuration dictionary (merged or base)
        """
        return self._merged_config if hasattr(self, '_merged_config') else self._base_config
