"""Exchange configuration management for OpenClaw trading system.

Provides secure storage and management of exchange API configurations
with encryption support for sensitive data.
"""

from __future__ import annotations

import asyncio
import base64
import json
import secrets
import uuid
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional

import yaml
from cryptography.fernet import Fernet
from pydantic import BaseModel, Field, field_validator

from openclaw.utils.logging import get_logger

logger = get_logger("config.exchange")


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

    BINANCE = "binance"
    BINANCE_FUTURES = "binanceusdm"
    OKX = "okx"
    BYBIT = "bybit"
    KUCOIN = "kucoin"
    GATE = "gate"
    MEXC = "mexc"
    KRAKEN = "kraken"
    COINBASE = "coinbase"


class ProxyConfig(BaseModel):
    """Proxy configuration for exchange connections."""

    enabled: bool = Field(default=False, description="是否启用代理")
    http_proxy: Optional[str] = Field(default=None, description="HTTP代理地址")
    https_proxy: Optional[str] = Field(default=None, description="HTTPS代理地址")
    socks_proxy: Optional[str] = Field(default=None, description="SOCKS代理地址")


class TimeoutConfig(BaseModel):
    """Timeout configuration for exchange connections."""

    connect: int = Field(default=10, description="连接超时(秒)", ge=1, le=300)
    read: int = Field(default=30, description="读取超时(秒)", ge=1, le=300)
    request: int = Field(default=30, description="请求超时(秒)", ge=1, le=300)


class FeeConfig(BaseModel):
    """Trading fee configuration."""

    maker_fee: float = Field(default=0.001, description="Maker费率", ge=0, le=0.1)
    taker_fee: float = Field(default=0.001, description="Taker费率", ge=0, le=0.1)
    withdrawal_fee: Optional[Dict[str, float]] = Field(
        default_factory=dict, description="提现费率"
    )


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

    Contains all settings needed to connect to a cryptocurrency exchange.
    Sensitive fields (api_key, api_secret) are stored encrypted.
    """

    id: str = Field(default_factory=lambda: str(uuid.uuid4())[:8], description="配置ID")
    name: str = Field(..., description="配置名称")
    exchange_id: ExchangeType = Field(..., description="交易所类型")
    api_key: str = Field(..., description="API密钥")
    api_secret: str = Field(..., description="API密钥密码")
    passphrase: Optional[str] = Field(default=None, description="API密钥口令(部分交易所需要)")
    sandbox: bool = Field(default=False, description="是否使用测试网")
    enabled: bool = Field(default=True, description="是否启用")
    proxy: ProxyConfig = Field(default_factory=ProxyConfig, description="代理配置")
    timeout: TimeoutConfig = Field(default_factory=TimeoutConfig, description="超时配置")
    fee_rate: FeeConfig = Field(default_factory=FeeConfig, description="费率配置")
    created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.now, description="更新时间")
    description: Optional[str] = Field(default=None, description="配置描述")

    @field_validator("api_key")
    @classmethod
    def validate_api_key(cls, v: str) -> str:
        """Validate API key is not empty."""
        if not v or not v.strip():
            raise ValueError("API密钥不能为空")
        return v.strip()

    @field_validator("api_secret")
    @classmethod
    def validate_api_secret(cls, v: str) -> str:
        """Validate API secret is not empty."""
        if not v or not v.strip():
            raise ValueError("API密钥密码不能为空")
        return v.strip()

    model_config = {"extra": "ignore"}


class EncryptedExchangeConfig(BaseModel):
    """Encrypted exchange configuration storage model."""

    id: str
    name: str
    exchange_id: str
    api_key_encrypted: str
    api_secret_encrypted: str
    passphrase_encrypted: Optional[str] = None
    sandbox: bool = False
    enabled: bool = True
    proxy: Dict[str, Any] = Field(default_factory=dict)
    timeout: Dict[str, Any] = Field(default_factory=dict)
    fee_rate: Dict[str, Any] = Field(default_factory=dict)
    created_at: str
    updated_at: str
    description: Optional[str] = None


class EncryptionManager:
    """Manages encryption for sensitive configuration data."""

    _instance: Optional[EncryptionManager] = None
    _key: Optional[bytes] = None

    def __new__(cls) -> EncryptionManager:
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def initialize(self, key: Optional[str] = None) -> None:
        """Initialize encryption with a key.

        Args:
            key: Encryption key. If not provided, generates a new one
                 from environment or generates a random key.
        """
        if key:
            # Derive key from provided string
            import hashlib

            self._key = base64.urlsafe_b64encode(hashlib.sha256(key.encode()).digest())
        else:
            # Try to load from file or generate new
            key_file = Path(".encryption_key")
            if key_file.exists():
                self._key = key_file.read_bytes()
            else:
                self._key = Fernet.generate_key()
                key_file.write_bytes(self._key)
                key_file.chmod(0o600)

    def _ensure_key(self) -> bytes:
        """Ensure encryption key is available."""
        if self._key is None:
            self.initialize()
        return self._key  # type: ignore

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

        Args:
            data: Data to encrypt

        Returns:
            Encrypted data as base64 string
        """
        key = self._ensure_key()
        f = Fernet(key)
        encrypted = f.encrypt(data.encode())
        return base64.b64encode(encrypted).decode()

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

        Args:
            encrypted_data: Encrypted data as base64 string

        Returns:
            Decrypted string
        """
        key = self._ensure_key()
        f = Fernet(key)
        decrypted = f.decrypt(base64.b64decode(encrypted_data))
        return decrypted.decode()


class ExchangeConfigManager:
    """Manages exchange configurations with persistence and encryption.

    Handles loading, saving, and testing of exchange configurations.
    Sensitive data is encrypted at rest.
    """

    DEFAULT_CONFIG_PATH = Path("config/exchanges.yaml")

    def __init__(self, config_path: Optional[Path] = None) -> None:
        """Initialize the configuration manager.

        Args:
            config_path: Path to configuration file. Defaults to config/exchanges.yaml
        """
        self.config_path = config_path or self.DEFAULT_CONFIG_PATH
        self._configs: Dict[str, ExchangeConfig] = {}
        self._encryption = EncryptionManager()
        self._lock = asyncio.Lock()

        # Ensure directory exists
        self.config_path.parent.mkdir(parents=True, exist_ok=True)

        # Load existing configs
        self._load_configs()

    def _load_configs(self) -> None:
        """Load configurations from file."""
        if not self.config_path.exists():
            logger.info(f"配置文件不存在: {self.config_path}, 创建新的配置")
            return

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

            for config_data in data.get("exchanges", []):
                try:
                    # Decrypt sensitive fields
                    encrypted_config = EncryptedExchangeConfig(**config_data)
                    config = self._decrypt_config(encrypted_config)
                    self._configs[config.id] = config
                except Exception as e:
                    logger.error(f"加载配置失败: {e}")

            logger.info(f"已加载 {len(self._configs)} 个交易所配置")

        except Exception as e:
            logger.error(f"读取配置文件失败: {e}")

    def _save_configs(self) -> None:
        """Save configurations to file."""
        try:
            encrypted_configs = []
            for config in self._configs.values():
                encrypted_config = self._encrypt_config(config)
                encrypted_configs.append(encrypted_config.model_dump())

            data = {
                "exchanges": encrypted_configs,
                "metadata": {
                    "version": "1.0",
                    "updated_at": datetime.now().isoformat(),
                },
            }

            yaml_content = yaml.safe_dump(
                data, allow_unicode=True, sort_keys=False, default_flow_style=False
            )
            self.config_path.write_text(yaml_content, encoding="utf-8")
            logger.info(f"配置已保存到 {self.config_path}")

        except Exception as e:
            logger.error(f"保存配置失败: {e}")
            raise

    def _encrypt_config(self, config: ExchangeConfig) -> EncryptedExchangeConfig:
        """Encrypt sensitive fields in configuration."""
        return EncryptedExchangeConfig(
            id=config.id,
            name=config.name,
            exchange_id=config.exchange_id.value,
            api_key_encrypted=self._encryption.encrypt(config.api_key),
            api_secret_encrypted=self._encryption.encrypt(config.api_secret),
            passphrase_encrypted=self._encryption.encrypt(config.passphrase)
            if config.passphrase
            else None,
            sandbox=config.sandbox,
            enabled=config.enabled,
            proxy=config.proxy.model_dump(),
            timeout=config.timeout.model_dump(),
            fee_rate=config.fee_rate.model_dump(),
            created_at=config.created_at.isoformat(),
            updated_at=config.updated_at.isoformat(),
            description=config.description,
        )

    def _decrypt_config(self, encrypted: EncryptedExchangeConfig) -> ExchangeConfig:
        """Decrypt sensitive fields in configuration."""
        return ExchangeConfig(
            id=encrypted.id,
            name=encrypted.name,
            exchange_id=ExchangeType(encrypted.exchange_id),
            api_key=self._encryption.decrypt(encrypted.api_key_encrypted),
            api_secret=self._encryption.decrypt(encrypted.api_secret_encrypted),
            passphrase=self._encryption.decrypt(encrypted.passphrase_encrypted)
            if encrypted.passphrase_encrypted
            else None,
            sandbox=encrypted.sandbox,
            enabled=encrypted.enabled,
            proxy=ProxyConfig(**encrypted.proxy),
            timeout=TimeoutConfig(**encrypted.timeout),
            fee_rate=FeeConfig(**encrypted.fee_rate),
            created_at=datetime.fromisoformat(encrypted.created_at),
            updated_at=datetime.fromisoformat(encrypted.updated_at),
            description=encrypted.description,
        )

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

        Returns:
            Dictionary mapping config IDs to ExchangeConfig objects
        """
        return self._configs.copy()

    def get_config(self, config_id: str) -> Optional[ExchangeConfig]:
        """Get a specific configuration by ID.

        Args:
            config_id: Configuration ID

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

    def add_config(
        self,
        exchange: str,
        api_key: str,
        api_secret: str,
        name: Optional[str] = None,
        passphrase: Optional[str] = None,
        testnet: bool = False,
    ) -> str:
        """Add a new exchange configuration.

        Args:
            exchange: Exchange identifier (e.g., 'binance', 'okx')
            api_key: API key
            api_secret: API secret
            name: Optional configuration name
            passphrase: Optional passphrase for exchanges that require it
            testnet: Whether to use testnet

        Returns:
            Configuration ID
        """
        config = ExchangeConfig(
            name=name or f"{exchange}_{len(self._configs) + 1}",
            exchange_id=ExchangeType(exchange.lower()),
            api_key=api_key,
            api_secret=api_secret,
            passphrase=passphrase,
            sandbox=testnet,
        )

        self._configs[config.id] = config
        self._save_configs()
        logger.info(f"添加交易所配置: {config.id} ({config.name})")
        return config.id

    def update_config(
        self,
        config_id: str,
        exchange: Optional[str] = None,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        name: Optional[str] = None,
        passphrase: Optional[str] = None,
        testnet: Optional[bool] = None,
    ) -> Optional[ExchangeConfig]:
        """Update an existing configuration.

        Args:
            config_id: Configuration ID to update
            exchange: New exchange identifier
            api_key: New API key
            api_secret: New API secret
            name: New name
            passphrase: New passphrase
            testnet: New testnet setting

        Returns:
            Updated ExchangeConfig or None if not found
        """
        config = self._configs.get(config_id)
        if not config:
            return None

        if exchange:
            config.exchange_id = ExchangeType(exchange.lower())
        if api_key:
            config.api_key = api_key
        if api_secret:
            config.api_secret = api_secret
        if name:
            config.name = name
        if passphrase is not None:
            config.passphrase = passphrase
        if testnet is not None:
            config.sandbox = testnet

        config.updated_at = datetime.now()

        self._configs[config_id] = config
        self._save_configs()
        logger.info(f"更新交易所配置: {config_id}")
        return config

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

        Args:
            config_id: Configuration ID to delete

        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()
        logger.info(f"删除交易所配置: {config_id}")
        return True

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

        Args:
            config_id: Configuration ID to test

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

        try:
            # Import CCXT adapter
            from openclaw.exchange.ccxt_adapter import CCXTAdapter

            adapter = CCXTAdapter(
                exchange_id=config.exchange_id.value,
                api_key=config.api_key,
                api_secret=config.api_secret,
                passphrase=config.passphrase,
                testnet=config.sandbox,
                use_async=True,
            )

            # Try to connect
            connected = await adapter.connect()

            if connected:
                # Try to fetch balance to verify credentials work
                try:
                    balance = await adapter.get_balance()
                    await adapter.disconnect()
                    return {
                        "success": True,
                        "message": "连接成功",
                        "details": {
                            "exchange": config.exchange_id.value,
                            "balance_count": len(balance),
                        },
                    }
                except Exception as e:
                    await adapter.disconnect()
                    return {
                        "success": True,
                        "message": "连接成功,但获取余额失败",
                        "details": {"exchange": config.exchange_id.value, "error": str(e)},
                    }
            else:
                return {"success": False, "message": "连接失败"}

        except Exception as e:
            error_msg = str(e).lower()
            if "authentication" in error_msg or "apikey" in error_msg:
                return {"success": False, "message": f"认证失败: API密钥无效"}
            elif "network" in error_msg or "timeout" in error_msg:
                return {"success": False, "message": f"网络错误: 请检查网络连接"}
            else:
                return {"success": False, "message": f"测试失败: {str(e)}"}

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

        Args:
            config_id: Configuration ID to validate

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

        errors = []

        # Check API key format (basic checks)
        if len(config.api_key) < 10:
            errors.append("API密钥格式不正确(太短)")

        if len(config.api_secret) < 10:
            errors.append("API密钥密码格式不正确(太短)")

        # Check exchange is supported
        try:
            from openclaw.exchange.ccxt_adapter import CCXTAdapter

            if config.exchange_id.value not in CCXTAdapter.SUPPORTED_EXCHANGES:
                errors.append(f"不支持的交易所: {config.exchange_id.value}")
        except ImportError:
            pass

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


# Global manager instance
_manager_instance: Optional[ExchangeConfigManager] = None


def get_exchange_manager(config_path: Optional[Path] = None) -> ExchangeConfigManager:
    """Get the global exchange configuration manager instance.

    Args:
        config_path: Optional path to configuration file

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


def reset_exchange_manager() -> None:
    """Reset the global manager instance (useful for testing)."""
    global _manager_instance
    _manager_instance = None
