"""Golden tests for the supply calculation core in
backend/services/supply_service.py:

  - `_roll_forward`  : the 13-week stock projection now shared by the supply
                       dashboard, stock projection, and coverage grid.
  - `_classify`      : the absolute-weeks status thresholds used by those views.
  - `_safe_float`    : NaN/None coercion guarding the int(ceil(...)) math.

Importing supply_service pulls in SQLAlchemy (the repo class), but the functions
under test take plain arguments — no DB connection is opened.
"""
import math

import pytest

from backend.services import supply_service as ss


# ------------------------------------------------------------------
# _roll_forward
# ------------------------------------------------------------------

def _cells(weeks):
    """Compact a result list into tuples for easy assertions."""
    return [(c["year"], c["week"], c["opening"], c["demand"], c["incoming"], c["closing"])
            for c in weeks]


def test_roll_forward_floors_closing_at_zero():
    # opening 5, demand 10 (from avg fallback), no incoming → closing floored to 0,
    # not -5.
    weeks = ss._roll_forward(
        pid=1, current_stock=5.0, avg=10.0,
        horizon=[(2026, 1)], fc_map={}, incoming_map={},
    )
    assert _cells(weeks) == [(2026, 1, 5.0, 10.0, 0.0, 0.0)]


def test_roll_forward_uses_forecast_when_present_else_avg():
    weeks = ss._roll_forward(
        pid=1, current_stock=100.0, avg=10.0,
        horizon=[(2026, 1), (2026, 2)],
        fc_map={(1, 2026, 1): 3.0},   # week 1 has a forecast; week 2 falls back to avg
        incoming_map={},
    )
    assert weeks[0]["demand"] == 3.0
    assert weeks[1]["demand"] == 10.0


def test_roll_forward_rolls_opening_into_next_closing_with_incoming():
    weeks = ss._roll_forward(
        pid=1, current_stock=20.0, avg=0.0,
        horizon=[(2026, 1), (2026, 2)],
        fc_map={(1, 2026, 1): 10.0, (1, 2026, 2): 10.0},
        incoming_map={(1, 2026, 2): 50.0},
    )
    assert _cells(weeks) == [
        (2026, 1, 20.0, 10.0, 0.0, 10.0),   # 20 - 10 = 10
        (2026, 2, 10.0, 10.0, 50.0, 50.0),  # 10 - 10 + 50 = 50
    ]


def test_roll_forward_excluded_map_subtracts_and_clamps():
    # raw demand 5, exclusion 999 → demand clamped to 0 (not negative).
    weeks = ss._roll_forward(
        pid=1, current_stock=20.0, avg=0.0,
        horizon=[(2026, 1)],
        fc_map={(1, 2026, 1): 5.0},
        incoming_map={},
        excluded_map={(1, 2026, 1): 999.0},
    )
    assert weeks[0]["demand"] == 0.0
    assert weeks[0]["closing"] == 20.0


def test_roll_forward_without_excluded_map_does_not_subtract():
    weeks = ss._roll_forward(
        pid=1, current_stock=20.0, avg=0.0,
        horizon=[(2026, 1)],
        fc_map={(1, 2026, 1): 5.0},
        incoming_map={},
        excluded_map=None,
    )
    assert weeks[0]["demand"] == 5.0


# ------------------------------------------------------------------
# _classify (absolute-weeks status)
# ------------------------------------------------------------------

@pytest.mark.parametrize("coverage, expected", [
    (None, "OK"),
    (1.99, "Order Now"),    # < 2
    (2.0, "Order Soon"),    # not < 2, but < 4
    (3.5, "Order Soon"),
    (4.0, "OK"),            # not < 4, not > 13
    (13.0, "OK"),           # boundary: not > 13
    (13.5, "Pull in"),      # > 13
])
def test_classify_thresholds(coverage, expected):
    assert ss._classify(coverage) == expected


# ------------------------------------------------------------------
# _safe_float
# ------------------------------------------------------------------

def test_safe_float_passes_through_real_numbers():
    assert ss._safe_float(5) == 5.0
    assert ss._safe_float("3.5") == 3.5


def test_safe_float_returns_none_for_missing_or_bad():
    assert ss._safe_float(None) is None
    assert ss._safe_float("not-a-number") is None
    assert ss._safe_float(float("nan")) is None
