"""Tests for the broker API adapter interface and Alpaca implementation.

Validates data structures, request building, response parsing, and fail-closed behavior.
"""
from services.adapters.broker_adapter import (
    AccountInfo,
    AlpacaBrokerAdapter,
    BrokerDataAdapter,
    OrderEventType,
    OrderRequest,
    OrderResponse,
    OrderSide,
    OrderStatus,
    OrderType,
    PositionInfo,
    TradingMode,
)

# --- Fake Alpaca responses ---

ALPACA_ORDER_RESPONSE = {
    "id": "order-abc-123",
    "client_order_id": "client-001",
    "status": "accepted",
    "symbol": "AAPL",
    "side": "buy",
    "qty": "10",
    "filled_qty": "0",
    "filled_avg_price": None,
    "type": "market",
    "time_in_force": "day",
    "created_at": "2026-04-11T14:00:00Z",
}

ALPACA_FILLED_ORDER = {
    "id": "order-def-456",
    "status": "filled",
    "symbol": "AAPL",
    "side": "buy",
    "qty": "10",
    "filled_qty": "10",
    "filled_avg_price": "172.50",
    "type": "market",
    "time_in_force": "day",
}

ALPACA_REJECTED_ORDER = {
    "id": "order-ghi-789",
    "status": "rejected",
    "symbol": "AAPL",
    "side": "sell",
    "qty": "100",
    "filled_qty": "0",
    "filled_avg_price": None,
}

ALPACA_POSITION = {
    "symbol": "AAPL",
    "qty": "10",
    "avg_entry_price": "172.50",
    "current_price": "175.00",
    "unrealized_pl": "25.00",
    "market_value": "1750.00",
    "side": "long",
}

ALPACA_ACCOUNT = {
    "id": "acct-001",
    "buying_power": "50000.00",
    "cash": "25000.00",
    "portfolio_value": "75000.00",
    "currency": "USD",
}


# --- Enum tests ---


class TestBrokerEnums:
    def test_order_side_values(self):
        assert OrderSide.BUY.value == "buy"
        assert OrderSide.SELL.value == "sell"

    def test_order_type_values(self):
        assert OrderType.MARKET.value == "market"
        assert OrderType.LIMIT.value == "limit"
        assert OrderType.STOP.value == "stop"
        assert OrderType.STOP_LIMIT.value == "stop_limit"

    def test_order_status_values(self):
        assert OrderStatus.PENDING.value == "pending"
        assert OrderStatus.FILLED.value == "filled"
        assert OrderStatus.REJECTED.value == "rejected"

    def test_trading_mode_values(self):
        assert TradingMode.PAPER.value == "paper"
        assert TradingMode.LIVE.value == "live"

    def test_order_event_type_values(self):
        assert OrderEventType.SUBMITTED.value == "submitted"
        assert OrderEventType.FILL.value == "fill"
        assert OrderEventType.CANCELLED.value == "cancelled"


# --- OrderRequest tests ---


class TestOrderRequest:
    def test_basic_market_order(self):
        req = OrderRequest(
            ticker="AAPL",
            side=OrderSide.BUY,
            quantity=10,
        )
        assert req.ticker == "AAPL"
        assert req.side == OrderSide.BUY
        assert req.quantity == 10
        assert req.order_type == OrderType.MARKET
        assert req.time_in_force == "day"
        assert req.idempotency_key  # auto-generated

    def test_limit_order(self):
        req = OrderRequest(
            ticker="MSFT",
            side=OrderSide.SELL,
            quantity=5,
            order_type=OrderType.LIMIT,
            limit_price=400.0,
        )
        assert req.order_type == OrderType.LIMIT
        assert req.limit_price == 400.0

    def test_custom_idempotency_key(self):
        req = OrderRequest(
            ticker="AAPL",
            side=OrderSide.BUY,
            quantity=1,
            idempotency_key="my-key-123",
        )
        assert req.idempotency_key == "my-key-123"

    def test_to_dict(self):
        req = OrderRequest(
            ticker="AAPL",
            side=OrderSide.BUY,
            quantity=10,
            order_type=OrderType.LIMIT,
            limit_price=170.0,
            idempotency_key="key-1",
        )
        d = req.to_dict()
        assert d["ticker"] == "AAPL"
        assert d["side"] == "buy"
        assert d["quantity"] == 10
        assert d["order_type"] == "limit"
        assert d["limit_price"] == 170.0
        assert d["idempotency_key"] == "key-1"

    def test_to_dict_omits_none_prices(self):
        req = OrderRequest(ticker="AAPL", side=OrderSide.BUY, quantity=1)
        d = req.to_dict()
        assert "limit_price" not in d
        assert "stop_price" not in d


# --- OrderResponse tests ---


class TestOrderResponse:
    def test_ok_when_accepted(self):
        resp = OrderResponse(
            broker_order_id="abc",
            status=OrderStatus.ACCEPTED,
            ticker="AAPL",
            side=OrderSide.BUY,
            quantity=10,
        )
        assert resp.ok is True

    def test_not_ok_when_rejected(self):
        resp = OrderResponse(
            broker_order_id="abc",
            status=OrderStatus.REJECTED,
            ticker="AAPL",
            side=OrderSide.BUY,
            quantity=10,
            error="insufficient funds",
        )
        assert resp.ok is False

    def test_not_ok_when_error(self):
        resp = OrderResponse(
            broker_order_id="abc",
            status=OrderStatus.SUBMITTED,
            ticker="AAPL",
            side=OrderSide.BUY,
            quantity=10,
            error="network failure",
        )
        assert resp.ok is False

    def test_to_dict(self):
        resp = OrderResponse(
            broker_order_id="order-1",
            status=OrderStatus.FILLED,
            ticker="AAPL",
            side=OrderSide.BUY,
            quantity=10,
            filled_quantity=10,
            filled_avg_price=172.5,
        )
        d = resp.to_dict()
        assert d["broker_order_id"] == "order-1"
        assert d["status"] == "filled"
        assert d["filled_avg_price"] == 172.5


# --- PositionInfo tests ---


class TestPositionInfo:
    def test_basic_position(self):
        pos = PositionInfo(
            ticker="AAPL",
            quantity=10,
            avg_entry_price=172.5,
            current_price=175.0,
            unrealized_pnl=25.0,
            market_value=1750.0,
        )
        assert pos.ticker == "AAPL"
        assert pos.side == "long"

    def test_to_dict(self):
        pos = PositionInfo(
            ticker="AAPL",
            quantity=10,
            avg_entry_price=172.5,
            current_price=175.0,
            unrealized_pnl=25.0,
            market_value=1750.0,
            side="short",
        )
        d = pos.to_dict()
        assert d["side"] == "short"
        assert d["unrealized_pnl"] == 25.0


# --- AccountInfo tests ---


class TestAccountInfo:
    def test_basic_account(self):
        acct = AccountInfo(
            account_id="acct-1",
            buying_power=50000,
            cash=25000,
            portfolio_value=75000,
        )
        assert acct.mode == TradingMode.PAPER
        assert acct.currency == "USD"

    def test_to_dict(self):
        acct = AccountInfo(
            account_id="acct-1",
            buying_power=50000,
            cash=25000,
            portfolio_value=75000,
            mode=TradingMode.LIVE,
        )
        d = acct.to_dict()
        assert d["mode"] == "live"
        assert d["portfolio_value"] == 75000


# --- AlpacaBrokerAdapter tests ---


class TestAlpacaSourceType:
    def test_source_type(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s")
        assert adapter.source_type() == "broker"

    def test_inherits_broker_data_adapter(self):
        assert issubclass(AlpacaBrokerAdapter, BrokerDataAdapter)

    def test_bucket_name(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s")
        assert adapter.bucket_name() == "stonks-raw-broker"

    def test_default_mode_is_paper(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s")
        assert adapter.mode == TradingMode.PAPER

    def test_paper_base_url(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s", mode=TradingMode.PAPER)
        assert "paper" in adapter.base_url

    def test_live_base_url(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s", mode=TradingMode.LIVE)
        assert "paper" not in adapter.base_url

    def test_custom_base_url(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s", base_url="http://localhost:8080/")
        assert adapter.base_url == "http://localhost:8080"


class TestAlpacaHeaders:
    def test_headers_contain_api_keys(self):
        adapter = AlpacaBrokerAdapter(api_key="my-key", api_secret="my-secret")
        headers = adapter._headers()
        assert headers["APCA-API-KEY-ID"] == "my-key"
        assert headers["APCA-API-SECRET-KEY"] == "my-secret"
        assert headers["Content-Type"] == "application/json"


class TestAlpacaBuildFetchUrl:
    def setup_method(self):
        self.adapter = AlpacaBrokerAdapter(
            api_key="k", api_secret="s", base_url="https://paper-api.alpaca.markets"
        )

    def test_positions_url(self):
        url = self.adapter._build_fetch_url("AAPL", "positions")
        assert url == "https://paper-api.alpaca.markets/v2/positions/AAPL"

    def test_orders_url(self):
        url = self.adapter._build_fetch_url("AAPL", "orders")
        assert "v2/orders" in url
        assert "symbols=AAPL" in url

    def test_account_url(self):
        url = self.adapter._build_fetch_url("AAPL", "account")
        assert url == "https://paper-api.alpaca.markets/v2/account"

    def test_default_is_positions(self):
        url = self.adapter._build_fetch_url("AAPL", "unknown")
        assert "/v2/positions/AAPL" in url


class TestAlpacaParseOrderResponse:
    def setup_method(self):
        self.adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s")

    def test_parse_accepted_order(self):
        resp = self.adapter._parse_order_response(ALPACA_ORDER_RESPONSE)
        assert resp.broker_order_id == "order-abc-123"
        assert resp.status == OrderStatus.ACCEPTED
        assert resp.ticker == "AAPL"
        assert resp.side == OrderSide.BUY
        assert resp.quantity == 10
        assert resp.filled_quantity == 0
        assert resp.filled_avg_price is None

    def test_parse_filled_order(self):
        resp = self.adapter._parse_order_response(ALPACA_FILLED_ORDER)
        assert resp.status == OrderStatus.FILLED
        assert resp.filled_quantity == 10
        assert resp.filled_avg_price == 172.5

    def test_parse_rejected_order(self):
        resp = self.adapter._parse_order_response(ALPACA_REJECTED_ORDER)
        assert resp.status == OrderStatus.REJECTED
        assert resp.ok is False

    def test_parse_unknown_status_defaults_to_pending(self):
        data = {**ALPACA_ORDER_RESPONSE, "status": "some_new_status"}
        resp = self.adapter._parse_order_response(data)
        assert resp.status == OrderStatus.PENDING

    def test_parse_sell_side(self):
        data = {**ALPACA_ORDER_RESPONSE, "side": "sell"}
        resp = self.adapter._parse_order_response(data)
        assert resp.side == OrderSide.SELL


class TestAlpacaParsePosition:
    def setup_method(self):
        self.adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s")

    def test_parse_position(self):
        pos = self.adapter._parse_position(ALPACA_POSITION)
        assert pos.ticker == "AAPL"
        assert pos.quantity == 10
        assert pos.avg_entry_price == 172.5
        assert pos.current_price == 175.0
        assert pos.unrealized_pnl == 25.0
        assert pos.market_value == 1750.0
        assert pos.side == "long"

    def test_parse_position_missing_fields(self):
        pos = self.adapter._parse_position({"symbol": "TSLA"})
        assert pos.ticker == "TSLA"
        assert pos.quantity == 0
        assert pos.avg_entry_price == 0


class TestAlpacaErrorResult:
    def test_error_result_fields(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s")
        result = adapter._error_result("AAPL", "rate limited", 150.0, http_status=429, raw=b"slow down")
        assert not result.ok
        assert result.error == "rate limited"
        assert result.http_status == 429
        assert result.response_time_ms == 150.0
        assert result.raw_payload == b"slow down"
        assert result.metadata["provider"] == "alpaca"
        assert result.metadata["mode"] == "paper"
        assert result.source_type == "broker"

    def test_error_result_defaults(self):
        adapter = AlpacaBrokerAdapter(api_key="k", api_secret="s")
        result = adapter._error_result("MSFT", "timeout", 200.0)
        assert result.http_status is None
        assert result.raw_payload == b""
        assert result.ticker == "MSFT"
