from __future__ import annotations

from dataclasses import dataclass

from config import BotConfig


@dataclass(frozen=True)
class PositionSizeDecision:
    coin: str
    size: float
    notional_usd: float
    margin_usd: float
    risk_usd: float
    stop_distance_pct: float
    leverage: float
    capped_by: str


def _round_money(value: float) -> float:
    return round(float(value), 8)


def calculate_position_size(*, coin: str, current_price: float, leverage: float, stop_distance_pct: float, cfg: BotConfig) -> PositionSizeDecision:
    if current_price <= 0:
        raise ValueError("current_price must be positive")
    if leverage <= 0:
        raise ValueError("leverage must be positive")

    mode = cfg.position_sizing_mode.strip().lower()
    min_notional = max(0.0, float(cfg.min_order_notional_usd))
    if mode != "risk":
        notional = max(float(cfg.trade_size_usd), min_notional)
        return PositionSizeDecision(
            coin=coin.upper(),
            size=_round_money(notional / current_price),
            notional_usd=_round_money(notional),
            margin_usd=_round_money(notional / leverage),
            risk_usd=_round_money(notional * max(stop_distance_pct, 0.0) / 100.0),
            stop_distance_pct=_round_money(stop_distance_pct),
            leverage=_round_money(leverage),
            capped_by="fixed",
        )

    stop_pct = max(float(stop_distance_pct), 0.01)
    risk_usd = max(0.0, float(cfg.wallet_equity_usdc) * float(cfg.risk_per_trade_pct) / 100.0)
    risk_notional = risk_usd / (stop_pct / 100.0) if risk_usd > 0 else min_notional
    margin_cap_notional = float(cfg.wallet_equity_usdc) * float(cfg.max_position_margin_pct) / 100.0 * leverage
    wallet_notional_cap = float(cfg.wallet_equity_usdc) * float(cfg.max_position_notional_pct) / 100.0

    cap_candidates = [("risk", risk_notional)]
    if margin_cap_notional > 0:
        cap_candidates.append(("margin_cap", margin_cap_notional))
    if wallet_notional_cap > 0:
        cap_candidates.append(("notional_cap", wallet_notional_cap))
    capped_by, notional = min(cap_candidates, key=lambda item: item[1])
    notional = max(min_notional, notional)

    return PositionSizeDecision(
        coin=coin.upper(),
        size=_round_money(notional / current_price),
        notional_usd=_round_money(notional),
        margin_usd=_round_money(notional / leverage),
        risk_usd=_round_money(notional * stop_pct / 100.0),
        stop_distance_pct=_round_money(stop_pct),
        leverage=_round_money(leverage),
        capped_by=capped_by,
    )
