from __future__ import annotations

import numpy as np

import pytest

pytest.importorskip("hypothesis")

from hypothesis import given, settings, strategies as st

from zen_fronts.selection_core import mc_rank_select_detailed


@settings(max_examples=20, deadline=None)
@given(
    N=st.integers(min_value=3, max_value=20),
    M=st.integers(min_value=2, max_value=8),
    seed=st.integers(min_value=0, max_value=10_000),
    percentile=st.floats(min_value=0.05, max_value=0.4, allow_nan=False, allow_infinity=False),
)
def test_quantiles_mode_does_not_change_core_outputs(N: int, M: int, seed: int, percentile: float) -> None:
    """Quantile strategy must not affect: winners/losers, mean/std, or quality.

    Only the median/q25/q75 are allowed to differ between exact and streaming.
    """

    rng = np.random.default_rng(20240101)
    mu = rng.normal(size=(N, M)).astype(np.float64)
    sigma2 = (0.001 + rng.random((N, M))).astype(np.float64)

    n_samples = 35  # >=5 so P² is well-defined

    out_exact = mc_rank_select_detailed(
        mu,
        sigma2,
        n_samples,
        percentile=float(percentile),
        seed=int(seed),
        mode_i=1,
        within_mode_i=0,
        quantiles_mode_i=0,
        quantiles_budget=0,
    )

    out_p2 = mc_rank_select_detailed(
        mu,
        sigma2,
        n_samples,
        percentile=float(percentile),
        seed=int(seed),
        mode_i=1,
        within_mode_i=0,
        quantiles_mode_i=1,
        quantiles_budget=0,
    )

    (w0, l0, wc0, lc0, rs0, place0, front0, within0, q0) = out_exact
    (w1, l1, wc1, lc1, rs1, place1, front1, within1, q1) = out_p2

    # Selection must not change.
    assert np.array_equal(w0, w1)
    assert np.array_equal(l0, l1)
    assert np.array_equal(wc0, wc1)
    assert np.array_equal(lc0, lc1)
    assert np.array_equal(rs0, rs1)

    # Mean/std must not change (quality is computed from means).
    for b0, b1 in ((place0, place1), (front0, front1), (within0, within1)):
        assert np.allclose(b0["mean"], b1["mean"], rtol=0.0, atol=1e-12)
        assert np.allclose(b0["std"], b1["std"], rtol=0.0, atol=1e-12)

    assert np.allclose(q0, q1, rtol=0.0, atol=1e-12)
