"""Margin of Safety and price bands (spec §26 / VALUATION.md §5)."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Optional

DEFAULT_STRONG_BUY_MOS = 0.35
DEFAULT_BUY_MOS = 0.20
DEFAULT_OVERVALUED_PREMIUM = 0.15


@dataclass(frozen=True)
class PriceBands:
    strong_buy_price: Optional[float]
    buy_price: Optional[float]
    fair_value: Optional[float]
    overvalued_price: Optional[float]
    margin_of_safety: Optional[float]
    risk_adjustment_multiplier: float


def risk_adjustment_multiplier(risk_score_0_100: Optional[float]) -> float:
    """
    Higher risk -> wider required margin of safety. risk_score is 0(highest risk)-100(lowest risk)
    in this codebase's convention (see engines/risk — a higher score means LOWER risk, consistent
    with the other 0-100 scores where higher is always better). Maps risk_score 100->0.85x
    (tighter required MoS for a very low-risk company) .. risk_score 0->1.5x (much wider required
    MoS for a very high-risk company).
    """
    if risk_score_0_100 is None:
        return 1.0
    score = max(0.0, min(100.0, risk_score_0_100))
    return 1.5 - (score / 100.0) * 0.65


def compute_margin_of_safety(price: Optional[float], fair_value: Optional[float]) -> Optional[float]:
    if price is None or fair_value is None or fair_value <= 0:
        return None
    return 1 - price / fair_value


def compute_price_bands(
    fair_value: Optional[float],
    price: Optional[float],
    risk_score_0_100: Optional[float] = None,
    strong_buy_mos: float = DEFAULT_STRONG_BUY_MOS,
    buy_mos: float = DEFAULT_BUY_MOS,
    overvalued_premium: float = DEFAULT_OVERVALUED_PREMIUM,
) -> PriceBands:
    # AUDIT FIX (StockLab final engineering pass, Part B3 -- docs/AUDIT_FAIR_VALUE_B3.md).
    # `fair_value <= 0` used to fall straight through this guard and be multiplied by the band
    # factors, producing a negative "strong buy price" and a negative "overvalued price" -- a
    # complete price ladder built out of a number that is not a valuation. `compute_margin_of_
    # safety()` immediately below already refused a non-positive fair value; this guard makes the
    # band computation consistent with it instead of the two disagreeing.
    if fair_value is None or fair_value <= 0:
        return PriceBands(None, None, None, None, None, 1.0)
    mult = risk_adjustment_multiplier(risk_score_0_100)
    return PriceBands(
        strong_buy_price=fair_value * (1 - strong_buy_mos * mult),
        buy_price=fair_value * (1 - buy_mos * mult),
        fair_value=fair_value,
        overvalued_price=fair_value * (1 + overvalued_premium * mult),
        margin_of_safety=compute_margin_of_safety(price, fair_value),
        risk_adjustment_multiplier=mult,
    )
