from decimal import Decimal

from binance_quant.auto_buy import AutoBuyMarketSnapshot, AutoBuyScanner
from binance_quant.models import Candle


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


def snapshot(
    symbol: str,
    closes: list[str],
    *,
    bid: str = "109.9",
    ask: str = "110",
    volume: str = "12",
    volumes: list[str] | None = None,
) -> AutoBuyMarketSnapshot:
    candle_volumes = volumes or [volume] * len(closes)
    candles = [
        candle(symbol, close, volume=candle_volume, open_time=index)
        for index, (close, candle_volume) in enumerate(zip(closes, candle_volumes, strict=True), start=1)
    ]
    return AutoBuyMarketSnapshot(symbol=symbol, bid=Decimal(bid), ask=Decimal(ask), candles=candles)


def test_scanner_selects_highest_scoring_eligible_symbol():
    scanner = AutoBuyScanner(symbols=("BTCUSDT", "ETHUSDT"), min_score=Decimal("4"), max_spread_bps=Decimal("20"))

    candidate = scanner.select(
        [
            snapshot("BTCUSDT", ["100", "101", "102", "103", "104", "105"], bid="104.9", ask="105"),
            snapshot(
                "ETHUSDT",
                ["100", "99", "100", "101", "103", "110"],
                bid="109.9",
                ask="110",
                volumes=["10", "10", "10", "10", "10", "11"],
            ),
        ]
    )

    assert candidate is not None
    assert candidate.symbol == "ETHUSDT"
    assert candidate.score > Decimal("4")
    assert "positive 1h momentum" in candidate.reasons
    assert "short SMA above long SMA" in candidate.reasons


def test_scanner_returns_none_when_score_is_below_threshold():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("4"), max_spread_bps=Decimal("20"))

    candidate = scanner.select([snapshot("BTCUSDT", ["100", "100", "100", "100", "100", "100"])])

    assert candidate is None


def test_scanner_requires_current_volume_above_baseline_for_eligibility():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("5"), max_spread_bps=Decimal("20"))

    candidate = scanner.select([snapshot("BTCUSDT", ["100", "100", "100", "100", "100", "110"], volume="10")])

    assert candidate is None


def test_scanner_rejects_wide_spread():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("5"))

    candidate = scanner.select([snapshot("BTCUSDT", ["100", "101", "102", "103", "104", "105"], bid="100", ask="101")])

    assert candidate is None


def test_scanner_rejects_invalid_quotes():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))
    closes = ["100", "101", "102", "103", "104", "105"]

    assert scanner.select([snapshot("BTCUSDT", closes, bid="0", ask="105")]) is None
    assert scanner.select([snapshot("BTCUSDT", closes, bid="104.9", ask="0")]) is None
    assert scanner.select([snapshot("BTCUSDT", closes, bid="105", ask="104.9")]) is None


def test_scanner_rejects_non_finite_quotes_without_raising():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))
    closes = ["100", "101", "102", "103", "104", "105"]

    assert scanner.select([snapshot("BTCUSDT", closes, bid="NaN", ask="105")]) is None
    assert scanner.select([snapshot("BTCUSDT", closes, bid="Infinity", ask="105")]) is None
    assert scanner.select([snapshot("BTCUSDT", closes, bid="104.9", ask="NaN")]) is None
    assert scanner.select([snapshot("BTCUSDT", closes, bid="104.9", ask="Infinity")]) is None


def test_scanner_rejects_fewer_than_six_candles():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))

    candidate = scanner.select([snapshot("BTCUSDT", ["100", "101", "102", "103", "104"], bid="103.9", ask="104")])

    assert candidate is None


def test_scanner_rejects_zero_close_in_recent_six_candle_window():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))

    candidate = scanner.select(
        [
            snapshot(
                "BTCUSDT",
                ["100", "0", "102", "103", "104", "105"],
                bid="104.9",
                ask="105",
                volumes=["10", "10", "10", "10", "10", "11"],
            )
        ]
    )

    assert candidate is None


def test_scanner_rejects_negative_close_in_recent_six_candle_window():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))

    candidate = scanner.select(
        [
            snapshot(
                "BTCUSDT",
                ["100", "-1", "102", "103", "104", "105"],
                bid="104.9",
                ask="105",
                volumes=["10", "10", "10", "10", "10", "11"],
            )
        ]
    )

    assert candidate is None


def test_scanner_rejects_non_finite_close_in_recent_six_candle_window_without_raising():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))

    assert (
        scanner.select(
            [
                snapshot(
                    "BTCUSDT",
                    ["100", "NaN", "102", "103", "104", "105"],
                    bid="104.9",
                    ask="105",
                    volumes=["10", "10", "10", "10", "10", "11"],
                )
            ]
        )
        is None
    )
    assert (
        scanner.select(
            [
                snapshot(
                    "BTCUSDT",
                    ["100", "Infinity", "102", "103", "104", "105"],
                    bid="104.9",
                    ask="105",
                    volumes=["10", "10", "10", "10", "10", "11"],
                )
            ]
        )
        is None
    )


def test_scanner_rejects_zero_volume_in_recent_six_candle_window():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))

    candidate = scanner.select(
        [
            snapshot(
                "BTCUSDT",
                ["100", "101", "102", "103", "104", "105"],
                bid="104.9",
                ask="105",
                volumes=["10", "0", "10", "10", "10", "11"],
            )
        ]
    )

    assert candidate is None


def test_scanner_rejects_negative_volume_in_recent_six_candle_window():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))

    candidate = scanner.select(
        [
            snapshot(
                "BTCUSDT",
                ["100", "101", "102", "103", "104", "105"],
                bid="104.9",
                ask="105",
                volumes=["10", "-1", "10", "10", "10", "11"],
            )
        ]
    )

    assert candidate is None


def test_scanner_rejects_non_finite_volume_in_recent_six_candle_window_without_raising():
    scanner = AutoBuyScanner(symbols=("BTCUSDT",), min_score=Decimal("1"), max_spread_bps=Decimal("20"))

    assert (
        scanner.select(
            [
                snapshot(
                    "BTCUSDT",
                    ["100", "101", "102", "103", "104", "105"],
                    bid="104.9",
                    ask="105",
                    volumes=["10", "NaN", "10", "10", "10", "11"],
                )
            ]
        )
        is None
    )
    assert (
        scanner.select(
            [
                snapshot(
                    "BTCUSDT",
                    ["100", "101", "102", "103", "104", "105"],
                    bid="104.9",
                    ask="105",
                    volumes=["10", "Infinity", "10", "10", "10", "11"],
                )
            ]
        )
        is None
    )


def test_scanner_tie_breaks_by_configured_symbol_order():
    scanner = AutoBuyScanner(symbols=("ETHUSDT", "BTCUSDT"), min_score=Decimal("4"), max_spread_bps=Decimal("20"))

    candidate = scanner.select(
        [
            snapshot(
                "BTCUSDT",
                ["100", "101", "102", "103", "104", "105"],
                bid="104.9",
                ask="105",
                volumes=["10", "10", "10", "10", "10", "11"],
            ),
            snapshot(
                "ETHUSDT",
                ["100", "101", "102", "103", "104", "105"],
                bid="104.9",
                ask="105",
                volumes=["10", "10", "10", "10", "10", "11"],
            ),
        ]
    )

    assert candidate is not None
    assert candidate.symbol == "ETHUSDT"
