"""Normalize the heterogeneous paper-tracker *_state.json files into the common forward read-model.

Three families exist (detected by structure):
  * days-list       — has a list under "days": full per-day [{date,ret,nav}] series (e.g. combined book).
  * welford-equity  — has "equity" + Welford "sum_r"/"sumsq_r": aggregates only (e.g. multistrat, 60/40).
  * cumulative-funding -- has "cum_net" + "positions": cumulative carry only (e.g. funding, crossvenue).

Only the days-list family yields a real per-day history. The others yield one forward point at their as_of
date (nav = current equity); repeated daily ingests build their forward series going forward."""
from __future__ import annotations

import json
import math
from pathlib import Path

from fxhnt.application.forward_models import ForwardNavRow, ForwardSummary

_ANN = math.sqrt(365.0)   # calendar-daily annualization (crypto trades weekends; close enough for futures)


class ForwardStateReader:
    def read(self, path: str | Path, strategy_id: str) -> tuple[ForwardSummary, list[ForwardNavRow]]:
        state = json.loads(Path(path).read_text())
        if isinstance(state.get("days"), list):
            return self._days_list(state, strategy_id)
        if "equity" in state:
            return self._welford(state, strategy_id)
        if "cum_net" in state:
            return self._funding(state, strategy_id)
        raise ValueError(f"unrecognized tracker state shape for {strategy_id}: keys={sorted(state)}")

    def _days_list(self, state: dict, sid: str) -> tuple[ForwardSummary, list[ForwardNavRow]]:
        days = state["days"]
        rows = [ForwardNavRow(strategy_id=sid, date=d["date"], ret=float(d["ret"]), nav=float(d["nav"]))
                for d in days]
        nav = float(state.get("nav", rows[-1].nav if rows else 1.0))
        as_of = state.get("last_date") or (rows[-1].date if rows else "")
        rets = [r.ret for r in rows]
        sharpe = self._sharpe(rets)
        maxdd = self._maxdd([r.nav for r in rows])
        summary = ForwardSummary(strategy_id=sid, as_of=as_of, days=len(rows), nav=nav,
                                 total_return=nav - 1.0, sharpe=sharpe, maxdd=maxdd)
        return summary, rows

    def _welford(self, state: dict, sid: str) -> tuple[ForwardSummary, list[ForwardNavRow]]:
        days = int(state.get("days", 0))
        equity = float(state["equity"])
        as_of = state.get("last_date", "")
        mean = float(state["sum_r"]) / days if days else 0.0
        # sumsq_r is the raw sum of squared returns (not a Welford M2 accumulator); use
        # the two-moment sample-variance formula to match the days-list _sharpe path.
        var = (float(state["sumsq_r"]) - float(state["sum_r"]) ** 2 / days) / (days - 1) if days > 1 else 0.0
        std = math.sqrt(var) if var > 0 else 0.0
        sharpe = (mean / std * _ANN) if std > 0 else 0.0
        summary = ForwardSummary(strategy_id=sid, as_of=as_of, days=days, nav=equity,
                                 total_return=equity - 1.0, sharpe=sharpe,
                                 maxdd=float(state.get("max_dd", 0.0)))
        rows = [ForwardNavRow(strategy_id=sid, date=as_of, ret=0.0, nav=equity)] if as_of else []
        return summary, rows

    def _funding(self, state: dict, sid: str) -> tuple[ForwardSummary, list[ForwardNavRow]]:
        days = int(state.get("days", 0))
        cum_net = float(state["cum_net"])
        as_of = state.get("last_run_date", "")
        nav = 1.0 + cum_net
        summary = ForwardSummary(strategy_id=sid, as_of=as_of, days=days, nav=nav,
                                 total_return=cum_net, sharpe=0.0, maxdd=0.0)
        rows = [ForwardNavRow(strategy_id=sid, date=as_of, ret=0.0, nav=nav)] if as_of else []
        return summary, rows

    @staticmethod
    def _sharpe(rets: list[float]) -> float:
        n = len(rets)
        if n < 2:
            return 0.0
        mean = sum(rets) / n
        var = sum((x - mean) ** 2 for x in rets) / (n - 1)
        std = math.sqrt(var)
        return (mean / std * _ANN) if std > 0 else 0.0

    @staticmethod
    def _maxdd(nav: list[float]) -> float:
        if not nav:
            return 0.0
        peak = nav[0]
        mdd = 0.0
        for v in nav:
            peak = max(peak, v)
            mdd = min(mdd, v / peak - 1.0)
        return mdd
