"""Tests for the stateless Engine (Layer 1)."""

from __future__ import annotations

import pytest

from pyfortis._engine import Engine, _CalculatorChecker, _ValidatorChecker
from pyfortis._types import (
    BreachAction,
    CircuitBreakerSpec,
    CircuitBreakerType,
    EscalationPolicy,
    GateVerdict,
    LimitCheckResult,
    MetricResult,
    Order,
    Position,
    Severity,
    Side,
)
from pyfortis._validators import BUILTIN_VALIDATORS
from pyfortis._calculators import BUILTIN_CALCULATORS
from pyfortis.errors import RiskPolicy


def _make_engine(limits, metrics=None, cbs=None, escalation=None, policy=None):
    esc = escalation or EscalationPolicy(
        default_action=BreachAction.LOG,
        escalation={
            Severity.WARNING: [BreachAction.LOG, BreachAction.NOTIFY],
            Severity.CRITICAL: [BreachAction.LOG, BreachAction.NOTIFY, BreachAction.BLOCK_ORDER],
        },
    )
    return Engine(
        limits=limits,
        metrics=metrics or [],
        circuit_breakers=cbs or [],
        escalation_policy=esc,
        risk_policy=policy or RiskPolicy(),
        validator_checker=_ValidatorChecker(BUILTIN_VALIDATORS.get),
        calculator_checker=_CalculatorChecker(BUILTIN_CALCULATORS.get),
    )


class TestEvaluateOrder:
    def test_all_pass(self, sample_order, sample_limits, sample_exposures):
        engine = _make_engine(sample_limits)
        result = engine.evaluate_order(sample_order, {}, sample_exposures)
        assert result.verdict is GateVerdict.APPROVED

    def test_critical_breach_rejects(self, sample_limits, sample_exposures, position_limit):
        engine = _make_engine([position_limit])
        order = Order(
            order_id="x", instrument="AAPL", side=Side.BUY,
            quantity=20000, price=150.0,
        )
        result = engine.evaluate_order(order, {}, sample_exposures)
        assert result.verdict is GateVerdict.REJECTED
        assert len(result.breaches) > 0

    def test_warning_breach_approves_with_warning(self, concentration_limit):
        engine = _make_engine([concentration_limit])
        positions = {
            "AAPL": Position(
                instrument="AAPL", quantity=1000, avg_price=150.0, current_price=150.0,
            ),
        }
        # Large order that causes high concentration
        order = Order(
            order_id="x", instrument="AAPL", side=Side.BUY,
            quantity=10000, price=150.0, sector="tech",
        )
        result = engine.evaluate_order(order, positions, {})
        # Should be APPROVED_WITH_WARNING since severity is WARNING
        assert result.verdict is GateVerdict.APPROVED_WITH_WARNING

    def test_disabled_limits_skipped(self):
        from pyfortis._types import LimitSpec, LimitType, LimitScope
        limit = LimitSpec(
            name="disabled", type=LimitType.POSITION, scope=LimitScope.INSTRUMENT,
            params={"max_long": 1}, severity=Severity.CRITICAL, enabled=False,
        )
        engine = _make_engine([limit])
        order = Order(
            order_id="x", instrument="AAPL", side=Side.BUY,
            quantity=1000, price=150.0,
        )
        result = engine.evaluate_order(order, {}, {})
        assert result.verdict is GateVerdict.APPROVED


class TestComputeMetrics:
    def test_compute_all(self, sample_metrics, sample_returns):
        engine = _make_engine([], metrics=sample_metrics)
        results = engine.compute_all_metrics({}, sample_returns, {
            "peak_value": 1000000, "current_value": 950000,
        })
        assert len(results) == 2

    def test_compute_single(self, var_metric, sample_returns):
        engine = _make_engine([], metrics=[var_metric])
        result = engine.compute_metric(var_metric, {}, sample_returns)
        assert result.metric_name == "portfolio_var"


class TestCircuitBreakers:
    def test_no_trip(self, daily_pnl_breaker):
        engine = _make_engine([], cbs=[daily_pnl_breaker])
        tripped = engine.evaluate_circuit_breakers(daily_pnl=-10000)
        assert len(tripped) == 0

    def test_trip_daily_pnl(self, daily_pnl_breaker):
        engine = _make_engine([], cbs=[daily_pnl_breaker])
        tripped = engine.evaluate_circuit_breakers(daily_pnl=-60000)
        assert len(tripped) == 1
        assert tripped[0].name == "daily_loss_limit"

    def test_trip_drawdown(self):
        cb = CircuitBreakerSpec(
            name="dd_breaker",
            type=CircuitBreakerType.DRAWDOWN,
            params={"max_drawdown": 0.10},
            action=BreachAction.HALT_TRADING,
        )
        engine = _make_engine([], cbs=[cb])
        tripped = engine.evaluate_circuit_breakers(
            peak_value=1000000, current_value=850000,
        )
        assert len(tripped) == 1
