"""Exchange configuration management for OpenClaw trading system.

Provides secure storage and management of exchange API credentials with:
- AES encryption for API secrets
- Multi-exchange support
- Configuration validation and connection testing
- Integration with the main Config system
"""

from __future__ import annotations

import base64
import hashlib
import json
import os
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any

import yaml
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from pydantic import BaseModel, Field, field_validator


class ExchangeType(str, Enum):
    """Supported exchange types."""

    BINANCE = "binance"
    BINANCE_US = "binance_us"
    COINBASE = "coinbase"
    KRAKEN = "kraken"
    BYBIT = "bybit"
    OKX = "okx"
    GATEIO = "gateio"
    KUCOIN = "kucoin"
    MEXC = "mexc"
    HTX = "htx"


class ExchangeConfig(BaseModel):
    """Exchange configuration model.

    Attributes:
        exchange: Exchange identifier
        name: Display name for this exchange config
        api_key: API key for the exchange
        secret: API secret (encrypted when stored)
        passphrase: Optional passphrase (for exchanges like OKX, Coinbase)
        testnet: Whether to use testnet/sandbox
        enabled: Whether this exchange is enabled
        created_at: Creation timestamp
        updated_at: Last update timestamp
    """

    exchange: ExchangeType = Field(..., description="Exchange identifier")
    name: str = Field(default="", description="Display name for this config")
    api_key: str = Field(..., description="API key")
    secret: str = Field(..., description="API secret")
    passphrase: str | None = Field(default=None, description="Optional passphrase")
    testnet: bool = Field(default=False, description="Use testnet/sandbox")
    enabled: bool = Field(default=True, description="Whether this exchange is enabled")
    created_at: datetime = Field(default_factory=datetime.now, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.now, description="Last update time")

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str, info: Any) -> str:
        """Set default name from exchange if not provided."""
        if not v and "exchange" in info.data:
            return info.data["exchange"].value.upper()
        return v or ""

    @field_validator("updated_at", mode="before")
    @classmethod
    def set_updated_at(cls, v: datetime | None, info: Any) -> datetime:
        """Ensure updated_at is set."""
        return v or datetime.now()

    model_config = {"json_encoders": {datetime: lambda v: v.isoformat()}}


class ExchangeConfigManager:
    """Manager for exchange configurations with encrypted storage.

    Handles secure storage of API credentials using AES encryption
    and provides methods for CRUD operations on exchange configs.
    """

    CONFIG_FILE = Path("config/exchanges.yaml")
    ENV_ENCRYPTION_KEY = "OPENCLAW_EXCHANGE_KEY"

    def __init__(self, encryption_key: str | None = None) -> None:
        """Initialize the config manager.

        Args:
            encryption_key: Optional encryption key. If not provided,
                will use OPENCLAW_EXCHANGE_KEY environment variable.
        """
        self._key = encryption_key or os.getenv(self.ENV_ENCRYPTION_KEY)
        self._configs: dict[str, ExchangeConfig] = {}
        self._fernet: Fernet | None = None

        if self._key:
            self._fernet = self._create_fernet(self._key)

        # Load existing configs
        self._load_configs()

    def _create_fernet(self, key: str) -> Fernet:
        """Create Fernet instance from key.

        Args:
            key: Encryption key string

        Returns:
            Fernet instance
        """
        # Derive a proper key using PBKDF2
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=hashlib.sha256(b"openclaw_exchange_salt_v1").digest(),
            iterations=100000,
        )
        derived_key = base64.urlsafe_b64encode(kdf.derive(key.encode()))
        return Fernet(derived_key)

    def _encrypt(self, data: str) -> str:
        """Encrypt string data.

        Args:
            data: String to encrypt

        Returns:
            Base64-encoded encrypted data
        """
        if not self._fernet:
            raise ValueError(
                f"Encryption key not set. Please set {self.ENV_ENCRYPTION_KEY} environment variable"
            )
        encrypted = self._fernet.encrypt(data.encode())
        return base64.urlsafe_b64encode(encrypted).decode()

    def _decrypt(self, data: str) -> str:
        """Decrypt encrypted data.

        Args:
            data: Base64-encoded encrypted data

        Returns:
            Decrypted string
        """
        if not self._fernet:
            raise ValueError(
                f"Encryption key not set. Please set {self.ENV_ENCRYPTION_KEY} environment variable"
            )
        encrypted = base64.urlsafe_b64decode(data.encode())
        return self._fernet.decrypt(encrypted).decode()

    def _load_configs(self) -> None:
        """Load configurations from file."""
        if not self.CONFIG_FILE.exists():
            return

        try:
            content = self.CONFIG_FILE.read_text(encoding="utf-8")
            data = yaml.safe_load(content) or {}

            for config_id, config_data in data.items():
                try:
                    # Decrypt sensitive fields if encrypted
                    if self._fernet and config_data.get("_encrypted"):
                        if "secret" in config_data:
                            config_data["secret"] = self._decrypt(config_data["secret"])
                        if "passphrase" in config_data and config_data["passphrase"]:
                            config_data["passphrase"] = self._decrypt(config_data["passphrase"])

                    config_data.pop("_encrypted", None)
                    self._configs[config_id] = ExchangeConfig(**config_data)
                except Exception as e:
                    # Log error but continue loading other configs
                    print(f"Error loading exchange config {config_id}: {e}")

        except Exception as e:
            print(f"Error loading exchange configs: {e}")

    def _save_configs(self) -> None:
        """Save configurations to file."""
        self.CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)

        data: dict[str, dict[str, Any]] = {}
        for config_id, config in self._configs.items():
            config_dict = config.model_dump()

            # Encrypt sensitive fields
            if self._fernet:
                config_dict["secret"] = self._encrypt(config_dict["secret"])
                if config_dict.get("passphrase"):
                    config_dict["passphrase"] = self._encrypt(config_dict["passphrase"])
                config_dict["_encrypted"] = True

            data[config_id] = config_dict

        with open(self.CONFIG_FILE, "w", encoding="utf-8") as f:
            yaml.dump(data, f, default_flow_style=False, allow_unicode=True)

    def add_config(
        self,
        exchange: ExchangeType | str,
        api_key: str,
        secret: str,
        name: str = "",
        passphrase: str | None = None,
        testnet: bool = False,
    ) -> str:
        """Add a new exchange configuration.

        Args:
            exchange: Exchange identifier
            api_key: API key
            secret: API secret
            name: Display name
            passphrase: Optional passphrase
            testnet: Use testnet

        Returns:
            Config ID
        """
        if isinstance(exchange, str):
            exchange = ExchangeType(exchange)

        config_id = f"{exchange.value}_{len(self._configs) + 1}"

        config = ExchangeConfig(
            exchange=exchange,
            name=name or exchange.value.upper(),
            api_key=api_key,
            secret=secret,
            passphrase=passphrase,
            testnet=testnet,
        )

        self._configs[config_id] = config
        self._save_configs()

        return config_id

    def update_config(self, config_id: str, **kwargs: Any) -> ExchangeConfig | None:
        """Update an existing configuration.

        Args:
            config_id: Config ID
            **kwargs: Fields to update

        Returns:
            Updated config or None if not found
        """
        if config_id not in self._configs:
            return None

        config = self._configs[config_id]
        config_data = config.model_dump()
        config_data.update(kwargs)
        config_data["updated_at"] = datetime.now()

        self._configs[config_id] = ExchangeConfig(**config_data)
        self._save_configs()

        return self._configs[config_id]

    def delete_config(self, config_id: str) -> bool:
        """Delete a configuration.

        Args:
            config_id: Config ID

        Returns:
            True if deleted, False if not found
        """
        if config_id not in self._configs:
            return False

        del self._configs[config_id]
        self._save_configs()
        return True

    def get_config(self, config_id: str) -> ExchangeConfig | None:
        """Get a configuration by ID.

        Args:
            config_id: Config ID

        Returns:
            Config or None if not found
        """
        return self._configs.get(config_id)

    def get_all_configs(self) -> dict[str, ExchangeConfig]:
        """Get all configurations.

        Returns:
            Dictionary of config_id -> ExchangeConfig
        """
        return self._configs.copy()

    def get_enabled_configs(self) -> dict[str, ExchangeConfig]:
        """Get all enabled configurations.

        Returns:
            Dictionary of config_id -> ExchangeConfig
        """
        return {k: v for k, v in self._configs.items() if v.enabled}

    def test_connection(self, config_id: str) -> dict[str, Any]:
        """Test connection to an exchange.

        Args:
            config_id: Config ID

        Returns:
            Dictionary with success status and message
        """
        config = self.get_config(config_id)
        if not config:
            return {"success": False, "message": "配置不存在"}

        try:
            # Import here to avoid circular imports
            from openclaw.exchange.base import ExchangeAdapter

            # Try to create adapter and fetch balance
            adapter = ExchangeAdapter.create(config.exchange.value, config)
            balance = adapter.get_balance()

            return {
                "success": True,
                "message": f"连接成功 - {config.exchange.value.upper()}",
                "balance": {b.asset: b.total for b in balance},
            }
        except Exception as e:
            return {
                "success": False,
                "message": f"连接失败: {str(e)}",
            }

    def validate_config(self, config_id: str) -> dict[str, Any]:
        """Validate exchange configuration.

        Args:
            config_id: Config ID

        Returns:
            Dictionary with validation results
        """
        config = self.get_config(config_id)
        if not config:
            return {"valid": False, "errors": ["配置不存在"]}

        errors = []

        # Check required fields
        if not config.api_key:
            errors.append("API Key 不能为空")
        if not config.secret:
            errors.append("API Secret 不能为空")

        # Try connection test
        if not errors:
            connection_result = self.test_connection(config_id)
            if not connection_result["success"]:
                errors.append(connection_result["message"])

        return {
            "valid": len(errors) == 0,
            "errors": errors,
        }

    def export_config(self, config_id: str, include_secrets: bool = False) -> dict[str, Any] | None:
        """Export configuration.

        Args:
            config_id: Config ID
            include_secrets: Whether to include API secrets

        Returns:
            Config data or None if not found
        """
        config = self.get_config(config_id)
        if not config:
            return None

        data = config.model_dump()
        if not include_secrets:
            data["secret"] = "***"
            data["passphrase"] = "***" if data["passphrase"] else None

        return data


# Global manager instance
_manager_instance: ExchangeConfigManager | None = None


def get_exchange_manager() -> ExchangeConfigManager:
    """Get the global exchange config manager instance.

    Returns:
        ExchangeConfigManager instance
    """
    global _manager_instance
    if _manager_instance is None:
        _manager_instance = ExchangeConfigManager()
    return _manager_instance


def set_exchange_manager(manager: ExchangeConfigManager) -> None:
    """Set the global exchange config manager instance.

    Args:
        manager: Manager instance to set
    """
    global _manager_instance
    _manager_instance = manager
