from decimal import Decimal

import pytest

from binance_quant.execution import (
    ExecutionEngine,
    build_client_order_id,
    order_intent_from_decision,
)
from binance_quant.models import (
    OrderIntent,
    OrderSide,
    RiskDecision,
    Signal,
    SignalType,
    TradingMode,
)
from binance_quant.storage import Storage


class FakeExchange:
    def __init__(self):
        self.orders = []

    def create_market_order(self, symbol, side, quote_amount, client_order_id):
        self.orders.append((symbol, side, quote_amount, client_order_id))
        return {"symbol": symbol, "status": "FILLED", "clientOrderId": client_order_id}


def test_dry_run_execution_does_not_call_exchange():
    exchange = FakeExchange()
    engine = ExecutionEngine(mode=TradingMode.DRY_RUN, exchange_client=exchange)
    intent = OrderIntent("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")

    result = engine.execute(intent)

    assert result.dry_run is True
    assert result.status == "DRY_RUN"
    assert exchange.orders == []


def test_dry_run_execution_returns_raw_payload_contents():
    exchange = FakeExchange()
    engine = ExecutionEngine(mode=TradingMode.DRY_RUN, exchange_client=exchange)
    intent = OrderIntent("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")

    result = engine.execute(intent)

    assert result.raw == {
        "symbol": "BTCUSDT",
        "side": "BUY",
        "quoteOrderQty": "25",
        "clientOrderId": "bq-1",
    }


def test_dry_run_result_persists_through_storage(tmp_path):
    storage = Storage(tmp_path / "audit.sqlite3")
    storage.initialize()
    engine = ExecutionEngine(mode=TradingMode.DRY_RUN, exchange_client=FakeExchange())
    intent = OrderIntent("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")

    result = engine.execute(intent)
    storage.record_order_result(result)

    assert storage.list_orders()[0]["client_order_id"] == "bq-1"


def test_testnet_live_execution_calls_exchange():
    exchange = FakeExchange()
    engine = ExecutionEngine(mode=TradingMode.TESTNET_LIVE, exchange_client=exchange)
    intent = OrderIntent("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")

    result = engine.execute(intent)

    assert result.dry_run is False
    assert result.status == "FILLED"
    assert exchange.orders == [("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")]


def test_live_execution_calls_exchange():
    exchange = FakeExchange()
    engine = ExecutionEngine(mode=TradingMode.LIVE, exchange_client=exchange)
    intent = OrderIntent("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")

    result = engine.execute(intent)

    assert result.status == "FILLED"
    assert not result.dry_run
    assert exchange.orders == [("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")]


def test_unsupported_mode_raises_value_error():
    engine = ExecutionEngine(mode="paper", exchange_client=FakeExchange())
    intent = OrderIntent("BTCUSDT", OrderSide.BUY, Decimal("25"), "bq-1")

    with pytest.raises(ValueError, match="unsupported trading mode"):
        engine.execute(intent)


def test_build_client_order_id_default_shape():
    client_order_id = build_client_order_id()
    suffix = client_order_id.removeprefix("bq-")

    assert client_order_id.startswith("bq-")
    assert len(client_order_id) == 27
    int(suffix, 16)


@pytest.mark.parametrize(
    "decision",
    [
        RiskDecision(
            approved=False,
            reason="rejected",
            signal=Signal("BTCUSDT", SignalType.BUY, OrderSide.BUY, "entry"),
            quote_amount=Decimal("25"),
        ),
        RiskDecision(
            approved=True,
            reason="approved",
            signal=Signal("BTCUSDT", SignalType.HOLD, None, "hold"),
            quote_amount=Decimal("25"),
        ),
        RiskDecision(
            approved=True,
            reason="approved",
            signal=Signal("BTCUSDT", SignalType.BUY, OrderSide.BUY, "entry"),
            quote_amount=None,
        ),
    ],
)
def test_order_intent_from_decision_rejects_invalid_decisions(decision):
    with pytest.raises(
        ValueError,
        match="approved risk decision with side and quote_amount is required",
    ):
        order_intent_from_decision(decision)


def test_order_intent_from_decision_accepts_approved_decision():
    decision = RiskDecision(
        approved=True,
        reason="approved",
        signal=Signal("BTCUSDT", SignalType.BUY, OrderSide.BUY, "entry"),
        quote_amount=Decimal("25"),
    )

    intent = order_intent_from_decision(decision)

    assert intent.symbol == "BTCUSDT"
    assert intent.side is OrderSide.BUY
    assert intent.quote_amount == Decimal("25")
    assert intent.client_order_id.startswith("bq-")
