from decimal import Decimal

import pytest

from binance_quant.config import Settings, load_settings
from binance_quant.models import TradingMode


DEFAULT_TRADING_SYMBOLS = (
    "BTCUSDT",
    "ETHUSDT",
    "BNBUSDT",
    "SOLUSDT",
    "SPCXBUSDT",
    "TSLABUSDT",
    "MUBUSDT",
    "CRCLBUSDT",
    "SNDKBUSDT",
    "NVDABUSDT",
)


def test_load_settings_from_environment(monkeypatch):
    monkeypatch.setenv("BINANCE_MODE", "dry_run")
    monkeypatch.setenv("BINANCE_SYMBOL", "ethusdt")
    monkeypatch.setenv("BINANCE_QUOTE_AMOUNT", "12.5")
    monkeypatch.setenv("BINANCE_MAX_QUOTE_AMOUNT", "20")
    monkeypatch.setenv("BINANCE_FAST_WINDOW", "3")
    monkeypatch.setenv("BINANCE_SLOW_WINDOW", "9")
    monkeypatch.setenv("BINANCE_COOLDOWN_SECONDS", "30")
    monkeypatch.setenv("BINANCE_DATABASE_PATH", "test.sqlite3")

    settings = load_settings()

    assert settings.mode is TradingMode.DRY_RUN
    assert settings.symbol == "ETHUSDT"
    assert settings.quote_amount == Decimal("12.5")
    assert settings.max_quote_amount == Decimal("20")


def test_testnet_live_mode_requires_credentials():
    with pytest.raises(ValueError, match="Testnet live mode requires"):
        Settings(_env_file=None, mode=TradingMode.TESTNET_LIVE, api_key="", api_secret="")


def test_production_live_mode_requires_credentials():
    with pytest.raises(ValueError, match="live mode requires"):
        Settings(_env_file=None, mode=TradingMode.LIVE, api_key="", api_secret="")


def test_production_live_mode_accepts_production_urls():
    settings = Settings(
        _env_file=None,
        mode=TradingMode.LIVE,
        api_key="key",
        api_secret="secret",
        rest_base_url="https://api.binance.com/api",
        stream_base_url="wss://stream.binance.com:9443/ws",
    )

    assert settings.rest_base_url == "https://api.binance.com/api"
    assert settings.rest_base_urls == ("https://api.binance.com/api",)
    assert settings.stream_base_url == "wss://stream.binance.com:9443/ws"


def test_production_live_mode_keeps_explicit_single_rest_url_as_single_endpoint():
    settings = Settings(
        _env_file=None,
        mode=TradingMode.LIVE,
        api_key="key",
        api_secret="secret",
        rest_base_url="https://api4.binance.com/api",
        stream_base_url="wss://stream.binance.com:9443/ws",
    )

    assert settings.rest_base_url == "https://api4.binance.com/api"
    assert settings.rest_base_urls == ("https://api4.binance.com/api",)


def test_production_live_mode_defaults_to_production_urls_when_unset():
    settings = Settings(
        _env_file=None,
        mode=TradingMode.LIVE,
        api_key="key",
        api_secret="secret",
    )

    assert settings.rest_base_url == "https://api.binance.com/api"
    assert settings.rest_base_urls == (
        "https://api.binance.com/api",
        "https://api1.binance.com/api",
        "https://api2.binance.com/api",
        "https://api3.binance.com/api",
        "https://api4.binance.com/api",
    )
    assert settings.stream_base_url == "wss://stream.binance.com:9443/ws"


def test_production_live_mode_accepts_multiple_production_rest_urls():
    settings = Settings(
        _env_file=None,
        mode=TradingMode.LIVE,
        api_key="key",
        api_secret="secret",
        rest_base_urls=" https://api3.binance.com/api , https://api1.binance.com/api/ ",
    )

    assert settings.rest_base_url == "https://api3.binance.com/api"
    assert settings.rest_base_urls == ("https://api3.binance.com/api", "https://api1.binance.com/api")


def test_production_live_mode_rejects_explicit_testnet_urls():
    with pytest.raises(ValueError, match="production URL"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            rest_base_url="https://testnet.binance.vision/api",
        )

    with pytest.raises(ValueError, match="production URL"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            rest_base_urls="https://api1.binance.com/api,https://testnet.binance.vision/api",
        )

    with pytest.raises(ValueError, match="production URL"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            stream_base_url="wss://stream.testnet.binance.vision/ws",
        )


def test_rejects_http_rest_url_for_testnet():
    with pytest.raises(ValueError, match="REST base URL scheme must be https"):
        Settings(_env_file=None, rest_base_url="http://testnet.binance.vision/api")


def test_rejects_ws_stream_url_for_testnet():
    with pytest.raises(ValueError, match="stream base URL scheme must be wss"):
        Settings(_env_file=None, stream_base_url="ws://stream.testnet.binance.vision/ws")


def test_rejects_http_rest_url_for_production_live():
    with pytest.raises(ValueError, match="REST base URL scheme must be https"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            rest_base_url="http://api.binance.com/api",
        )


def test_rejects_ws_stream_url_for_production_live():
    with pytest.raises(ValueError, match="stream base URL scheme must be wss"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            stream_base_url="ws://stream.binance.com:9443/ws",
        )


def test_testnet_mode_rejects_production_rest_url():
    with pytest.raises(ValueError, match="Testnet URL"):
        Settings(_env_file=None, rest_base_url="https://api.binance.com/api")


def test_testnet_mode_rejects_production_stream_url():
    with pytest.raises(ValueError, match="Testnet URL"):
        Settings(_env_file=None, stream_base_url="wss://stream.binance.com:9443/ws")


def test_production_live_mode_defaults_to_futures_production_urls_when_unset():
    settings = Settings(
        _env_file=None,
        mode=TradingMode.LIVE,
        api_key="key",
        api_secret="secret",
    )

    assert settings.futures_rest_base_url == "https://fapi.binance.com"
    assert settings.futures_rest_base_urls == (
        "https://fapi.binance.com",
        "https://fapi1.binance.com",
        "https://fapi2.binance.com",
        "https://fapi3.binance.com",
    )
    assert settings.futures_stream_base_url == "wss://fstream.binance.com/ws"


def test_production_live_mode_accepts_multiple_futures_production_rest_urls():
    settings = Settings(
        _env_file=None,
        mode=TradingMode.LIVE,
        api_key="key",
        api_secret="secret",
        futures_rest_base_urls=" https://fapi2.binance.com/ , https://fapi1.binance.com/ ",
    )

    assert settings.futures_rest_base_url == "https://fapi2.binance.com"
    assert settings.futures_rest_base_urls == ("https://fapi2.binance.com", "https://fapi1.binance.com")


def test_production_live_mode_rejects_futures_testnet_urls():
    with pytest.raises(ValueError, match="production URL"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            futures_rest_base_url="https://testnet.binancefuture.com",
        )

    with pytest.raises(ValueError, match="production URL"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            futures_rest_base_urls="https://fapi1.binance.com,https://testnet.binancefuture.com",
        )

    with pytest.raises(ValueError, match="production URL"):
        Settings(
            _env_file=None,
            mode=TradingMode.LIVE,
            api_key="key",
            api_secret="secret",
            futures_stream_base_url="wss://stream.binancefuture.com/ws",
        )


@pytest.mark.parametrize("mode", [TradingMode.DRY_RUN, TradingMode.TESTNET_LIVE])
def test_non_production_modes_reject_futures_production_rest_url(mode):
    with pytest.raises(ValueError, match="Testnet URL"):
        Settings(
            _env_file=None,
            mode=mode,
            api_key="key",
            api_secret="secret",
            futures_rest_base_url="https://fapi.binance.com",
        )


@pytest.mark.parametrize("mode", [TradingMode.DRY_RUN, TradingMode.TESTNET_LIVE])
def test_non_production_modes_reject_futures_production_stream_url(mode):
    with pytest.raises(ValueError, match="Testnet URL"):
        Settings(
            _env_file=None,
            mode=mode,
            api_key="key",
            api_secret="secret",
            futures_stream_base_url="wss://fstream.binance.com/ws",
        )


def test_fast_window_must_be_less_than_slow_window():
    with pytest.raises(ValueError, match="fast_window must be less than slow_window"):
        Settings(fast_window=20, slow_window=5)


def test_windows_must_be_positive():
    with pytest.raises(ValueError):
        Settings(fast_window=0, slow_window=20)

    with pytest.raises(ValueError):
        Settings(fast_window=1, slow_window=0)


def test_api_key_is_hidden_from_repr():
    settings = Settings(api_key="visible-key")

    assert "visible-key" not in repr(settings)
    assert settings.api_key == "visible-key"


def test_proxy_url_adds_http_scheme_when_missing():
    settings = Settings(_env_file=None, proxy_url="192.168.1.3:28090")

    assert settings.proxy_url == "http://192.168.1.3:28090"


def test_proxy_url_rejects_invalid_scheme():
    with pytest.raises(ValueError, match="proxy_url must be an HTTP or SOCKS URL"):
        Settings(_env_file=None, proxy_url="ftp://192.168.1.3:28090")


def test_auto_buyer_defaults_are_disabled_and_conservative():
    settings = Settings(_env_file=None)

    assert settings.live_auto_buyer_enabled is False
    assert settings.auto_buy_symbols == DEFAULT_TRADING_SYMBOLS
    assert settings.auto_buy_quote_amount == Decimal("5")
    assert settings.auto_buy_existing_base_value_limit == Decimal("5")
    assert settings.auto_buy_daily_limit == 5
    assert settings.auto_buy_take_profit_percent == Decimal("3")
    assert settings.auto_buy_stop_loss_percent == Decimal("2")
    assert settings.auto_buy_stop_limit_buffer_percent == Decimal("0.2")
    assert settings.auto_buy_max_spread_bps == Decimal("10")
    assert settings.auto_buy_max_24h_rally_percent == Decimal("10")
    assert settings.auto_buy_cooldown_seconds == 3600
    assert settings.auto_buy_min_score == Decimal("4")


def test_auto_buyer_symbol_list_parses_environment(monkeypatch):
    monkeypatch.setenv("BINANCE_AUTO_BUY_SYMBOLS", " ethusdt , btcusdt ")

    settings = Settings(_env_file=None)

    assert settings.auto_buy_symbols == ("ETHUSDT", "BTCUSDT")


def test_auto_buyer_existing_base_value_limit_parses_environment(monkeypatch):
    monkeypatch.setenv("BINANCE_AUTO_BUY_QUOTE_AMOUNT", "5")
    monkeypatch.setenv("BINANCE_AUTO_BUY_EXISTING_BASE_VALUE_LIMIT", "20")

    settings = Settings(_env_file=None)

    assert settings.auto_buy_quote_amount == Decimal("5")
    assert settings.auto_buy_existing_base_value_limit == Decimal("20")


def test_auto_buyer_rejects_empty_symbol_list():
    with pytest.raises(ValueError, match="auto_buy_symbols must contain at least one symbol"):
        Settings(_env_file=None, auto_buy_symbols="")


def test_auto_buyer_rejects_scalar_symbol_list():
    with pytest.raises(ValueError, match="auto_buy_symbols must be a string or iterable collection of symbols"):
        Settings(_env_file=None, auto_buy_symbols=123)


def test_auto_buyer_rejects_non_usdt_symbols():
    with pytest.raises(ValueError, match="auto_buy_symbols must be USDT spot symbols"):
        Settings(_env_file=None, auto_buy_symbols="BTCUSDT,ETHBTC")


def test_auto_buyer_rejects_non_positive_limits():
    with pytest.raises(ValueError, match="auto_buy_quote_amount must be positive"):
        Settings(_env_file=None, auto_buy_quote_amount=Decimal("0"))
    with pytest.raises(ValueError, match="auto_buy_existing_base_value_limit must be positive"):
        Settings(_env_file=None, auto_buy_existing_base_value_limit=Decimal("0"))
    with pytest.raises(ValueError, match="auto_buy_daily_limit must be positive"):
        Settings(_env_file=None, auto_buy_daily_limit=0)
    with pytest.raises(ValueError, match="auto_buy_take_profit_percent must be positive"):
        Settings(_env_file=None, auto_buy_take_profit_percent=Decimal("0"))
    with pytest.raises(ValueError, match="auto_buy_stop_loss_percent must be positive"):
        Settings(_env_file=None, auto_buy_stop_loss_percent=Decimal("0"))
    with pytest.raises(ValueError, match="auto_buy_stop_limit_buffer_percent must be positive"):
        Settings(_env_file=None, auto_buy_stop_limit_buffer_percent=Decimal("0"))
    with pytest.raises(ValueError, match="auto_buy_max_spread_bps must be positive"):
        Settings(_env_file=None, auto_buy_max_spread_bps=Decimal("0"))
    with pytest.raises(ValueError, match="auto_buy_max_24h_rally_percent must be positive"):
        Settings(_env_file=None, auto_buy_max_24h_rally_percent=Decimal("0"))
    with pytest.raises(ValueError, match="auto_buy_min_score must be positive"):
        Settings(_env_file=None, auto_buy_min_score=Decimal("0"))


def test_auto_seller_defaults_are_disabled_and_conservative():
    settings = Settings(_env_file=None)

    assert settings.live_auto_seller_enabled is False
    assert settings.auto_sell_symbols == DEFAULT_TRADING_SYMBOLS
    assert settings.auto_sell_daily_limit == 5
    assert settings.auto_sell_cooldown_seconds == 3600
    assert settings.auto_sell_min_score == Decimal("4")
    assert settings.auto_sell_max_spread_bps == Decimal("10")
    assert settings.auto_sell_require_profit is True
    assert settings.auto_sell_min_hold_seconds == 21600


def test_auto_seller_symbol_list_parses_environment(monkeypatch):
    monkeypatch.setenv("BINANCE_AUTO_SELL_SYMBOLS", " solusdt , bnbusdt ")

    settings = Settings(_env_file=None)

    assert settings.auto_sell_symbols == ("SOLUSDT", "BNBUSDT")


def test_auto_seller_rejects_empty_symbol_list():
    with pytest.raises(ValueError, match="auto_sell_symbols must contain at least one symbol"):
        Settings(_env_file=None, auto_sell_symbols="")


def test_auto_seller_rejects_non_usdt_symbols():
    with pytest.raises(ValueError, match="auto_sell_symbols must be USDT spot symbols"):
        Settings(_env_file=None, auto_sell_symbols="BTCUSDT,ETHBTC")


def test_auto_seller_rejects_non_positive_limits():
    with pytest.raises(ValueError, match="auto_sell_daily_limit must be positive"):
        Settings(_env_file=None, auto_sell_daily_limit=0)
    with pytest.raises(ValueError, match="auto_sell_min_score must be positive"):
        Settings(_env_file=None, auto_sell_min_score=Decimal("0"))
    with pytest.raises(ValueError, match="auto_sell_max_spread_bps must be positive"):
        Settings(_env_file=None, auto_sell_max_spread_bps=Decimal("0"))
    with pytest.raises(ValueError):
        Settings(_env_file=None, auto_sell_min_hold_seconds=-1)


def test_ai_trader_defaults_are_disabled_and_conservative():
    settings = Settings(_env_file=None)

    assert settings.live_ai_trader_enabled is False
    assert settings.ai_trader_symbols == DEFAULT_TRADING_SYMBOLS
    assert settings.ai_trader_model is None
    assert settings.ai_trader_min_confidence == Decimal("0.70")
    assert settings.ai_trader_codex_command == "codex"
    assert settings.ai_trader_codex_timeout_seconds == 60
    assert settings.ai_trader_max_actions_per_run == 5
    assert settings.ai_trader_klines == (("1h", 24), ("4h", 30), ("1d", 14))
    assert settings.ai_trader_agg_trades_limit == 500
    assert settings.ai_trader_ws_enabled is True
    assert settings.ai_trader_ws_sample_seconds == 15


def test_ai_trader_settings_parse_environment(monkeypatch):
    monkeypatch.setenv("BINANCE_LIVE_AI_TRADER_ENABLED", "true")
    monkeypatch.setenv("BINANCE_AI_TRADER_SYMBOLS", " ethusdt , btcusdt ")
    monkeypatch.setenv("BINANCE_AI_TRADER_MODEL", "gpt-5")
    monkeypatch.setenv("BINANCE_AI_TRADER_MIN_CONFIDENCE", "0.85")
    monkeypatch.setenv("BINANCE_AI_TRADER_CODEX_COMMAND", r"D:\Program Files\nodejs\codex.cmd")
    monkeypatch.setenv("BINANCE_AI_TRADER_CODEX_TIMEOUT_SECONDS", "90")
    monkeypatch.setenv("BINANCE_AI_TRADER_MAX_ACTIONS_PER_RUN", "7")
    monkeypatch.setenv("BINANCE_AI_TRADER_KLINES", "1h:12,4h:8,1d:3")
    monkeypatch.setenv("BINANCE_AI_TRADER_AGG_TRADES_LIMIT", "250")
    monkeypatch.setenv("BINANCE_AI_TRADER_WS_ENABLED", "false")
    monkeypatch.setenv("BINANCE_AI_TRADER_WS_SAMPLE_SECONDS", "3")

    settings = Settings(_env_file=None)

    assert settings.live_ai_trader_enabled is True
    assert settings.ai_trader_symbols == ("ETHUSDT", "BTCUSDT")
    assert settings.ai_trader_model == "gpt-5"
    assert settings.ai_trader_min_confidence == Decimal("0.85")
    assert settings.ai_trader_codex_command == r"D:\Program Files\nodejs\codex.cmd"
    assert settings.ai_trader_codex_timeout_seconds == 90
    assert settings.ai_trader_max_actions_per_run == 7
    assert settings.ai_trader_klines == (("1h", 12), ("4h", 8), ("1d", 3))
    assert settings.ai_trader_agg_trades_limit == 250
    assert settings.ai_trader_ws_enabled is False
    assert settings.ai_trader_ws_sample_seconds == 3


def test_ai_trader_blank_model_becomes_none():
    settings = Settings(_env_file=None, ai_trader_model=" ")

    assert settings.ai_trader_model is None


def test_ai_trader_rejects_invalid_settings():
    with pytest.raises(ValueError, match="ai_trader_symbols must contain at least one symbol"):
        Settings(_env_file=None, ai_trader_symbols="")
    with pytest.raises(ValueError, match="ai_trader_symbols must be USDT spot symbols"):
        Settings(_env_file=None, ai_trader_symbols="BTCUSDT,ETHBTC")
    with pytest.raises(ValueError, match="ai_trader_min_confidence must be greater than 0 and less than or equal to 1"):
        Settings(_env_file=None, ai_trader_min_confidence=Decimal("0"))
    with pytest.raises(ValueError, match="ai_trader_min_confidence must be greater than 0 and less than or equal to 1"):
        Settings(_env_file=None, ai_trader_min_confidence=Decimal("1.01"))
    with pytest.raises(ValueError, match="ai_trader_codex_command must not be empty"):
        Settings(_env_file=None, ai_trader_codex_command=" ")
    with pytest.raises(ValueError):
        Settings(_env_file=None, ai_trader_codex_timeout_seconds=0)
    with pytest.raises(ValueError, match="ai_trader_max_actions_per_run must be positive"):
        Settings(_env_file=None, ai_trader_max_actions_per_run=0)
    with pytest.raises(ValueError, match="ai_trader_klines must contain at least one interval"):
        Settings(_env_file=None, ai_trader_klines="")
    with pytest.raises(ValueError, match="ai_trader_klines items must use interval:limit"):
        Settings(_env_file=None, ai_trader_klines="1h")
    with pytest.raises(ValueError, match="ai_trader_klines limit must be positive"):
        Settings(_env_file=None, ai_trader_klines="1h:0")
    with pytest.raises(ValueError, match="ai_trader_klines intervals must be unique"):
        Settings(_env_file=None, ai_trader_klines="1h:24,1h:12")
    with pytest.raises(ValueError):
        Settings(_env_file=None, ai_trader_agg_trades_limit=0)
    with pytest.raises(ValueError):
        Settings(_env_file=None, ai_trader_agg_trades_limit=1001)
    with pytest.raises(ValueError):
        Settings(_env_file=None, ai_trader_ws_sample_seconds=0)
    with pytest.raises(ValueError):
        Settings(_env_file=None, ai_trader_ws_sample_seconds=61)


def test_futures_ai_trader_defaults_are_disabled_and_conservative():
    settings = Settings(_env_file=None)

    assert settings.live_futures_ai_trader_enabled is False
    assert settings.live_futures_confirm_production is False
    assert settings.futures_ai_symbols == DEFAULT_TRADING_SYMBOLS
    assert settings.futures_margin_amount == Decimal("5")
    assert settings.futures_default_leverage == 2
    assert settings.futures_max_leverage == 3
    assert settings.futures_max_total_margin == Decimal("20")
    assert settings.futures_ai_min_confidence == Decimal("0.70")
    assert settings.futures_ai_max_actions_per_run == 5
    assert settings.futures_daily_realized_loss_limit == Decimal("10")
    assert settings.futures_max_margin_loss_percent == Decimal("50")
    assert settings.futures_min_liquidation_buffer_percent == Decimal("2")
    assert settings.futures_min_stop_distance_percent == Decimal("0.3")
    assert settings.futures_max_stop_distance_percent == Decimal("5")
    assert settings.futures_min_take_profit_distance_percent == Decimal("0.3")
    assert settings.futures_max_take_profit_distance_percent == Decimal("10")
    assert settings.futures_rest_base_url == "https://testnet.binancefuture.com"
    assert settings.futures_stream_base_url == "wss://stream.binancefuture.com/ws"


def test_futures_ai_trader_settings_parse_environment(monkeypatch):
    monkeypatch.setenv("BINANCE_LIVE_FUTURES_AI_TRADER_ENABLED", "true")
    monkeypatch.setenv("BINANCE_LIVE_FUTURES_CONFIRM_PRODUCTION", "true")
    monkeypatch.setenv("BINANCE_FUTURES_AI_SYMBOLS", " ethusdt , btcusdt ")
    monkeypatch.setenv("BINANCE_FUTURES_MARGIN_AMOUNT", "7.5")
    monkeypatch.setenv("BINANCE_FUTURES_DEFAULT_LEVERAGE", "4")
    monkeypatch.setenv("BINANCE_FUTURES_MAX_LEVERAGE", "5")
    monkeypatch.setenv("BINANCE_FUTURES_MAX_TOTAL_MARGIN", "30")
    monkeypatch.setenv("BINANCE_FUTURES_AI_MIN_CONFIDENCE", "0.82")
    monkeypatch.setenv("BINANCE_FUTURES_AI_MAX_ACTIONS_PER_RUN", "3")
    monkeypatch.setenv("BINANCE_FUTURES_DAILY_REALIZED_LOSS_LIMIT", "12.5")
    monkeypatch.setenv("BINANCE_FUTURES_MAX_MARGIN_LOSS_PERCENT", "40")
    monkeypatch.setenv("BINANCE_FUTURES_MIN_LIQUIDATION_BUFFER_PERCENT", "3")
    monkeypatch.setenv("BINANCE_FUTURES_MIN_STOP_DISTANCE_PERCENT", "0.4")
    monkeypatch.setenv("BINANCE_FUTURES_MAX_STOP_DISTANCE_PERCENT", "4")
    monkeypatch.setenv("BINANCE_FUTURES_MIN_TAKE_PROFIT_DISTANCE_PERCENT", "0.5")
    monkeypatch.setenv("BINANCE_FUTURES_MAX_TAKE_PROFIT_DISTANCE_PERCENT", "8")

    settings = Settings(_env_file=None)

    assert settings.live_futures_ai_trader_enabled is True
    assert settings.live_futures_confirm_production is True
    assert settings.futures_ai_symbols == ("ETHUSDT", "BTCUSDT")
    assert settings.futures_margin_amount == Decimal("7.5")
    assert settings.futures_default_leverage == 4
    assert settings.futures_max_leverage == 5
    assert settings.futures_max_total_margin == Decimal("30")
    assert settings.futures_ai_min_confidence == Decimal("0.82")
    assert settings.futures_ai_max_actions_per_run == 3
    assert settings.futures_daily_realized_loss_limit == Decimal("12.5")
    assert settings.futures_max_margin_loss_percent == Decimal("40")
    assert settings.futures_min_liquidation_buffer_percent == Decimal("3")
    assert settings.futures_min_stop_distance_percent == Decimal("0.4")
    assert settings.futures_max_stop_distance_percent == Decimal("4")
    assert settings.futures_min_take_profit_distance_percent == Decimal("0.5")
    assert settings.futures_max_take_profit_distance_percent == Decimal("8")


def test_futures_ai_trader_rejects_invalid_settings():
    with pytest.raises(ValueError, match="futures_ai_symbols must contain at least one symbol"):
        Settings(_env_file=None, futures_ai_symbols="")
    with pytest.raises(ValueError, match="futures_ai_symbols must be USDT futures symbols"):
        Settings(_env_file=None, futures_ai_symbols="BTCUSDT,ETHBTC")
    with pytest.raises(ValueError, match="futures_margin_amount must be positive"):
        Settings(_env_file=None, futures_margin_amount=Decimal("0"))
    with pytest.raises(ValueError, match="futures_default_leverage must be positive"):
        Settings(_env_file=None, futures_default_leverage=0)
    with pytest.raises(ValueError, match="futures_max_leverage must be positive"):
        Settings(_env_file=None, futures_max_leverage=0)
    with pytest.raises(ValueError, match="futures_default_leverage must be less than or equal to futures_max_leverage"):
        Settings(_env_file=None, futures_default_leverage=4, futures_max_leverage=3)
    with pytest.raises(
        ValueError, match="futures_ai_min_confidence must be greater than 0 and less than or equal to 1"
    ):
        Settings(_env_file=None, futures_ai_min_confidence=Decimal("0"))
    with pytest.raises(ValueError, match="futures_ai_max_actions_per_run must be positive"):
        Settings(_env_file=None, futures_ai_max_actions_per_run=0)
    with pytest.raises(
        ValueError,
        match="futures_min_stop_distance_percent must be less than or equal to futures_max_stop_distance_percent",
    ):
        Settings(
            _env_file=None,
            futures_min_stop_distance_percent=Decimal("6"),
            futures_max_stop_distance_percent=Decimal("5"),
        )
    with pytest.raises(
        ValueError,
        match=(
            "futures_min_take_profit_distance_percent must be less than or equal "
            "to futures_max_take_profit_distance_percent"
        ),
    ):
        Settings(
            _env_file=None,
            futures_min_take_profit_distance_percent=Decimal("11"),
            futures_max_take_profit_distance_percent=Decimal("10"),
        )


def test_email_notification_defaults_are_disabled():
    settings = Settings(_env_file=None)

    assert settings.email_notifications_enabled is False
    assert settings.email_smtp_server is None
    assert settings.email_smtp_port == 25
    assert settings.email_login is None
    assert settings.email_sender_password.get_secret_value() == ""
    assert settings.email_recipient is None


def test_email_notification_settings_parse_environment(monkeypatch):
    monkeypatch.setenv("BINANCE_EMAIL_NOTIFICATIONS_ENABLED", "true")
    monkeypatch.setenv("BINANCE_EMAIL_SMTP_SERVER", "smtp.163.com")
    monkeypatch.setenv("BINANCE_EMAIL_SMTP_PORT", "25")
    monkeypatch.setenv("BINANCE_EMAIL_LOGIN", "19176654177@163.com")
    monkeypatch.setenv("BINANCE_EMAIL_SENDER_PASSWORD", "secret-token")
    monkeypatch.setenv("BINANCE_EMAIL_RECIPIENT", "xia_qingbo@163.com")

    settings = Settings(_env_file=None)

    assert settings.email_notifications_enabled is True
    assert settings.email_smtp_server == "smtp.163.com"
    assert settings.email_smtp_port == 25
    assert settings.email_login == "19176654177@163.com"
    assert settings.email_sender_password.get_secret_value() == "secret-token"
    assert settings.email_recipient == "xia_qingbo@163.com"
    assert "secret-token" not in repr(settings)


def test_email_notification_enabled_requires_complete_smtp_settings():
    with pytest.raises(ValueError, match="email notifications require"):
        Settings(_env_file=None, email_notifications_enabled=True)


def test_email_notification_rejects_invalid_port():
    with pytest.raises(ValueError):
        Settings(_env_file=None, email_smtp_port=0)
