import pytest
import numpy as np
from weighted_mcc.binary import weighted_mcc
from weighted_mcc.robustness import (
    calculate_binary_stability_bound
)

class TestBinaryStabilityBound:
    def test_calculate_binary_stability_bound_validity(self):
        """Property test: Verify |MCC(S) - MCC(W)| <= Bound."""
        # Setup data
        N = 20
        y_true = np.random.randint(0, 2, N)
        y_pred = np.random.randint(0, 2, N)
        
        # Ensure not all same to avoid 0 variance
        y_true[0], y_true[1] = 0, 1
        y_pred[0], y_pred[1] = 0, 1
        
        # Base weights S
        S = np.random.uniform(1.0, 10.0, N)
        
        # Perturbation epsilon
        epsilon = 0.01
        
        # Perturbed weights W
        noise = np.random.uniform(-epsilon, epsilon, N)
        W = S + noise
        
        # Calculate MCCs
        mcc_s = weighted_mcc(y_true, y_pred, S)
        mcc_w = weighted_mcc(y_true, y_pred, W)
        
        diff = abs(mcc_s - mcc_w)
        
        # Calculate Bound using S
        bound = calculate_binary_stability_bound(y_true, y_pred, S, epsilon)
        
        # If bound is valid (not None due to precondition violations), assert
        if bound is not None:
             assert diff <= bound
        else:
            pytest.skip("Stability bound preconditions not met for generated data")

    def test_calculate_binary_stability_bound_epsilon_constraint(self):
        """Test that function handles large epsilon gracefully (likely returns None or raises)."""
        y_true = np.array([0, 1])
        y_pred = np.array([0, 1])
        S = np.array([1.0, 1.0])
        epsilon = 10.0 # Too large relative to weights
        
        # Assuming implementation returns None or raises error when constraints violated
        result = calculate_binary_stability_bound(y_true, y_pred, S, epsilon)
        assert result is None