from decimal import Decimal

import pytest

from binance_quant.backtest import BacktestResult, run_backtest, run_futures_backtest
from binance_quant.models import Candle


def candle(close: str, index: int) -> Candle:
    value = Decimal(close)
    return Candle("BTCUSDT", index, index + 1, value, value, value, value, Decimal("1"))


def test_run_backtest_counts_signals():
    candles = [candle(value, index) for index, value in enumerate(["5", "4", "3", "8", "9", "2"], start=1)]

    result = run_backtest(candles, fast_window=2, slow_window=3)

    assert isinstance(result, BacktestResult)
    assert result == BacktestResult(
        total_candles=6,
        buy_signals=1,
        sell_signals=1,
        hold_signals=4,
    )
    assert result.total_candles == result.buy_signals + result.sell_signals + result.hold_signals


def test_run_backtest_handles_empty_iterable():
    result = run_backtest([], fast_window=2, slow_window=3)

    assert result == BacktestResult(
        total_candles=0,
        buy_signals=0,
        sell_signals=0,
        hold_signals=0,
    )


def test_run_backtest_accepts_generator_input():
    candles = (candle(value, index) for index, value in enumerate(["5", "4", "3", "8"], start=1))

    result = run_backtest(candles, fast_window=2, slow_window=3)

    assert result.total_candles == 4
    assert result.total_candles == result.buy_signals + result.sell_signals + result.hold_signals


def test_run_backtest_propagates_invalid_window_errors():
    with pytest.raises(ValueError, match="fast_window must be less than slow_window"):
        run_backtest([], fast_window=3, slow_window=3)


def test_run_futures_backtest_tracks_profitable_long_trade():
    candles = [
        candle(value, index)
        for index, value in enumerate(["5", "4", "3", "8", "9", "10"], start=1)
    ]

    result = run_futures_backtest(
        candles,
        fast_window=2,
        slow_window=3,
        initial_equity=Decimal("100"),
        margin_amount=Decimal("10"),
        leverage=2,
        fee_rate=Decimal("0"),
        slippage_rate=Decimal("0"),
    )

    assert result.total_candles == 6
    assert result.trade_count == 1
    assert result.win_count == 1
    assert result.loss_count == 0
    assert result.total_fees == Decimal("0")
    assert result.net_pnl == Decimal("5.0")
    assert result.final_equity == Decimal("105.0")
    assert result.total_return_percent == Decimal("5.00")
    assert result.max_drawdown_percent == Decimal("0")
    assert result.trades[0].side == "LONG"
    assert result.trades[0].entry_price == Decimal("8")
    assert result.trades[0].exit_price == Decimal("10")


def test_run_futures_backtest_reverses_from_long_to_short():
    candles = [
        candle(value, index)
        for index, value in enumerate(["5", "4", "3", "8", "9", "2", "1"], start=1)
    ]

    result = run_futures_backtest(
        candles,
        fast_window=2,
        slow_window=3,
        initial_equity=Decimal("100"),
        margin_amount=Decimal("10"),
        leverage=1,
        fee_rate=Decimal("0"),
        slippage_rate=Decimal("0"),
    )

    assert result.trade_count == 2
    assert [trade.side for trade in result.trades] == ["LONG", "SHORT"]
    assert result.trades[0].entry_price == Decimal("8")
    assert result.trades[0].exit_price == Decimal("2")
    assert result.trades[0].net_pnl == Decimal("-7.5")
    assert result.trades[1].entry_price == Decimal("2")
    assert result.trades[1].exit_price == Decimal("1")
    assert result.trades[1].net_pnl == Decimal("5")
    assert result.net_pnl == Decimal("-2.5")
    assert result.final_equity == Decimal("97.5")
    assert result.max_drawdown_percent == Decimal("7.5")


def test_run_futures_backtest_deducts_fees_and_slippage():
    candles = [
        candle(value, index)
        for index, value in enumerate(["5", "4", "3", "8", "9", "10"], start=1)
    ]

    result = run_futures_backtest(
        candles,
        fast_window=2,
        slow_window=3,
        initial_equity=Decimal("100"),
        margin_amount=Decimal("10"),
        leverage=2,
        fee_rate=Decimal("0.001"),
        slippage_rate=Decimal("0.002"),
    )

    assert result.trade_count == 1
    assert result.total_fees == Decimal("0.0450")
    assert result.total_slippage == Decimal("0.0900")
    assert result.gross_pnl == Decimal("5.0")
    assert result.net_pnl == Decimal("4.8650")
    assert result.final_equity == Decimal("104.8650")


def test_run_futures_backtest_handles_empty_iterable():
    result = run_futures_backtest(
        [],
        fast_window=2,
        slow_window=3,
        initial_equity=Decimal("100"),
        margin_amount=Decimal("10"),
    )

    assert result.total_candles == 0
    assert result.trade_count == 0
    assert result.final_equity == Decimal("100")
    assert result.net_pnl == Decimal("0")
