"""HRP allocation: weights sum to 1, all positive, and a duplicated (perfectly-correlated) pair splits the
weight it would get as a single asset (the diversification property)."""
from __future__ import annotations

import numpy as np

from fxhnt.domain.diversification.hrp import hrp_weights


def _series(rng, n, vol):
    return rng.normal(0.0, vol, n)


def test_weights_sum_to_one_all_positive() -> None:
    rng = np.random.default_rng(0)
    rets = {f"S{i}": _series(rng, 500, 0.01) for i in range(5)}
    w = hrp_weights(rets)
    assert abs(sum(w.values()) - 1.0) < 1e-9
    assert all(v >= 0 for v in w.values())
    assert set(w) == set(rets)


def test_lower_vol_gets_more_weight() -> None:
    rng = np.random.default_rng(1)
    rets = {"LOWVOL": _series(rng, 500, 0.005), "HIGHVOL": _series(rng, 500, 0.02)}
    w = hrp_weights(rets)
    assert w["LOWVOL"] > w["HIGHVOL"]             # risk parity: less vol -> more weight


def test_degenerate_single_asset() -> None:
    rng = np.random.default_rng(2)
    w = hrp_weights({"ONLY": _series(rng, 100, 0.01)})
    assert abs(w["ONLY"] - 1.0) < 1e-9


def test_hrp_zero_vol_asset_falls_back_to_valid() -> None:
    """A constant (zero-variance) series must not produce NaN/negative weights — fallback to uniform."""
    rng = np.random.default_rng(42)
    constant = np.ones(200)                                 # zero vol — 1/0 = inf in _ivp
    normal = _series(rng, 200, 0.01)
    w = hrp_weights({"CONST": constant, "NORM": normal})
    vals = list(w.values())
    assert all(np.isfinite(v) for v in vals), f"non-finite weight(s): {w}"
    assert all(v >= 0 for v in vals), f"negative weight(s): {w}"
    assert abs(sum(vals) - 1.0) < 1e-9, f"weights don't sum to 1: {w}"


def test_correlated_cluster_shares_weight() -> None:
    # two identical assets + one independent: the identical pair together should not dominate
    rng = np.random.default_rng(3)
    base = _series(rng, 800, 0.01)
    indep = _series(rng, 800, 0.01)
    w = hrp_weights({"A": base, "A_DUP": base.copy(), "B": indep})
    assert abs(sum(w.values()) - 1.0) < 1e-9
    assert w["B"] > w["A"]                        # the independent asset gets more than each twin
