"""MassiveDailyClient — total-return reconstruction from Massive (Polygon) split-only adjusted closes
+ dividends. NO live network: the `_get` HTTP layer is monkeypatched to return fixed fixtures.

The critical invariant proven here: on an ex-dividend day, the day-over-day return of the returned
total-return series equals the raw PRICE return PLUS dividend/prev_close — i.e. the dividend is added
back, matching Yahoo adjclose semantics. With no dividends, TR closes == split-adjusted closes.
"""
from __future__ import annotations

import datetime as dt
from typing import Any

from fxhnt.adapters.data.massive_daily import MassiveDailyClient


def _ms(date: str) -> int:
    """Midnight-UTC epoch milliseconds for a YYYY-MM-DD date (matches Massive aggregate `t`)."""
    d = dt.datetime.strptime(date, "%Y-%m-%d").replace(tzinfo=dt.timezone.utc)
    return int(d.timestamp() * 1000)


def _aggs(closes: dict[str, float]) -> dict[str, Any]:
    return {"results": [{"t": _ms(d), "c": c} for d, c in sorted(closes.items())]}


def _divs(divs: list[tuple[str, float]]) -> dict[str, Any]:
    return {"results": [{"ex_dividend_date": d, "cash_amount": c} for d, c in divs]}


def _install(client: MassiveDailyClient, closes: dict[str, float], divs: list[tuple[str, float]]) -> None:
    """Route `_get` by endpoint: aggregates vs dividends. No network."""
    def fake_get(url: str, tries: int = 4) -> dict[str, Any]:
        if "/v3/reference/dividends" in url:
            return _divs(divs)
        if "/v2/aggs/ticker/" in url:
            return _aggs(closes)
        raise AssertionError(f"unexpected url: {url}")

    client._get = fake_get  # type: ignore[method-assign]


def test_returns_date_to_price_dict() -> None:
    client = MassiveDailyClient(api_key="x")
    closes = {"2026-01-02": 100.0, "2026-01-03": 101.0, "2026-01-06": 102.0}
    _install(client, closes, divs=[])
    out = client.adj_closes("SPY")
    assert isinstance(out, dict)
    assert set(out) == set(closes)
    assert all(isinstance(k, str) and isinstance(v, float) for k, v in out.items())


def test_no_dividends_returns_split_adjusted_unchanged() -> None:
    client = MassiveDailyClient(api_key="x")
    closes = {"2026-01-02": 100.0, "2026-01-03": 101.0, "2026-01-06": 102.0}
    _install(client, closes, divs=[])
    out = client.adj_closes("SPY")
    for d, c in closes.items():
        assert abs(out[d] - c) < 1e-12


def test_ex_div_day_return_includes_dividend() -> None:
    """The load-bearing test: a single $1.80 dividend ex-date 2026-01-03.

    Raw price return on the ex-div day = c[03]/c[02] - 1.
    Total-return on the ex-div day = tr[03]/tr[02] - 1, with the dividend added back: exactly
    c_D/(prev_close - C) - 1 (canonical), ≈ price_return + dividend/prev_close (first order).
    """
    cash = 1.80
    closes = {"2026-01-02": 100.0, "2026-01-03": 99.0, "2026-01-06": 100.0}
    client = MassiveDailyClient(api_key="x")
    _install(client, closes, divs=[("2026-01-03", cash)])
    out = client.adj_closes("SPY")

    prev_close = closes["2026-01-02"]
    price_return = closes["2026-01-03"] / prev_close - 1.0
    tr_return = out["2026-01-03"] / out["2026-01-02"] - 1.0
    # EXACT canonical (CRSP / Yahoo) back-adjustment identity: prior close scaled by
    # (1 - C/prev_close) ⇒ ex-div-day total return = c_D/(prev_close - C) - 1, the dividend added back.
    exact_tr_return = closes["2026-01-03"] / (prev_close - cash) - 1.0
    assert abs(tr_return - exact_tr_return) < 1e-9
    # First-order (additive) form the spec describes: price return PLUS dividend yield.
    additive_tr_return = price_return + cash / prev_close
    assert abs(tr_return - additive_tr_return) < 1e-3
    # The most recent close is the anchor: factors only scale dates strictly before the ex-div date.
    assert abs(out["2026-01-06"] - closes["2026-01-06"]) < 1e-12
    assert abs(out["2026-01-03"] - closes["2026-01-03"]) < 1e-12
    # Total return on an ex-div day is strictly higher than the raw price return.
    assert tr_return > price_return


def test_multi_dividend_factors_compound() -> None:
    """Two dividends on different ex-dates: back-adjustment factors must COMPOUND.

    Dates strictly before BOTH ex-divs receive both factors multiplied together; a date that lies
    on/after the earlier ex-div but strictly before the later one receives ONLY the later factor; the
    most-recent close (on the later ex-div date) is the anchor and is unscaled. This locks the
    `factor[d] *= f` accumulation in MassiveDailyClient._total_return.
    """
    # Split-adjusted closes on five consecutive trading dates.
    closes = {
        "2026-01-02": 100.0,
        "2026-01-05": 101.0,
        "2026-01-06": 99.0,
        "2026-01-07": 102.0,
        "2026-01-08": 100.0,
    }
    cash1 = 1.50  # ex-date 2026-01-06; trading day before = 2026-01-05 (close 101.0)
    cash2 = 2.00  # ex-date 2026-01-08; trading day before = 2026-01-07 (close 102.0)
    client = MassiveDailyClient(api_key="x")
    _install(client, closes, divs=[("2026-01-06", cash1), ("2026-01-08", cash2)])
    out = client.adj_closes("SPY")

    f1 = 1.0 - cash1 / closes["2026-01-05"]  # = 0.9851485148514851
    f2 = 1.0 - cash2 / closes["2026-01-07"]  # = 0.9803921568627451

    # Strictly before BOTH ex-divs ⇒ BOTH factors compounded.
    assert abs(out["2026-01-02"] - closes["2026-01-02"] * f1 * f2) < 1e-9  # 96.58318773053776
    assert abs(out["2026-01-05"] - closes["2026-01-05"] * f1 * f2) < 1e-9  # 97.54901960784314
    # On/after the earlier ex-div (2026-01-06) but strictly before the later (2026-01-08) ⇒ ONLY f2.
    assert abs(out["2026-01-06"] - closes["2026-01-06"] * f2) < 1e-9  # 97.05882352941175
    assert abs(out["2026-01-07"] - closes["2026-01-07"] * f2) < 1e-9  # 100.0
    # The later ex-div date is the most-recent close: anchor, unscaled.
    assert abs(out["2026-01-08"] - closes["2026-01-08"]) < 1e-9  # 100.0

    # Sanity: the doubly-adjusted date is scaled strictly more than the singly-adjusted one (f1<1).
    assert out["2026-01-05"] / closes["2026-01-05"] < out["2026-01-06"] / closes["2026-01-06"]
