"""Hypothesis property-based tests for pyfortis."""

from __future__ import annotations

from hypothesis import given, settings, assume

from pyfortis._types import GateVerdict, Order, Position
from pyfortis.gate import RiskEngine

from tests.strategies import orders, positions, returns_data


SAMPLE_CONFIG = {
    "meta": {"version": "1.0.0", "engine_name": "prop_test"},
    "limits": [
        {
            "name": "max_pos",
            "type": "position",
            "scope": "instrument",
            "params": {"max_long": 10000, "max_short": 5000},
            "severity": "CRITICAL",
        },
        {
            "name": "max_notional",
            "type": "notional",
            "scope": "order",
            "params": {"max_notional": 5000000},
            "severity": "CRITICAL",
        },
    ],
    "metrics": [
        {"name": "var", "type": "var", "params": {"confidence": 0.95}},
    ],
    "circuit_breakers": [],
    "breach_policy": {"default_action": "log"},
}


class TestGateProperties:
    @given(order=orders)
    @settings(max_examples=50)
    def test_gate_always_returns_verdict(self, order: Order):
        """Gate must always return a valid GateResult."""
        engine = RiskEngine.from_dict(SAMPLE_CONFIG)
        result = engine.validate_order(order)
        assert result.verdict in (
            GateVerdict.APPROVED,
            GateVerdict.REJECTED,
            GateVerdict.APPROVED_WITH_WARNING,
        )
        assert result.order_id == order.order_id

    @given(order=orders)
    @settings(max_examples=50)
    def test_rejected_implies_breaches(self, order: Order):
        """If an order is rejected, there must be at least one breach."""
        engine = RiskEngine.from_dict(SAMPLE_CONFIG)
        result = engine.validate_order(order)
        if result.is_rejected:
            assert len(result.breaches) > 0

    @given(order=orders)
    @settings(max_examples=50)
    def test_approved_no_critical_breaches(self, order: Order):
        """Approved orders must have no CRITICAL+ breaches."""
        engine = RiskEngine.from_dict(SAMPLE_CONFIG)
        result = engine.validate_order(order)
        if result.verdict is GateVerdict.APPROVED:
            assert len(result.breaches) == 0


class TestMonitorProperties:
    @given(order=orders)
    @settings(max_examples=30)
    def test_monitor_state_consistent(self, order: Order):
        """Monitor positions stay consistent after updates."""
        engine = RiskEngine.from_dict(SAMPLE_CONFIG)
        monitor = engine.create_monitor()

        pos = Position(
            instrument=order.instrument,
            quantity=100,
            avg_price=order.price,
            current_price=order.price,
        )
        monitor.update_position(pos)
        positions = monitor.get_positions()
        assert order.instrument in positions

        result = monitor.validate_order(order)
        assert result.verdict in (
            GateVerdict.APPROVED,
            GateVerdict.REJECTED,
            GateVerdict.APPROVED_WITH_WARNING,
        )

    @given(data=returns_data)
    @settings(max_examples=30)
    def test_metrics_always_return_list(self, data: list[float]):
        """Metrics computation always returns a list."""
        engine = RiskEngine.from_dict(SAMPLE_CONFIG)
        monitor = engine.create_monitor()
        results = monitor.compute_metrics(returns_data=data)
        assert isinstance(results, list)
