"""Built-in limit validators for pyfortis."""

from __future__ import annotations

from typing import Any

from pyfortis._types import (
    Exposure,
    LimitCheckResult,
    LimitSpec,
    Order,
    Position,
    Severity,
    Side,
)


def validate_position(
    limit: LimitSpec,
    order: Order,
    positions: dict[str, Position],
    exposures: dict[str, Exposure],
    context: dict[str, Any],
) -> LimitCheckResult:
    """Check max long / max short position limits."""
    max_long = limit.params.get("max_long", float("inf"))
    max_short = limit.params.get("max_short", float("inf"))

    current_qty = 0.0
    pos = positions.get(order.instrument)
    if pos is not None:
        current_qty = pos.quantity

    projected = current_qty + order.signed_quantity

    if projected > 0 and projected > max_long:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=False,
            severity=limit.severity,
            message=(
                f"Projected long position {projected} exceeds max_long {max_long} "
                f"for {order.instrument}"
            ),
            details={"projected": projected, "max_long": max_long},
        )

    if projected < 0 and abs(projected) > max_short:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=False,
            severity=limit.severity,
            message=(
                f"Projected short position {abs(projected)} exceeds max_short {max_short} "
                f"for {order.instrument}"
            ),
            details={"projected": projected, "max_short": max_short},
        )

    return LimitCheckResult(
        limit_name=limit.name,
        passed=True,
        severity=limit.severity,
        message="Position within limits",
    )


def validate_concentration(
    limit: LimitSpec,
    order: Order,
    positions: dict[str, Position],
    exposures: dict[str, Exposure],
    context: dict[str, Any],
) -> LimitCheckResult:
    """Check max single-asset % and sector % concentration."""
    max_single_pct = limit.params.get("max_single_asset_pct", 1.0)
    max_sector_pct = limit.params.get("max_sector_pct", 1.0)

    total_notional = sum(p.notional for p in positions.values())
    if total_notional == 0:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=True,
            severity=limit.severity,
            message="No positions, concentration check skipped",
        )

    # Single-asset check
    pos = positions.get(order.instrument)
    current_notional = pos.notional if pos else 0.0
    projected_notional = current_notional + order.notional
    projected_total = total_notional + order.notional

    asset_pct = projected_notional / projected_total if projected_total > 0 else 0.0

    if asset_pct > max_single_pct:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=False,
            severity=limit.severity,
            message=(
                f"Asset {order.instrument} concentration {asset_pct:.2%} "
                f"exceeds max {max_single_pct:.2%}"
            ),
            details={"asset_pct": asset_pct, "max_single_asset_pct": max_single_pct},
        )

    # Sector check
    if order.sector:
        sector_notional = sum(
            p.notional for p in positions.values() if p.sector == order.sector
        )
        sector_notional += order.notional
        sector_pct = sector_notional / projected_total if projected_total > 0 else 0.0

        if sector_pct > max_sector_pct:
            return LimitCheckResult(
                limit_name=limit.name,
                passed=False,
                severity=limit.severity,
                message=(
                    f"Sector {order.sector} concentration {sector_pct:.2%} "
                    f"exceeds max {max_sector_pct:.2%}"
                ),
                details={"sector_pct": sector_pct, "max_sector_pct": max_sector_pct},
            )

    return LimitCheckResult(
        limit_name=limit.name,
        passed=True,
        severity=limit.severity,
        message="Concentration within limits",
    )


def validate_price_tolerance(
    limit: LimitSpec,
    order: Order,
    positions: dict[str, Position],
    exposures: dict[str, Exposure],
    context: dict[str, Any],
) -> LimitCheckResult:
    """Fat-finger check: order price vs current market price."""
    max_deviation_pct = limit.params.get("max_deviation_pct", 0.05)

    reference_price = context.get("reference_price")
    if reference_price is None:
        pos = positions.get(order.instrument)
        if pos is not None:
            reference_price = pos.current_price

    if reference_price is None or reference_price == 0:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=True,
            severity=limit.severity,
            message="No reference price available, skipping price tolerance check",
        )

    deviation = abs(order.price - reference_price) / reference_price

    if deviation > max_deviation_pct:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=False,
            severity=limit.severity,
            message=(
                f"Price {order.price} deviates {deviation:.2%} from reference "
                f"{reference_price} (max {max_deviation_pct:.2%})"
            ),
            details={
                "deviation": deviation,
                "max_deviation_pct": max_deviation_pct,
                "reference_price": reference_price,
            },
        )

    return LimitCheckResult(
        limit_name=limit.name,
        passed=True,
        severity=limit.severity,
        message="Price within tolerance",
    )


def validate_order_rate(
    limit: LimitSpec,
    order: Order,
    positions: dict[str, Position],
    exposures: dict[str, Exposure],
    context: dict[str, Any],
) -> LimitCheckResult:
    """Check max orders per time window."""
    max_orders = limit.params.get("max_orders", 100)
    recent_count = context.get("recent_order_count", 0)

    if recent_count >= max_orders:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=False,
            severity=limit.severity,
            message=f"Order rate {recent_count} exceeds max {max_orders}",
            details={"recent_count": recent_count, "max_orders": max_orders},
        )

    return LimitCheckResult(
        limit_name=limit.name,
        passed=True,
        severity=limit.severity,
        message="Order rate within limits",
    )


def validate_notional(
    limit: LimitSpec,
    order: Order,
    positions: dict[str, Position],
    exposures: dict[str, Exposure],
    context: dict[str, Any],
) -> LimitCheckResult:
    """Check max notional per order."""
    max_notional = limit.params.get("max_notional", float("inf"))

    if order.notional > max_notional:
        return LimitCheckResult(
            limit_name=limit.name,
            passed=False,
            severity=limit.severity,
            message=(
                f"Order notional {order.notional:,.2f} exceeds max {max_notional:,.2f}"
            ),
            details={"order_notional": order.notional, "max_notional": max_notional},
        )

    return LimitCheckResult(
        limit_name=limit.name,
        passed=True,
        severity=limit.severity,
        message="Notional within limits",
    )


def validate_self_trade(
    limit: LimitSpec,
    order: Order,
    positions: dict[str, Position],
    exposures: dict[str, Exposure],
    context: dict[str, Any],
) -> LimitCheckResult:
    """Prevent conflicting orders on same instrument."""
    pending_orders: list[Order] = context.get("pending_orders", [])

    for pending in pending_orders:
        if pending.instrument == order.instrument and pending.side is not order.side:
            return LimitCheckResult(
                limit_name=limit.name,
                passed=False,
                severity=limit.severity,
                message=(
                    f"Self-trade detected: existing {pending.side.value} order "
                    f"{pending.order_id} conflicts with {order.side.value} order "
                    f"on {order.instrument}"
                ),
                details={
                    "conflicting_order_id": pending.order_id,
                    "conflicting_side": pending.side.value,
                },
            )

    return LimitCheckResult(
        limit_name=limit.name,
        passed=True,
        severity=limit.severity,
        message="No self-trade detected",
    )


BUILTIN_VALIDATORS: dict[str, Any] = {
    "position": validate_position,
    "concentration": validate_concentration,
    "price_tolerance": validate_price_tolerance,
    "order_rate": validate_order_rate,
    "notional": validate_notional,
    "self_trade": validate_self_trade,
}
