from decimal import Decimal

from binance_quant.models import TradingMode
from binance_quant.spot_trade_sync import sync_spot_trade_fills
from binance_quant.storage import Storage


class FakeExchange:
    def __init__(self):
        self.calls = []
        self.trades = {
            "BTCUSDT": [
                _trade(10, order_id=500, is_buyer=True, time_ms=1000),
                _trade(11, order_id=501, is_buyer=False, time_ms=2000),
            ],
            "ETHUSDT": [_trade(20, order_id=600, is_buyer=False, time_ms=3000)],
        }

    def my_trades(self, symbol, start_time_ms=None, limit=1000):
        self.calls.append((symbol, start_time_ms, limit))
        return [trade for trade in self.trades[symbol] if start_time_ms is None or trade["time"] >= start_time_ms]


def test_sync_spot_trade_fills_records_missing_trades_idempotently(tmp_path):
    storage = Storage(tmp_path / "audit.sqlite3")
    storage.initialize()
    settings = _settings(tmp_path)
    exchange = FakeExchange()

    first = sync_spot_trade_fills(settings, exchange_client=exchange, storage=storage, symbols=("BTCUSDT", "ETHUSDT"))
    second = sync_spot_trade_fills(settings, exchange_client=exchange, storage=storage, symbols=("BTCUSDT", "ETHUSDT"))

    assert first.scanned_symbols == 2
    assert first.fetched_trades == 3
    assert first.inserted_trades == 3
    assert second.scanned_symbols == 2
    assert second.fetched_trades == 2
    assert second.inserted_trades == 0
    assert [row["trade_id"] for row in storage.list_spot_trade_fills("BTCUSDT")] == [11, 10]
    assert [row["trade_id"] for row in storage.list_spot_trade_fills("ETHUSDT")] == [20]
    assert exchange.calls == [
        ("BTCUSDT", None, 1000),
        ("ETHUSDT", None, 1000),
        ("BTCUSDT", 2000, 1000),
        ("ETHUSDT", 3000, 1000),
    ]


def test_sync_spot_trade_fills_refuses_dry_run_mode(tmp_path):
    settings = _settings(tmp_path)
    settings.mode = TradingMode.DRY_RUN

    result = sync_spot_trade_fills(settings, exchange_client=FakeExchange(), symbols=("BTCUSDT",))

    assert result.status == "rejected"
    assert result.reason == "live or testnet_live mode required"
    assert result.scanned_symbols == 0


def _settings(tmp_path):
    class Settings:
        api_key = "key"
        api_secret = type("Secret", (), {"get_secret_value": lambda self: "secret"})()
        database_path = tmp_path / "audit.sqlite3"
        mode = TradingMode.LIVE
        rest_base_url = "https://api.binance.com/api"
        rest_base_urls = ("https://api.binance.com/api",)
        recv_window = 5000
        proxy_url = None
        auto_buy_symbols = ("BTCUSDT",)
        auto_sell_symbols = ("ETHUSDT",)
        ai_trader_symbols = ("BTCUSDT", "ETHUSDT")

    return Settings()


def _trade(trade_id: int, *, order_id: int, is_buyer: bool, time_ms: int) -> dict:
    return {
        "id": trade_id,
        "orderId": order_id,
        "orderListId": -1,
        "price": "100",
        "qty": "0.01",
        "quoteQty": "1",
        "commission": "0.00001",
        "commissionAsset": "BTC",
        "time": time_ms,
        "isBuyer": is_buyer,
        "isMaker": False,
        "isBestMatch": True,
    }
