"""Tests for exchange configuration management."""

import asyncio
import tempfile
from datetime import datetime
from pathlib import Path

import pytest
import yaml

from openclaw.config.exchange_config import (
    EncryptionManager,
    ExchangeConfig,
    ExchangeConfigManager,
    ExchangeType,
    FeeConfig,
    ProxyConfig,
    TimeoutConfig,
    get_exchange_manager,
    reset_exchange_manager,
)


class TestEncryptionManager:
    """Test encryption functionality."""

    def test_singleton_pattern(self):
        """Test that EncryptionManager is a singleton."""
        manager1 = EncryptionManager()
        manager2 = EncryptionManager()
        assert manager1 is manager2

    def test_encrypt_decrypt(self):
        """Test encryption and decryption."""
        manager = EncryptionManager()
        manager.initialize("test_key_12345")

        original = "sensitive_data_123"
        encrypted = manager.encrypt(original)
        decrypted = manager.decrypt(encrypted)

        assert encrypted != original
        assert decrypted == original

    def test_different_data_produces_different_ciphertexts(self):
        """Test that same data encrypts differently each time."""
        manager = EncryptionManager()
        manager.initialize("test_key_12345")

        encrypted1 = manager.encrypt("test_data")
        encrypted2 = manager.encrypt("test_data")

        # Fernet produces different ciphertexts for same plaintext
        assert encrypted1 != encrypted2
        # But both decrypt to same plaintext
        assert manager.decrypt(encrypted1) == manager.decrypt(encrypted2)


class TestExchangeConfig:
    """Test ExchangeConfig model."""

    def test_create_basic_config(self):
        """Test creating a basic exchange configuration."""
        config = ExchangeConfig(
            name="Test Binance",
            exchange_id=ExchangeType.BINANCE,
            api_key="test_api_key_12345",
            api_secret="test_secret_12345",
        )

        assert config.name == "Test Binance"
        assert config.exchange_id == ExchangeType.BINANCE
        assert config.api_key == "test_api_key_12345"
        assert config.api_secret == "test_secret_12345"
        assert config.sandbox is False
        assert config.enabled is True

    def test_config_validation_empty_api_key(self):
        """Test that empty API key raises validation error."""
        with pytest.raises(ValueError, match="API密钥不能为空"):
            ExchangeConfig(
                name="Test",
                exchange_id=ExchangeType.BINANCE,
                api_key="",
                api_secret="secret",
            )

    def test_config_validation_empty_api_secret(self):
        """Test that empty API secret raises validation error."""
        with pytest.raises(ValueError, match="API密钥密码不能为空"):
            ExchangeConfig(
                name="Test",
                exchange_id=ExchangeType.BINANCE,
                api_key="key",
                api_secret="  ",
            )

    def test_config_with_optional_fields(self):
        """Test creating config with all optional fields."""
        config = ExchangeConfig(
            name="Full Config",
            exchange_id=ExchangeType.OKX,
            api_key="api_key_12345",
            api_secret="secret_12345",
            passphrase="passphrase_123",
            sandbox=True,
            enabled=False,
            proxy=ProxyConfig(
                enabled=True,
                http_proxy="http://proxy.example.com:8080",
            ),
            timeout=TimeoutConfig(connect=5, read=60, request=60),
            fee_rate=FeeConfig(maker_fee=0.0005, taker_fee=0.001),
            description="Test configuration",
        )

        assert config.passphrase == "passphrase_123"
        assert config.sandbox is True
        assert config.enabled is False
        assert config.proxy.enabled is True
        assert config.proxy.http_proxy == "http://proxy.example.com:8080"
        assert config.timeout.connect == 5
        assert config.fee_rate.maker_fee == 0.0005
        assert config.description == "Test configuration"

    def test_exchange_type_enum(self):
        """Test ExchangeType enum values."""
        assert ExchangeType.BINANCE.value == "binance"
        assert ExchangeType.BINANCE_FUTURES.value == "binanceusdm"
        assert ExchangeType.OKX.value == "okx"
        assert ExchangeType.BYBIT.value == "bybit"


class TestExchangeConfigManager:
    """Test ExchangeConfigManager functionality."""

    @pytest.fixture
    def temp_config_file(self):
        """Create a temporary config file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            yaml.dump({"exchanges": []}, f)
            path = Path(f.name)
        yield path
        path.unlink(missing_ok=True)
        reset_exchange_manager()

    @pytest.fixture
    def manager(self, temp_config_file):
        """Create a config manager with temp file."""
        reset_exchange_manager()
        return ExchangeConfigManager(temp_config_file)

    def test_add_config(self, manager):
        """Test adding a configuration."""
        config_id = manager.add_config(
            exchange="binance",
            api_key="test_key_12345",
            api_secret="test_secret_12345",
            name="Test Binance",
        )

        assert config_id is not None
        assert len(manager.get_all_configs()) == 1

        config = manager.get_config(config_id)
        assert config is not None
        assert config.name == "Test Binance"
        assert config.exchange_id == ExchangeType.BINANCE

    def test_add_multiple_configs(self, manager):
        """Test adding multiple configurations."""
        id1 = manager.add_config(
            exchange="binance",
            api_key="key1",
            api_secret="secret1",
            name="Binance 1",
        )
        id2 = manager.add_config(
            exchange="okx",
            api_key="key2",
            api_secret="secret2",
            name="OKX 1",
        )

        configs = manager.get_all_configs()
        assert len(configs) == 2
        assert id1 in configs
        assert id2 in configs

    def test_get_nonexistent_config(self, manager):
        """Test getting a non-existent configuration."""
        config = manager.get_config("nonexistent_id")
        assert config is None

    def test_update_config(self, manager):
        """Test updating a configuration."""
        config_id = manager.add_config(
            exchange="binance",
            api_key="old_key",
            api_secret="old_secret",
            name="Old Name",
        )

        updated = manager.update_config(
            config_id,
            name="New Name",
            api_key="new_key",
            testnet=True,
        )

        assert updated is not None
        assert updated.name == "New Name"
        assert updated.api_key == "new_key"
        assert updated.sandbox is True
        # Fields not updated should remain
        assert updated.api_secret == "old_secret"

    def test_update_nonexistent_config(self, manager):
        """Test updating a non-existent configuration."""
        result = manager.update_config(
            "nonexistent_id",
            name="New Name",
        )
        assert result is None

    def test_delete_config(self, manager):
        """Test deleting a configuration."""
        config_id = manager.add_config(
            exchange="binance",
            api_key="key",
            api_secret="secret",
        )

        assert len(manager.get_all_configs()) == 1

        deleted = manager.delete_config(config_id)
        assert deleted is True
        assert len(manager.get_all_configs()) == 0

    def test_delete_nonexistent_config(self, manager):
        """Test deleting a non-existent configuration."""
        result = manager.delete_config("nonexistent_id")
        assert result is False

    def test_persistence(self, temp_config_file):
        """Test that configurations persist to file."""
        # Create manager and add config
        manager1 = ExchangeConfigManager(temp_config_file)
        config_id = manager1.add_config(
            exchange="binance",
            api_key="persist_key",
            api_secret="persist_secret",
            name="Persistent Config",
        )

        # Create new manager instance with same file
        reset_exchange_manager()
        manager2 = ExchangeConfigManager(temp_config_file)

        config = manager2.get_config(config_id)
        assert config is not None
        assert config.name == "Persistent Config"
        assert config.api_key == "persist_key"

    def test_validate_config_valid(self, manager):
        """Test validating a valid configuration."""
        config_id = manager.add_config(
            exchange="binance",
            api_key="valid_key_12345",
            api_secret="valid_secret_12345",
        )

        result = manager.validate_config(config_id)
        assert result["valid"] is True
        assert len(result["errors"]) == 0

    def test_validate_config_invalid_short_key(self, manager):
        """Test validating config with short API key."""
        config_id = manager.add_config(
            exchange="binance",
            api_key="short",
            api_secret="valid_secret_12345",
        )

        result = manager.validate_config(config_id)
        assert result["valid"] is False
        assert any("API密钥格式不正确" in e for e in result["errors"])

    def test_validate_nonexistent_config(self, manager):
        """Test validating a non-existent configuration."""
        result = manager.validate_config("nonexistent_id")
        assert result["valid"] is False
        assert any("配置不存在" in e for e in result["errors"])


class TestExchangeConfigAsync:
    """Test async methods of ExchangeConfigManager."""

    @pytest.fixture
    def temp_config_file(self):
        """Create a temporary config file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            yaml.dump({"exchanges": []}, f)
            path = Path(f.name)
        yield path
        path.unlink(missing_ok=True)
        reset_exchange_manager()

    @pytest.fixture
    def manager(self, temp_config_file):
        """Create a config manager with temp file."""
        reset_exchange_manager()
        return ExchangeConfigManager(temp_config_file)

    @pytest.mark.asyncio
    async def test_test_connection_nonexistent_config(self, manager):
        """Test connection test for non-existent config."""
        result = await manager.test_connection("nonexistent_id")
        assert result["success"] is False
        assert "配置不存在" in result["message"]


class TestProxyConfig:
    """Test ProxyConfig model."""

    def test_default_proxy_config(self):
        """Test default proxy configuration."""
        config = ProxyConfig()
        assert config.enabled is False
        assert config.http_proxy is None
        assert config.https_proxy is None
        assert config.socks_proxy is None

    def test_custom_proxy_config(self):
        """Test custom proxy configuration."""
        config = ProxyConfig(
            enabled=True,
            http_proxy="http://proxy.example.com:8080",
            https_proxy="https://proxy.example.com:8443",
            socks_proxy="socks5://proxy.example.com:1080",
        )
        assert config.enabled is True
        assert config.http_proxy == "http://proxy.example.com:8080"


class TestTimeoutConfig:
    """Test TimeoutConfig model."""

    def test_default_timeout_config(self):
        """Test default timeout configuration."""
        config = TimeoutConfig()
        assert config.connect == 10
        assert config.read == 30
        assert config.request == 30

    def test_timeout_validation(self):
        """Test timeout value validation."""
        # Valid values
        config = TimeoutConfig(connect=1, read=1, request=1)
        assert config.connect == 1

        # Invalid values should raise validation error
        with pytest.raises(ValueError):
            TimeoutConfig(connect=0)
        with pytest.raises(ValueError):
            TimeoutConfig(connect=301)


class TestFeeConfig:
    """Test FeeConfig model."""

    def test_default_fee_config(self):
        """Test default fee configuration."""
        config = FeeConfig()
        assert config.maker_fee == 0.001
        assert config.taker_fee == 0.001

    def test_fee_validation(self):
        """Test fee rate validation."""
        # Valid values
        config = FeeConfig(maker_fee=0.0001, taker_fee=0.0005)
        assert config.maker_fee == 0.0001

        # Invalid values should raise validation error
        with pytest.raises(ValueError):
            FeeConfig(maker_fee=-0.001)
        with pytest.raises(ValueError):
            FeeConfig(taker_fee=0.2)


class TestGlobalManager:
    """Test global manager instance functions."""

    def test_get_exchange_manager_singleton(self):
        """Test that get_exchange_manager returns a singleton."""
        reset_exchange_manager()
        manager1 = get_exchange_manager()
        manager2 = get_exchange_manager()
        assert manager1 is manager2

    def test_reset_exchange_manager(self):
        """Test resetting the global manager."""
        manager1 = get_exchange_manager()
        reset_exchange_manager()
        manager2 = get_exchange_manager()
        assert manager1 is not manager2
