"""Deterministic Decimal Position Sizer with Small-Account Protection."""
from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, ROUND_DOWN
from typing import Optional

from backend.app.config.settings import settings
from backend.app.schemas.risk import InstrumentFilters


@dataclass
class SizingResult:
    approved: bool
    quantity: Decimal
    notional: Decimal
    risk_budget: Decimal
    effective_loss_per_unit: Decimal
    rejection_reason: Optional[str] = None


class PositionSizer:
    """Calculates exact order quantity based on fixed fractional equity risk and exchange lot filters."""

    def __init__(
        self,
        risk_pct: Optional[float] = None,
        taker_fee_pct: Optional[float] = None,
        slippage_pct: Optional[float] = None,
        uncertainty_pct: Optional[float] = None,
    ) -> None:
        self.risk_pct = Decimal(str(risk_pct if risk_pct is not None else settings.RISK_PER_TRADE_PCT))
        self.taker_fee_pct = Decimal(str(taker_fee_pct if taker_fee_pct is not None else settings.DEFAULT_TAKER_FEE_PCT))
        self.slippage_pct = Decimal(str(slippage_pct if slippage_pct is not None else settings.ESTIMATED_SLIPPAGE_PCT))
        self.uncertainty_pct = Decimal(str(uncertainty_pct if uncertainty_pct is not None else settings.UNCERTAINTY_BUFFER_PCT))

    def calculate_size(
        self,
        equity: Decimal,
        entry_price: Decimal,
        stop_loss_price: Decimal,
        filters: InstrumentFilters,
    ) -> SizingResult:
        if equity <= Decimal("0"):
            return SizingResult(
                approved=False,
                quantity=Decimal("0"),
                notional=Decimal("0"),
                risk_budget=Decimal("0"),
                effective_loss_per_unit=Decimal("0"),
                rejection_reason="Account equity is zero or negative.",
            )

        stop_distance = abs(entry_price - stop_loss_price)
        if stop_distance <= Decimal("0"):
            return SizingResult(
                approved=False,
                quantity=Decimal("0"),
                notional=Decimal("0"),
                risk_budget=Decimal("0"),
                effective_loss_per_unit=Decimal("0"),
                rejection_reason="Stop loss price cannot be identical to entry price.",
            )

        # 1. Calculate risk budget
        risk_budget = equity * self.risk_pct

        # 2. Friction per unit (round trip taker fees + slippage + uncertainty margin)
        friction_pct = (Decimal("2") * self.taker_fee_pct) + self.slippage_pct + self.uncertainty_pct
        friction_per_unit = entry_price * friction_pct
        effective_loss_per_unit = stop_distance + friction_per_unit

        # 3. Raw quantity
        raw_qty = risk_budget / effective_loss_per_unit

        # 4. Quantize to instrument step size (always round down to avoid exceeding risk)
        qty_step = filters.qty_step
        steps = (raw_qty / qty_step).quantize(Decimal("1"), rounding=ROUND_DOWN)
        quantity = steps * qty_step

        notional = quantity * entry_price

        # 5. Small Account & Exchange Constraints Check
        if quantity < filters.min_order_qty:
            return SizingResult(
                approved=False,
                quantity=Decimal("0"),
                notional=Decimal("0"),
                risk_budget=risk_budget,
                effective_loss_per_unit=effective_loss_per_unit,
                rejection_reason=(
                    f"Calculated size ({quantity}) is below exchange minimum order quantity ({filters.min_order_qty}). "
                    "Trade rejected by Small Account Protection to prevent risk inflation."
                ),
            )

        if notional < filters.min_notional:
            return SizingResult(
                approved=False,
                quantity=Decimal("0"),
                notional=Decimal("0"),
                risk_budget=risk_budget,
                effective_loss_per_unit=effective_loss_per_unit,
                rejection_reason=(
                    f"Calculated notional (${notional:.2f}) is below exchange minimum notional (${filters.min_notional:.2f}). "
                    "Trade rejected by Small Account Protection to preserve capital."
                ),
            )

        return SizingResult(
            approved=True,
            quantity=quantity,
            notional=notional,
            risk_budget=risk_budget,
            effective_loss_per_unit=effective_loss_per_unit,
        )


position_sizer = PositionSizer()
