from decimal import Decimal

import pytest

from binance_quant.models import Candle, OrderSide, SignalType
from binance_quant.strategy import SmaCrossoverStrategy


def candle(close: str, open_time: int) -> Candle:
    value = Decimal(close)
    return Candle(
        symbol="BTCUSDT",
        open_time=open_time,
        close_time=open_time + 60_000,
        open=value,
        high=value,
        low=value,
        close=value,
        volume=Decimal("1"),
    )


def test_hold_until_enough_candles():
    strategy = SmaCrossoverStrategy(fast_window=3, slow_window=5)

    signal = strategy.on_candle(candle("100", 1))

    assert signal.signal_type is SignalType.HOLD
    assert signal.side is None


def test_invalid_windows_must_be_positive():
    with pytest.raises(ValueError, match="positive"):
        SmaCrossoverStrategy(fast_window=0, slow_window=3)


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


def test_every_candle_before_slow_window_returns_hold():
    strategy = SmaCrossoverStrategy(fast_window=2, slow_window=4)

    for index, close in enumerate(["5", "4", "3"], start=1):
        signal = strategy.on_candle(candle(close, index))

        assert signal.signal_type is SignalType.HOLD
        assert signal.side is None


def test_first_full_slow_window_returns_hold():
    strategy = SmaCrossoverStrategy(fast_window=2, slow_window=3)

    for index, close in enumerate(["5", "4"], start=1):
        strategy.on_candle(candle(close, index))
    signal = strategy.on_candle(candle("3", 3))

    assert signal.signal_type is SignalType.HOLD
    assert signal.side is None


def test_equal_fast_and_slow_sma_returns_hold():
    strategy = SmaCrossoverStrategy(fast_window=2, slow_window=3)

    for index, close in enumerate(["3", "6"], start=1):
        strategy.on_candle(candle(close, index))
    signal = strategy.on_candle(candle("3", 3))

    assert signal.signal_type is SignalType.HOLD
    assert signal.side is None


def test_signal_preserves_candle_close_time():
    strategy = SmaCrossoverStrategy(fast_window=2, slow_window=3)
    current_candle = candle("100", 10)

    signal = strategy.on_candle(current_candle)

    assert signal.created_at_ms == current_candle.close_time


def test_buy_when_fast_sma_crosses_above_slow_sma():
    strategy = SmaCrossoverStrategy(fast_window=2, slow_window=3)
    for index, close in enumerate(["5", "4", "3"], start=1):
        strategy.on_candle(candle(close, index))

    signal = strategy.on_candle(candle("8", 4))

    assert signal.signal_type is SignalType.BUY
    assert signal.side is OrderSide.BUY
    assert "crossed above" in signal.reason


def test_sell_when_fast_sma_crosses_below_slow_sma():
    strategy = SmaCrossoverStrategy(fast_window=2, slow_window=3)
    for index, close in enumerate(["3", "4", "5"], start=1):
        strategy.on_candle(candle(close, index))

    signal = strategy.on_candle(candle("1", 4))

    assert signal.signal_type is SignalType.SELL
    assert signal.side is OrderSide.SELL
    assert "crossed below" in signal.reason
