"""Tests for the wholesale per-buyer forecaster (tiering, churn, Other bucket)."""
import numpy as np

from forecast_v4.wholesale import (
    forecast_buyer, forecast_wholesale_sku, active_weeks, _croston_rate,
)


def test_regular_buyer_rate_near_mean():
    # Orders ~100 most weeks -> Croston rate ~100/week.
    qty = np.array([100, 110, 90, 105, 95, 100, 108, 92, 100, 104, 96, 100], dtype=float)
    fc = forecast_buyer(qty, h=13)
    assert len(fc) == 13
    assert 80 < fc[0] < 120, fc[0]


def test_churned_buyer_decays():
    # Active early, then 12 zero weeks -> churn decay, forecast well below the
    # historical rate.
    qty = np.array([100, 120, 110, 90, 100, 115, 105, 95] + [0] * 12, dtype=float)
    fc = forecast_buyer(qty, h=13, churn_weeks=8)
    assert fc[0] < 40, f"churned buyer not decayed: {fc[0]:.1f}"


def test_single_order_buyer_is_zero():
    qty = np.zeros(30); qty[10] = 5000
    fc = forecast_buyer(qty, h=13)
    assert np.all(fc == 0), "a single order is a one-off, not a recurring baseline"


def test_sparse_buyer_damped():
    # 3 orders over 30 weeks -> sparse tier, damped run-rate (small positive).
    qty = np.zeros(30); qty[[2, 14, 27]] = [120, 90, 110]
    fc = forecast_buyer(qty, h=13)
    assert 0 < fc[0] < 50, fc[0]


def test_sku_sums_and_other_bucket():
    rng = np.random.default_rng(0)
    buyers = {f"b{i}": np.clip(rng.normal(50, 10, 40), 0, None) for i in range(30)}
    total, per_buyer, excess = forecast_wholesale_sku(buyers, h=13, top_n=25)
    assert len(total) == 13
    # top 25 individually + one Other bucket = 26 forecast series
    assert "__other__" in per_buyer
    assert len(per_buyer) == 26
    # total is the sum of the per-buyer forecasts
    assert np.allclose(total, np.sum(list(per_buyer.values()), axis=0))


def test_one_off_excess_separated():
    # Buyer with steady ~100 and one 5000 bulk -> bulk goes to excess, not baseline.
    q = np.full(20, 100.0); q[10] = 5000.0
    _, per_buyer, excess = forecast_wholesale_sku({"konzum": q}, h=13, top_n=25)
    assert excess["konzum"][10] > 4000
    assert per_buyer["konzum"][0] < 300   # baseline reflects the ~100 rhythm
