"""Tests for EngagementContext model and ActionType enum."""

from decimal import Decimal
from uuid import uuid4

import pytest
from tradepose_models.enums import (
    ActionType,
    EngagementPhase,
    OrderSide,
    OrderStatus,
    OrderStrategy,
    TradeDirection,
)
from tradepose_models.trading import (
    EngagementContext,
    ExitReason,
)


class TestActionType:
    """Tests for ActionType enum."""

    def test_action_type_values(self):
        """Test ActionType enum has all expected values."""
        # Entry actions
        assert ActionType.MARKET_ENTRY == "MARKET_ENTRY"
        assert ActionType.LIMIT_ENTRY == "LIMIT_ENTRY"
        # Position actions
        assert ActionType.CLOSE_POSITION == "CLOSE_POSITION"
        assert ActionType.MODIFY_POSITION == "MODIFY_POSITION"
        # Order actions
        assert ActionType.MODIFY_ORDER == "MODIFY_ORDER"
        assert ActionType.CANCEL_ORDER == "CANCEL_ORDER"
        # No action
        assert ActionType.NONE == "NONE"

    def test_action_type_is_string_enum(self):
        """Test ActionType is a string enum for JSON serialization."""
        assert isinstance(ActionType.MARKET_ENTRY.value, str)
        assert ActionType.MARKET_ENTRY.value == "MARKET_ENTRY"


class TestEngagementContext:
    """Tests for EngagementContext model."""

    @pytest.fixture
    def base_context(self):
        """Create a base context for testing with all fields."""
        return EngagementContext(
            # ============================================================
            # 1. IDENTITY & RELATIONS
            # ============================================================
            engagement_id=uuid4(),
            config_id=uuid4(),
            trade_id=uuid4(),
            user_id=uuid4(),
            account_id=uuid4(),
            portfolio_id=uuid4(),
            # ============================================================
            # 2. STRATEGY METADATA
            # ============================================================
            strategy_name="Test Strategy",
            blueprint_name="Test Blueprint",
            # ============================================================
            # 3. INSTRUMENT SPECIFICATIONS
            # ============================================================
            symbol="EUR-USD",
            trading_symbol="EURUSD",
            trading_point_value=Decimal("100000"),
            price_precision=5,
            quantity_precision=2,
            # ============================================================
            # 4. PORTFOLIO & CAPITAL
            # ============================================================
            capital=Decimal("10000"),
            capital_currency="USD",
            capital_override=None,
            num_allocations=1,
            # ============================================================
            # 5. TRADE STATE
            # ============================================================
            direction=TradeDirection.LONG,
            trade_status=True,  # Open
            entry_price=Decimal("1.1000"),
            exit_price=None,
            mae=Decimal("50"),
            order_strategy=None,  # Default: IMMEDIATE_ENTRY
            entry_volatility=None,
            # ============================================================
            # 6. ENGAGEMENT LIFECYCLE
            # ============================================================
            phase=EngagementPhase.PENDING,
            target_quantity=Decimal("0.1"),
            # ============================================================
            # 7. BROKER STATE
            # ============================================================
            entry_broker_order_id=None,
            entry_status=None,
            entry_filled_qty=Decimal("0"),
            entry_avg_price=None,
            sl_broker_order_id=None,
            current_sl=None,
            tp_broker_order_id=None,
            current_tp=None,
            broker_position_id=None,
            current_entry_order_price=None,
            # ============================================================
            # 8. MODIFY SIGNALS
            # ============================================================
            suggested_sl=None,
            suggested_tp=None,
            suggested_entry_price=None,
            # ============================================================
            # 9. RISK CONTROL
            # ============================================================
            expected_loss_pct=None,
            historical_trade_count=0,
            risk_padding_pct=None,
        )

    def test_side_property_long(self, base_context):
        """Test side property for long direction."""
        assert base_context.side == OrderSide.BUY
        assert base_context.exit_side == OrderSide.SELL

    def test_side_property_short(self, base_context):
        """Test side property for short direction."""
        base_context.direction = TradeDirection.SHORT
        assert base_context.side == OrderSide.SELL
        assert base_context.exit_side == OrderSide.BUY

    def test_is_terminal_false(self, base_context):
        """Test is_terminal for non-terminal phase."""
        assert base_context.is_terminal is False

    def test_is_terminal_true(self, base_context):
        """Test is_terminal for terminal phases."""
        for phase in [
            EngagementPhase.CLOSED,
            EngagementPhase.FAILED,
            EngagementPhase.CANCELLED,
            EngagementPhase.EXPIRED,
        ]:
            base_context.phase = phase
            assert base_context.is_terminal is True

    def test_needs_entry_pending_open(self, base_context):
        """Test needs_entry for PENDING + trade open."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = True
        assert base_context.needs_entry is True

    def test_needs_entry_pending_closed(self, base_context):
        """Test needs_entry for PENDING + trade closed."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = False
        assert base_context.needs_entry is False

    def test_needs_exit_holding_closed(self, base_context):
        """Test needs_exit for HOLDING + trade closed."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = False
        assert base_context.needs_exit is True

    def test_needs_exit_holding_open(self, base_context):
        """Test needs_exit for HOLDING + trade open."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = True
        assert base_context.needs_exit is False

    def test_is_expired_pending_closed(self, base_context):
        """Test is_expired for PENDING + trade closed."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = False
        assert base_context.is_expired is True

    # ==================== needs_modify tests ====================

    def test_needs_modify_holding_with_sl_change(self, base_context):
        """Test needs_modify when SL differs from suggested."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = True
        base_context.current_sl = Decimal("1.0900")
        base_context.suggested_sl = Decimal("1.0950")  # Different
        assert base_context.needs_modify is True

    def test_needs_modify_holding_with_tp_change(self, base_context):
        """Test needs_modify when TP differs from suggested."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = True
        base_context.current_tp = Decimal("1.1100")
        base_context.suggested_tp = Decimal("1.1200")  # Different
        assert base_context.needs_modify is True

    def test_needs_modify_holding_no_change(self, base_context):
        """Test needs_modify when SL/TP match suggested."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = True
        base_context.current_sl = Decimal("1.0900")
        base_context.suggested_sl = Decimal("1.0900")  # Same
        base_context.current_tp = Decimal("1.1100")
        base_context.suggested_tp = Decimal("1.1100")  # Same
        assert base_context.needs_modify is False

    def test_needs_modify_no_suggested_values(self, base_context):
        """Test needs_modify when no suggested values."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = True
        base_context.suggested_sl = None
        base_context.suggested_tp = None
        assert base_context.needs_modify is False

    def test_needs_modify_not_holding(self, base_context):
        """Test needs_modify returns False when not in HOLDING phase."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = True
        base_context.suggested_sl = Decimal("1.0950")
        assert base_context.needs_modify is False

    def test_needs_modify_trade_closed(self, base_context):
        """Test needs_modify returns False when trade is closed."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = False  # Closed
        base_context.suggested_sl = Decimal("1.0950")
        assert base_context.needs_modify is False

    # ==================== determine_action tests ====================

    def test_determine_action_terminal_returns_none(self, base_context):
        """Test that terminal phases return NONE."""
        for phase in [
            EngagementPhase.CLOSED,
            EngagementPhase.FAILED,
            EngagementPhase.CANCELLED,
            EngagementPhase.EXPIRED,
        ]:
            base_context.phase = phase
            assert base_context.determine_action() == ActionType.NONE

    def test_determine_action_pending_open_immediate_returns_market_entry(self, base_context):
        """Test PENDING + trade open + IMMEDIATE_ENTRY returns MARKET_ENTRY."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = True
        base_context.entry_reason = 0  # IMMEDIATE_ENTRY
        assert base_context.determine_action() == ActionType.MARKET_ENTRY

    def test_determine_action_pending_open_favorable_returns_limit_entry(self, base_context):
        """Test PENDING + trade open + FAVORABLE_DELAY_ENTRY returns LIMIT_ENTRY."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = True
        base_context.entry_reason = 1  # FAVORABLE_DELAY_ENTRY
        assert base_context.determine_action() == ActionType.LIMIT_ENTRY

    def test_determine_action_pending_open_adverse_returns_limit_entry(self, base_context):
        """Test PENDING + trade open + ADVERSE_DELAY_ENTRY returns LIMIT_ENTRY."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = True
        base_context.entry_reason = 2  # ADVERSE_DELAY_ENTRY
        assert base_context.determine_action() == ActionType.LIMIT_ENTRY

    def test_determine_action_pending_closed_returns_none(self, base_context):
        """Test PENDING + trade closed returns NONE (should mark EXPIRED)."""
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = False
        assert base_context.determine_action() == ActionType.NONE

    def test_determine_action_holding_closed_returns_close_position(self, base_context):
        """Test HOLDING + trade closed returns CLOSE_POSITION."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = False
        base_context.entry_filled_qty = Decimal("0.1")
        base_context.broker_position_id = "12345"
        assert base_context.determine_action() == ActionType.CLOSE_POSITION

    def test_determine_action_holding_open_no_modify_returns_none(self, base_context):
        """Test HOLDING + trade open + no SL/TP change returns NONE."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = True
        base_context.suggested_sl = None
        base_context.suggested_tp = None
        assert base_context.determine_action() == ActionType.NONE

    def test_determine_action_holding_open_with_modify_returns_modify_position(self, base_context):
        """Test HOLDING + trade open + SL/TP change returns MODIFY_POSITION."""
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = True
        base_context.current_sl = Decimal("1.0900")
        base_context.suggested_sl = Decimal("1.0950")  # Different
        assert base_context.determine_action() == ActionType.MODIFY_POSITION

    def test_determine_action_entering_returns_none(self, base_context):
        """Test ENTERING phase returns NONE (waiting for broker)."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.trade_status = True
        assert base_context.determine_action() == ActionType.NONE

    def test_determine_action_exiting_returns_none(self, base_context):
        """Test EXITING phase returns NONE (waiting for broker)."""
        base_context.phase = EngagementPhase.EXITING
        base_context.trade_status = False
        assert base_context.determine_action() == ActionType.NONE

    def test_entry_action_short_direction(self, base_context):
        """Test entry action for short direction."""
        base_context.direction = TradeDirection.SHORT
        base_context.phase = EngagementPhase.PENDING
        base_context.trade_status = True
        assert base_context.determine_action() == ActionType.MARKET_ENTRY
        assert base_context.side == OrderSide.SELL

    def test_exit_action_short_direction(self, base_context):
        """Test exit action for short direction."""
        base_context.direction = TradeDirection.SHORT
        base_context.phase = EngagementPhase.HOLDING
        base_context.trade_status = False
        base_context.entry_filled_qty = Decimal("0.1")
        base_context.broker_position_id = "12345"
        assert base_context.determine_action() == ActionType.CLOSE_POSITION
        assert base_context.exit_side == OrderSide.BUY

    # ==================== is_limit_entry tests ====================

    def test_is_limit_entry_immediate(self, base_context):
        """Test is_limit_entry is False for IMMEDIATE_ENTRY."""
        base_context.entry_reason = 0  # IMMEDIATE_ENTRY
        assert base_context.is_limit_entry is False

    def test_is_limit_entry_favorable(self, base_context):
        """Test is_limit_entry is True for FAVORABLE_DELAY_ENTRY."""
        base_context.entry_reason = 1  # FAVORABLE_DELAY_ENTRY
        assert base_context.is_limit_entry is True

    def test_is_limit_entry_adverse(self, base_context):
        """Test is_limit_entry is True for ADVERSE_DELAY_ENTRY."""
        base_context.entry_reason = 2  # ADVERSE_DELAY_ENTRY
        assert base_context.is_limit_entry is True

    # ==================== has_pending_entry_order tests ====================

    def test_has_pending_entry_order_true(self, base_context):
        """Test has_pending_entry_order for ENTERING with NEW order."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        assert base_context.has_pending_entry_order is True

    def test_has_pending_entry_order_partial_fill(self, base_context):
        """Test has_pending_entry_order for ENTERING with PARTIALLY_FILLED order."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.PARTIALLY_FILLED
        assert base_context.has_pending_entry_order is True

    def test_has_pending_entry_order_no_order_id(self, base_context):
        """Test has_pending_entry_order is False when no order ID."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = None
        base_context.entry_status = OrderStatus.NEW
        assert base_context.has_pending_entry_order is False

    def test_has_pending_entry_order_filled(self, base_context):
        """Test has_pending_entry_order is False when order is FILLED."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.FILLED
        assert base_context.has_pending_entry_order is False

    def test_has_pending_entry_order_wrong_phase(self, base_context):
        """Test has_pending_entry_order is False when not in ENTERING phase."""
        base_context.phase = EngagementPhase.PENDING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        assert base_context.has_pending_entry_order is False

    # ==================== needs_cancel_entry tests ====================

    def test_needs_cancel_entry_true(self, base_context):
        """Test needs_cancel_entry for ENTERING + trade closed."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = False  # Trade closed
        assert base_context.needs_cancel_entry is True

    def test_needs_cancel_entry_trade_open(self, base_context):
        """Test needs_cancel_entry is False when trade still open."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = True  # Trade open
        assert base_context.needs_cancel_entry is False

    def test_needs_cancel_entry_no_pending_order(self, base_context):
        """Test needs_cancel_entry is False when no pending order."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = None
        base_context.trade_status = False
        assert base_context.needs_cancel_entry is False

    # ==================== needs_modify_entry tests ====================

    def test_needs_modify_entry_true(self, base_context):
        """Test needs_modify_entry when entry price changed."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = True
        base_context.current_entry_order_price = Decimal("1.0950")
        base_context.suggested_entry_price = Decimal("1.0960")  # Different
        assert base_context.needs_modify_entry is True

    def test_needs_modify_entry_same_price(self, base_context):
        """Test needs_modify_entry is False when prices match."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = True
        base_context.current_entry_order_price = Decimal("1.0950")
        base_context.suggested_entry_price = Decimal("1.0950")  # Same
        assert base_context.needs_modify_entry is False

    def test_needs_modify_entry_no_suggested_price(self, base_context):
        """Test needs_modify_entry is False when no suggested price."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = True
        base_context.current_entry_order_price = Decimal("1.0950")
        base_context.suggested_entry_price = None
        assert base_context.needs_modify_entry is False

    def test_needs_modify_entry_trade_closed(self, base_context):
        """Test needs_modify_entry is False when trade closed."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = False  # Closed
        base_context.current_entry_order_price = Decimal("1.0950")
        base_context.suggested_entry_price = Decimal("1.0960")
        assert base_context.needs_modify_entry is False

    # ==================== determine_action CANCEL_ORDER/MODIFY_ORDER tests ====================

    def test_determine_action_entering_trade_closed_returns_cancel_order(self, base_context):
        """Test ENTERING + trade closed returns CANCEL_ORDER."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = False
        assert base_context.determine_action() == ActionType.CANCEL_ORDER

    def test_determine_action_entering_price_changed_returns_modify_order(self, base_context):
        """Test ENTERING + entry price changed returns MODIFY_ORDER."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = True
        base_context.current_entry_order_price = Decimal("1.0950")
        base_context.suggested_entry_price = Decimal("1.0960")
        assert base_context.determine_action() == ActionType.MODIFY_ORDER

    def test_determine_action_entering_no_change_returns_none(self, base_context):
        """Test ENTERING with no changes returns NONE."""
        base_context.phase = EngagementPhase.ENTERING
        base_context.entry_broker_order_id = "123456"
        base_context.entry_status = OrderStatus.NEW
        base_context.trade_status = True
        base_context.current_entry_order_price = Decimal("1.0950")
        base_context.suggested_entry_price = Decimal("1.0950")  # Same
        assert base_context.determine_action() == ActionType.NONE


class TestExitReasonSignal:
    """Test the SIGNAL exit reason."""

    def test_exit_reason_signal_exists(self):
        """Test that SIGNAL exit reason exists."""
        assert ExitReason.SIGNAL == "SIGNAL"


class TestCurrentEntryExitStrategy:
    """Tests for current_entry_strategy and current_exit_strategy computed properties."""

    @pytest.fixture
    def base_context(self):
        """Create a base context for testing."""
        return EngagementContext(
            engagement_id=uuid4(),
            config_id=uuid4(),
            trade_id=uuid4(),
            user_id=uuid4(),
            account_id=uuid4(),
            portfolio_id=uuid4(),
            strategy_name="Test Strategy",
            blueprint_name="Test Blueprint",
            symbol="EUR-USD",
            trading_symbol="EURUSD",
            trading_point_value=Decimal("100000"),
            price_precision=5,
            quantity_precision=2,
            capital=Decimal("10000"),
            capital_currency="USD",
            capital_override=None,
            num_allocations=1,
            direction=TradeDirection.LONG,
            trade_status=True,
            entry_price=Decimal("1.1000"),
            exit_price=None,
            mae=Decimal("50"),
            entry_reason=None,  # Use entry_reason instead of order_strategy
            entry_volatility=None,
            phase=EngagementPhase.PENDING,
            target_quantity=Decimal("0.1"),
            entry_broker_order_id=None,
            entry_status=None,
            entry_filled_qty=Decimal("0"),
            entry_avg_price=None,
            sl_broker_order_id=None,
            current_sl=None,
            tp_broker_order_id=None,
            current_tp=None,
            broker_position_id=None,
            current_entry_order_price=None,
            suggested_sl=None,
            suggested_tp=None,
            suggested_entry_price=None,
            expected_loss_pct=None,
            historical_trade_count=0,
            risk_padding_pct=None,
        )

    # ==================== current_entry_strategy tests ====================

    def test_current_entry_strategy_no_entry_reason(self, base_context):
        """Test current_entry_strategy defaults to IMMEDIATE_ENTRY when entry_reason is None."""
        base_context.entry_reason = None
        assert base_context.current_entry_strategy == OrderStrategy.IMMEDIATE_ENTRY

    def test_current_entry_strategy_immediate_entry(self, base_context):
        """Test current_entry_strategy with entry_reason=0 (ImmediateEntry)."""
        base_context.entry_reason = 0
        assert base_context.current_entry_strategy == OrderStrategy.IMMEDIATE_ENTRY

    def test_current_entry_strategy_favorable_delay_entry(self, base_context):
        """Test current_entry_strategy with entry_reason=1 (FavorableDelayEntry)."""
        base_context.entry_reason = 1
        assert base_context.current_entry_strategy == OrderStrategy.FAVORABLE_DELAY_ENTRY

    def test_current_entry_strategy_adverse_delay_entry(self, base_context):
        """Test current_entry_strategy with entry_reason=2 (AdverseDelayEntry)."""
        base_context.entry_reason = 2
        assert base_context.current_entry_strategy == OrderStrategy.ADVERSE_DELAY_ENTRY

    def test_current_entry_strategy_entry_reason_takes_precedence(self, base_context):
        """Test entry_reason determines current_entry_strategy."""
        base_context.entry_reason = 1  # FavorableDelayEntry
        assert base_context.current_entry_strategy == OrderStrategy.FAVORABLE_DELAY_ENTRY

    def test_current_entry_strategy_unknown_value_defaults_to_immediate(self, base_context):
        """Test unknown entry_reason value defaults to IMMEDIATE_ENTRY."""
        base_context.entry_reason = 99  # Unknown value
        assert base_context.current_entry_strategy == OrderStrategy.IMMEDIATE_ENTRY

    # ==================== current_exit_strategy tests ====================

    def test_current_exit_strategy_no_exit_reason(self, base_context):
        """Test current_exit_strategy returns IMMEDIATE_EXIT when exit_reason is None."""
        base_context.exit_reason = None
        assert base_context.current_exit_strategy == OrderStrategy.IMMEDIATE_EXIT

    def test_current_exit_strategy_immediate_exit(self, base_context):
        """Test current_exit_strategy with exit_reason=3 (ImmediateExit)."""
        base_context.exit_reason = 3
        assert base_context.current_exit_strategy == OrderStrategy.IMMEDIATE_EXIT

    def test_current_exit_strategy_stop_loss(self, base_context):
        """Test current_exit_strategy with exit_reason=4 (StopLoss)."""
        base_context.exit_reason = 4
        assert base_context.current_exit_strategy == OrderStrategy.STOP_LOSS

    def test_current_exit_strategy_take_profit(self, base_context):
        """Test current_exit_strategy with exit_reason=5 (TakeProfit)."""
        base_context.exit_reason = 5
        assert base_context.current_exit_strategy == OrderStrategy.TAKE_PROFIT

    def test_current_exit_strategy_trailing_stop(self, base_context):
        """Test current_exit_strategy with exit_reason=6 (TrailingStop)."""
        base_context.exit_reason = 6
        assert base_context.current_exit_strategy == OrderStrategy.TRAILING_STOP

    def test_current_exit_strategy_breakeven(self, base_context):
        """Test current_exit_strategy with exit_reason=7 (Breakeven)."""
        base_context.exit_reason = 7
        assert base_context.current_exit_strategy == OrderStrategy.BREAKEVEN

    def test_current_exit_strategy_timeout_exit(self, base_context):
        """Test current_exit_strategy with exit_reason=8 (TimeoutExit)."""
        base_context.exit_reason = 8
        assert base_context.current_exit_strategy == OrderStrategy.TIMEOUT_EXIT

    def test_current_exit_strategy_unknown_value_defaults_to_immediate(self, base_context):
        """Test unknown exit_reason value defaults to IMMEDIATE_EXIT."""
        base_context.exit_reason = 99  # Unknown value
        assert base_context.current_exit_strategy == OrderStrategy.IMMEDIATE_EXIT

    # ==================== JSON serialization tests ====================

    def test_computed_fields_included_in_model_dump(self, base_context):
        """Test computed fields are included in model_dump output."""
        base_context.entry_reason = 1  # FavorableDelayEntry
        base_context.exit_reason = 4  # StopLoss
        data = base_context.model_dump()
        assert data["current_entry_strategy"] == OrderStrategy.FAVORABLE_DELAY_ENTRY
        assert data["current_exit_strategy"] == OrderStrategy.STOP_LOSS


class TestEngagementContextValidators:
    """Test field validators for automatic type conversion from SQL rows."""

    @pytest.fixture
    def base_fields(self):
        """Base fields for creating EngagementContext with all fields."""
        return {
            # ============================================================
            # 1. IDENTITY & RELATIONS
            # ============================================================
            "engagement_id": uuid4(),
            "config_id": uuid4(),
            "trade_id": uuid4(),
            "user_id": uuid4(),
            "account_id": uuid4(),
            "portfolio_id": uuid4(),
            # ============================================================
            # 2. STRATEGY METADATA
            # ============================================================
            "strategy_name": "Test Strategy",
            "blueprint_name": "Test Blueprint",
            # ============================================================
            # 3. INSTRUMENT SPECIFICATIONS
            # ============================================================
            "symbol": "EURUSD",
            "trading_symbol": "EURUSD",
            "trading_point_value": Decimal("100000"),
            "price_precision": 5,
            "quantity_precision": 2,
            # ============================================================
            # 4. PORTFOLIO & CAPITAL
            # ============================================================
            "capital": Decimal("10000"),
            "capital_currency": "USD",
            "capital_override": None,
            "num_allocations": 1,
            # ============================================================
            # 5. TRADE STATE
            # ============================================================
            "trade_status": True,
            "entry_price": Decimal("1.1000"),
            "exit_price": None,
            "mae": Decimal("50"),
            "entry_volatility": None,
            # ============================================================
            # 6. ENGAGEMENT LIFECYCLE
            # ============================================================
            "phase": EngagementPhase.PENDING,
            "target_quantity": Decimal("0.1"),
            # ============================================================
            # 7. BROKER STATE
            # Note: entry_status excluded - tested explicitly by validators
            # ============================================================
            "entry_broker_order_id": None,
            # "entry_status" excluded - tests pass it explicitly
            "entry_filled_qty": Decimal("0"),
            "entry_avg_price": None,
            "sl_broker_order_id": None,
            "current_sl": None,
            "tp_broker_order_id": None,
            "current_tp": None,
            "broker_position_id": None,
            "current_entry_order_price": None,
            # ============================================================
            # 8. MODIFY SIGNALS
            # ============================================================
            "suggested_sl": None,
            "suggested_tp": None,
            "suggested_entry_price": None,
            # ============================================================
            # 9. RISK CONTROL
            # ============================================================
            "expected_loss_pct": None,
            "historical_trade_count": 0,
            "risk_padding_pct": None,
        }

    def test_direction_validator_accepts_int_long(self, base_fields):
        """Test direction field accepts int 1 for LONG."""
        ctx = EngagementContext(direction=1, **base_fields)
        assert ctx.direction == TradeDirection.LONG

    def test_direction_validator_accepts_int_short(self, base_fields):
        """Test direction field accepts int -1 for SHORT."""
        ctx = EngagementContext(direction=-1, **base_fields)
        assert ctx.direction == TradeDirection.SHORT

    def test_direction_validator_accepts_enum(self, base_fields):
        """Test direction field accepts TradeDirection enum directly."""
        ctx = EngagementContext(direction=TradeDirection.LONG, **base_fields)
        assert ctx.direction == TradeDirection.LONG

    def test_entry_status_validator_accepts_string(self, base_fields):
        """Test entry_status field accepts string and converts to OrderStatus."""
        ctx = EngagementContext(
            direction=TradeDirection.LONG,
            entry_status="filled",
            **base_fields,
        )
        assert ctx.entry_status == OrderStatus.FILLED

    def test_entry_status_validator_accepts_string_new(self, base_fields):
        """Test entry_status field accepts 'new' string."""
        ctx = EngagementContext(
            direction=TradeDirection.LONG,
            entry_status="new",
            **base_fields,
        )
        assert ctx.entry_status == OrderStatus.NEW

    def test_entry_status_validator_accepts_none(self, base_fields):
        """Test entry_status field accepts None."""
        ctx = EngagementContext(
            direction=TradeDirection.LONG,
            entry_status=None,
            **base_fields,
        )
        assert ctx.entry_status is None

    def test_entry_status_validator_accepts_enum(self, base_fields):
        """Test entry_status field accepts OrderStatus enum directly."""
        ctx = EngagementContext(
            direction=TradeDirection.LONG,
            entry_status=OrderStatus.PARTIALLY_FILLED,
            **base_fields,
        )
        assert ctx.entry_status == OrderStatus.PARTIALLY_FILLED

    def test_entry_status_validator_invalid_string_returns_none(self, base_fields):
        """Test invalid entry_status string returns None."""
        ctx = EngagementContext(
            direction=TradeDirection.LONG,
            entry_status="invalid_status_xyz",
            **base_fields,
        )
        assert ctx.entry_status is None

    def test_entry_reason_validator_accepts_int(self, base_fields):
        """Test entry_reason field accepts int value."""
        ctx = EngagementContext(
            direction=TradeDirection.LONG,
            entry_reason=1,  # FavorableDelayEntry
            **base_fields,
        )
        assert ctx.entry_reason == 1
        assert ctx.current_entry_strategy == OrderStrategy.FAVORABLE_DELAY_ENTRY

    def test_entry_reason_validator_accepts_none(self, base_fields):
        """Test entry_reason field accepts None and defaults to IMMEDIATE_ENTRY."""
        ctx = EngagementContext(
            direction=TradeDirection.LONG,
            entry_reason=None,
            **base_fields,
        )
        assert ctx.entry_reason is None
        assert ctx.current_entry_strategy == OrderStrategy.IMMEDIATE_ENTRY

    def test_model_validate_from_dict_like_sql_row(self, base_fields):
        """Test model_validate works with dict simulating SQL row."""
        # Simulate what comes from SQL: direction as int, entry_status as string
        sql_row = {
            **base_fields,
            "direction": 1,  # int from SQL
            "entry_status": "filled",  # string from SQL
            "entry_filled_qty": "0.1",  # Decimal as string from SQL
        }
        ctx = EngagementContext.model_validate(sql_row)
        assert ctx.direction == TradeDirection.LONG
        assert ctx.entry_status == OrderStatus.FILLED
        assert ctx.entry_filled_qty == Decimal("0.1")
