from decimal import Decimal

from binance_quant.spot_cost_basis import evaluate_sell_cost_guard
from binance_quant.storage import Storage


def test_sell_cost_guard_blocks_below_cost_sell_before_minimum_hold(tmp_path):
    storage = Storage(tmp_path / "audit.sqlite3")
    storage.initialize()
    storage.record_spot_trade_fill("BTCUSDT", _spot_buy(1, price="100", qty="1", quote_qty="100", time_ms=1_000))

    decision = evaluate_sell_cost_guard(
        storage,
        "BTCUSDT",
        quantity=Decimal("1"),
        bid_price=Decimal("95"),
        now_ms=11_000,
        require_profit=True,
        min_hold_seconds=60,
    )

    assert decision.approved is False
    assert decision.reason == "sell below cost before minimum hold"
    assert decision.matched_cost == Decimal("100")
    assert decision.estimated_proceeds == Decimal("95")


def test_sell_cost_guard_allows_below_cost_sell_after_minimum_hold(tmp_path):
    storage = Storage(tmp_path / "audit.sqlite3")
    storage.initialize()
    storage.record_spot_trade_fill("BTCUSDT", _spot_buy(1, price="100", qty="1", quote_qty="100", time_ms=1_000))

    decision = evaluate_sell_cost_guard(
        storage,
        "BTCUSDT",
        quantity=Decimal("1"),
        bid_price=Decimal("95"),
        now_ms=62_000,
        require_profit=True,
        min_hold_seconds=60,
    )

    assert decision.approved is True
    assert decision.reason == "minimum hold elapsed"


def test_sell_cost_guard_allows_profitable_sell_before_minimum_hold(tmp_path):
    storage = Storage(tmp_path / "audit.sqlite3")
    storage.initialize()
    storage.record_spot_trade_fill("BTCUSDT", _spot_buy(1, price="100", qty="1", quote_qty="100", time_ms=1_000))

    decision = evaluate_sell_cost_guard(
        storage,
        "BTCUSDT",
        quantity=Decimal("1"),
        bid_price=Decimal("101"),
        now_ms=11_000,
        require_profit=True,
        min_hold_seconds=60,
    )

    assert decision.approved is True
    assert decision.reason == "estimated proceeds cover cost"


def test_sell_cost_guard_allows_sell_without_local_cost_basis(tmp_path):
    storage = Storage(tmp_path / "audit.sqlite3")
    storage.initialize()

    decision = evaluate_sell_cost_guard(
        storage,
        "BTCUSDT",
        quantity=Decimal("1"),
        bid_price=Decimal("95"),
        now_ms=11_000,
        require_profit=True,
        min_hold_seconds=60,
    )

    assert decision.approved is True
    assert decision.reason == "no local cost basis"


def test_sell_cost_guard_consumes_prior_sells_fifo(tmp_path):
    storage = Storage(tmp_path / "audit.sqlite3")
    storage.initialize()
    storage.record_spot_trade_fill("BTCUSDT", _spot_buy(1, price="100", qty="1", quote_qty="100", time_ms=1_000))
    storage.record_spot_trade_fill("BTCUSDT", _spot_sell(2, price="105", qty="1", quote_qty="105", time_ms=2_000))
    storage.record_spot_trade_fill("BTCUSDT", _spot_buy(3, price="90", qty="1", quote_qty="90", time_ms=3_000))

    decision = evaluate_sell_cost_guard(
        storage,
        "BTCUSDT",
        quantity=Decimal("1"),
        bid_price=Decimal("95"),
        now_ms=13_000,
        require_profit=True,
        min_hold_seconds=60,
    )

    assert decision.approved is True
    assert decision.reason == "estimated proceeds cover cost"
    assert decision.matched_cost == Decimal("90")


def _spot_buy(trade_id: int, *, price: str, qty: str, quote_qty: str, time_ms: int) -> dict:
    return {
        "id": trade_id,
        "orderId": trade_id + 100,
        "orderListId": -1,
        "price": price,
        "qty": qty,
        "quoteQty": quote_qty,
        "commission": "0",
        "commissionAsset": "BNB",
        "time": time_ms,
        "isBuyer": True,
        "isMaker": False,
        "isBestMatch": True,
    }


def _spot_sell(trade_id: int, *, price: str, qty: str, quote_qty: str, time_ms: int) -> dict:
    return {
        "id": trade_id,
        "orderId": trade_id + 100,
        "orderListId": -1,
        "price": price,
        "qty": qty,
        "quoteQty": quote_qty,
        "commission": "0",
        "commissionAsset": "BNB",
        "time": time_ms,
        "isBuyer": False,
        "isMaker": False,
        "isBestMatch": True,
    }
