"""
Study engine — the single source of truth for every number in the notebooks.

Replace the toy domain below with the study's real model; the *structure*
is the pattern:

- ``ANCHOR_VERBATIM`` — the client/vendor source record, never edited.
- ``ANCHOR_CONTRACTED`` — signed values layered over it; ``anchor()`` reads
  through, so the source anchor survives for as-pitched comparisons.
- ``*_by_year`` schedules keyed by calendar year.
- A baseline-relative case frame (``case_flows`` / ``case_kpis``).

The notebooks hold no math — they call this module and render.
"""

from __future__ import annotations

import math

# ── Timeline ─────────────────────────────────────────────────────────

YEARS = [2026, 2027, 2028]             # model window; contract starts Jan of YEARS[0]
_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]


def month_label(m: int) -> str:
    """Calendar label for a 1-indexed month from Jan of YEARS[0]."""
    return f"{_MONTHS[(m - 1) % 12]} {YEARS[0] + (m - 1) // 12}"


# ── Anchors: verbatim source record + contracted overlay ─────────────

#: The vendor's pitch / the client's source deck — VERBATIM, do not edit.
ANCHOR_VERBATIM: dict[str, float] = {
    "baseline_annual": 1_000_000,   # do-nothing run-rate
    "platform_annual": 600_000,     # pitched platform run-rate
    "services_y1": 250_000,         # pitched one-off services, year 1
    "benefit_3yr": 900_000,         # claimed 3-yr benefit
    "npv_discount_rate": 0.10,
}

#: Signed values where they differ from the pitch — 🟢 contractual.
ANCHOR_CONTRACTED: dict[str, float] = {
    "platform_annual": 500_000,     # signed run-rate (pitch said $600K)
}


def anchor(key: str) -> float:
    """Contracted value where one exists, else the verbatim anchor."""
    return ANCHOR_CONTRACTED.get(key, ANCHOR_VERBATIM[key])


DEFAULT_RAMP_MONTHS = 6                # platform billing starts month 7
DEFAULT_TERMINATION_YEAR = 2027        # existing platform bills through this year
REALIZE_MONTH = 18                     # benefits realize from month 19


# ── Cost & benefit schedules (calendar-year keyed) ───────────────────


def platform_costs_by_year(
    ramp_months: int = DEFAULT_RAMP_MONTHS, annual: float | None = None
) -> dict[int, float]:
    """Ramp programme: billing starts in calendar month ramp_months + 1."""
    rate = ANCHOR_VERBATIM["platform_annual"] if annual is None else annual
    out = {}
    for yi, y in enumerate(YEARS, start=1):
        start, end = 12 * (yi - 1) + 1, 12 * yi
        months = max(0, end - max(start, ramp_months + 1) + 1)
        out[y] = rate * months / 12
    return out


def current_costs_by_year(
    termination_year: int = DEFAULT_TERMINATION_YEAR, annual: float | None = None
) -> dict[int, float]:
    """Existing-platform run-off (the double-billing line)."""
    rate = ANCHOR_VERBATIM["baseline_annual"] if annual is None else annual
    return {y: (rate if y <= termination_year else 0.0) for y in YEARS}


def services_by_year() -> dict[int, float]:
    """One-off services — verbatim, year 1 only."""
    return {y: (ANCHOR_VERBATIM["services_y1"] if y == YEARS[0] else 0.0)
            for y in YEARS}


def benefits_by_year(realize_month: int = REALIZE_MONTH) -> dict[int, float]:
    """Phase the claimed 3-yr benefit across its live months."""
    live = {y: max(0, 12 * yi - max(12 * (yi - 1), realize_month))
            for yi, y in enumerate(YEARS, start=1)}
    total = sum(live.values())
    return {y: anchor("benefit_3yr") * m / total if total else 0.0
            for y, m in live.items()}


# ── Business case (baseline-relative frame) ──────────────────────────


def case_flows(
    total_cost_by_year: dict[int, float],
    benefit_by_year: dict[int, float],
    baseline_annual: float | None = None,
) -> tuple[dict[int, float], dict[int, float]]:
    """(incremental cost, net) vs the do-nothing baseline."""
    base = ANCHOR_VERBATIM["baseline_annual"] if baseline_annual is None \
        else baseline_annual
    inc = {y: total_cost_by_year[y] - base for y in YEARS}
    net = {y: benefit_by_year[y] - inc[y] for y in YEARS}
    return inc, net


def npv(flows: list[float], rate: float) -> float:
    """NPV with the first flow discounted one full year."""
    return sum(v / (1 + rate) ** i for i, v in enumerate(flows, start=1))


def payback_label(net_by_year: dict[int, float]) -> str:
    cum = 0.0
    for i, y in enumerate(YEARS):
        step = net_by_year[y]
        if cum + step >= 0:
            if i == 0 and step >= 0:
                return "immediate"
            frac = (-cum / step) if step > 0 else 0.0
            m = math.ceil((i + frac) * 12)
            return f"{m} months (~{month_label(m)})"
        cum += step
    return f"beyond {YEARS[-1]}"


def case_kpis(
    inc: dict[int, float],
    net: dict[int, float],
    discount_rate: float | None = None,
) -> dict:
    """KPIs for one cost frame. Benefits are recoverable as net + inc."""
    rate = ANCHOR_VERBATIM["npv_discount_rate"] if discount_rate is None \
        else discount_rate
    net_list = [net[y] for y in YEARS]
    inc_total = sum(inc.values())
    net_total = sum(net_list)
    return {
        "benefits_3yr": net_total + inc_total,
        "incremental_cost_3yr": inc_total,
        "net_3yr": net_total,
        "roi": (net_total / inc_total) if inc_total > 0 else None,
        "npv": npv(net_list, rate),
        "discount_rate": rate,
        "payback": payback_label(net),
    }


# ── Display helpers ──────────────────────────────────────────────────


def money(v: float) -> str:
    sign, a = ("-" if v < 0 else ""), abs(v)
    return f"{sign}${a/1e6:,.1f}M" if a >= 1e6 else f"{sign}${a/1e3:,.0f}K"


def html_money(v: float) -> str:
    """Plotly text with two or more bare ``$`` triggers MathJax math mode —
    annotations holding several amounts must use the HTML entity instead."""
    return money(v).replace("$", "&#36;")
