"""Tests for the broker service - sandbox integration wiring.

Validates job parsing, risk evaluation integration, order building,
and the overall process_order_job flow using a mock Alpaca adapter.
"""

from services.adapters.broker_adapter import (
    AlpacaBrokerAdapter,
    OrderSide,
    OrderType,
    TradingMode,
)
from services.adapters.broker_service import (
    build_order_request,
    build_proposed_order,
    generate_idempotency_key,
)
from services.risk.engine import (
    AccountRiskState,
    PortfolioRiskConfig,
    ProposedOrder,
    evaluate_order,
)
from services.risk.engine import (
    TradingMode as RiskTradingMode,
)
from services.shared.redis_keys import QUEUE_BROKER

# ---------------------------------------------------------------------------
# build_order_request tests
# ---------------------------------------------------------------------------


class TestBuildOrderRequest:
    def test_basic_buy_market(self):
        job = {
            "ticker": "AAPL",
            "side": "buy",
            "quantity": 10,
            "order_type": "market",
            "idempotency_key": "key-1",
        }
        req = build_order_request(job)
        assert req.ticker == "AAPL"
        assert req.side == OrderSide.BUY
        assert req.quantity == 10
        assert req.order_type == OrderType.MARKET
        assert req.idempotency_key == "key-1"

    def test_sell_limit_order(self):
        job = {
            "ticker": "MSFT",
            "side": "sell",
            "quantity": 5,
            "order_type": "limit",
            "limit_price": 400.0,
        }
        req = build_order_request(job)
        assert req.side == OrderSide.SELL
        assert req.order_type == OrderType.LIMIT
        assert req.limit_price == 400.0

    def test_stop_order(self):
        job = {
            "ticker": "TSLA",
            "side": "sell",
            "quantity": 3,
            "order_type": "stop",
            "stop_price": 200.0,
        }
        req = build_order_request(job)
        assert req.order_type == OrderType.STOP
        assert req.stop_price == 200.0

    def test_defaults(self):
        job = {"ticker": "GOOG"}
        req = build_order_request(job)
        assert req.side == OrderSide.BUY
        assert req.quantity == 0
        assert req.order_type == OrderType.MARKET
        assert req.time_in_force == "day"
        assert req.idempotency_key  # deterministic from job content

    def test_deterministic_key_without_explicit(self):
        """Without an explicit key, the same job produces the same key."""
        job = {"ticker": "AAPL", "side": "buy", "quantity": 10}
        req1 = build_order_request(job)
        req2 = build_order_request(job)
        assert req1.idempotency_key == req2.idempotency_key

    def test_custom_time_in_force(self):
        job = {"ticker": "AAPL", "time_in_force": "gtc"}
        req = build_order_request(job)
        assert req.time_in_force == "gtc"


# ---------------------------------------------------------------------------
# build_proposed_order tests
# ---------------------------------------------------------------------------


class TestBuildProposedOrder:
    def test_basic_proposed_order(self):
        job = {
            "ticker": "AAPL",
            "side": "buy",
            "quantity": 10,
            "estimated_value": 1500.0,
            "confidence": 0.85,
            "sector": "technology",
            "recommendation_id": "rec-123",
        }
        proposed = build_proposed_order(job)
        assert proposed.ticker == "AAPL"
        assert proposed.action == "buy"
        assert proposed.quantity == 10
        assert proposed.estimated_value == 1500.0
        assert proposed.confidence == 0.85
        assert proposed.sector == "technology"
        assert proposed.recommendation_id == "rec-123"

    def test_defaults(self):
        job = {"ticker": "GOOG"}
        proposed = build_proposed_order(job)
        assert proposed.action == "buy"
        assert proposed.quantity == 0
        assert proposed.estimated_value == 0
        assert proposed.sector == ""
        assert proposed.recommendation_id is None


# ---------------------------------------------------------------------------
# Risk evaluation integration with broker service flow
# ---------------------------------------------------------------------------


class TestRiskEvaluationIntegration:
    """Verify that risk evaluation correctly gates order submission."""

    def test_order_passes_risk_in_paper_mode(self):
        config = PortfolioRiskConfig(trading_mode=RiskTradingMode.PAPER)
        state = AccountRiskState(
            portfolio_value=100_000.0,
            cash=50_000.0,
            buying_power=50_000.0,
        )
        proposed = ProposedOrder(
            ticker="AAPL",
            action="buy",
            quantity=10,
            estimated_value=1500.0,
            sector="technology",
        )
        result = evaluate_order(proposed, config, state)
        assert result.eligible
        assert result.allowed_mode == RiskTradingMode.PAPER

    def test_order_blocked_when_trading_disabled(self):
        config = PortfolioRiskConfig(trading_mode=RiskTradingMode.DISABLED)
        proposed = ProposedOrder(ticker="AAPL", quantity=10, estimated_value=1500.0)
        result = evaluate_order(proposed, config)
        assert not result.eligible
        assert "disabled" in result.rejection_reasons[0].lower()

    def test_order_blocked_by_position_size(self):
        config = PortfolioRiskConfig(trading_mode=RiskTradingMode.PAPER)
        config.position_limits.max_position_value = 1000.0
        state = AccountRiskState(portfolio_value=100_000.0)
        proposed = ProposedOrder(
            ticker="AAPL",
            quantity=100,
            estimated_value=15_000.0,
        )
        result = evaluate_order(proposed, config, state)
        assert not result.eligible


# ---------------------------------------------------------------------------
# Alpaca adapter sandbox mode verification
# ---------------------------------------------------------------------------


class TestAlpacaSandboxMode:
    def test_paper_mode_uses_sandbox_url(self):
        adapter = AlpacaBrokerAdapter(
            api_key="test-key",
            api_secret="test-secret",
            mode=TradingMode.PAPER,
        )
        assert adapter.mode == TradingMode.PAPER
        assert "paper" in adapter.base_url

    def test_custom_sandbox_url(self):
        adapter = AlpacaBrokerAdapter(
            api_key="test-key",
            api_secret="test-secret",
            mode=TradingMode.PAPER,
            base_url="https://paper-api.alpaca.markets",
        )
        assert adapter.base_url == "https://paper-api.alpaca.markets"

    def test_headers_set_correctly(self):
        adapter = AlpacaBrokerAdapter(
            api_key="pk-test",
            api_secret="sk-test",
        )
        headers = adapter._headers()
        assert headers["APCA-API-KEY-ID"] == "pk-test"
        assert headers["APCA-API-SECRET-KEY"] == "sk-test"


# ---------------------------------------------------------------------------
# Queue name constant
# ---------------------------------------------------------------------------


class TestQueueConstant:
    def test_broker_queue_name(self):
        assert QUEUE_BROKER == "broker_orders"


# ---------------------------------------------------------------------------
# Idempotency key generation tests
# ---------------------------------------------------------------------------


class TestGenerateIdempotencyKey:
    def test_explicit_key_passthrough(self):
        job = {"ticker": "AAPL", "idempotency_key": "my-explicit-key"}
        assert generate_idempotency_key(job) == "my-explicit-key"

    def test_deterministic_without_explicit_key(self):
        job = {"ticker": "AAPL", "side": "buy", "quantity": 10, "order_type": "market"}
        key1 = generate_idempotency_key(job)
        key2 = generate_idempotency_key(job)
        assert key1 == key2
        assert len(key1) == 40  # sha256 truncated to 40 chars

    def test_different_jobs_produce_different_keys(self):
        job_a = {"ticker": "AAPL", "side": "buy", "quantity": 10}
        job_b = {"ticker": "AAPL", "side": "sell", "quantity": 10}
        assert generate_idempotency_key(job_a) != generate_idempotency_key(job_b)

    def test_quantity_difference_changes_key(self):
        job_a = {"ticker": "AAPL", "side": "buy", "quantity": 10}
        job_b = {"ticker": "AAPL", "side": "buy", "quantity": 20}
        assert generate_idempotency_key(job_a) != generate_idempotency_key(job_b)

    def test_recommendation_id_included(self):
        job_a = {"ticker": "AAPL", "recommendation_id": "rec-1"}
        job_b = {"ticker": "AAPL", "recommendation_id": "rec-2"}
        assert generate_idempotency_key(job_a) != generate_idempotency_key(job_b)

    def test_minimal_job_still_produces_key(self):
        job = {"ticker": "AAPL"}
        key = generate_idempotency_key(job)
        assert key
        assert len(key) == 40
