import numpy as np
from backend.execution_context import active_node, execution_callbacks
from tests.node_tests._shared import make_field


def test_output_shapes():
    from backend.nodes.logistic_classification import LogisticClassification
    node = LogisticClassification()

    data = np.random.default_rng(0).standard_normal((64, 64))
    field = make_field(data=data)

    previews = []
    with execution_callbacks(preview=lambda nid, uri: previews.append(uri)), active_node("test"):
        mask, prob = node.process(
            field,
            use_gaussians=True,
            n_gaussians=4,
            use_sobel=True,
            use_laplacian=True,
            regularization=1.0,
            max_iter=500,
            seed=42,
        )

    assert mask.shape == field.data.shape
    assert prob.data.shape == field.data.shape


def test_mask_binary():
    from backend.nodes.logistic_classification import LogisticClassification
    node = LogisticClassification()

    data = np.zeros((32, 32))
    data[:, 16:] = 1.0
    field = make_field(data=data)

    with execution_callbacks(preview=lambda nid, uri: None), active_node("test"):
        mask, _ = node.process(
            field,
            use_gaussians=True,
            n_gaussians=2,
            use_sobel=True,
            use_laplacian=True,
            regularization=1.0,
            max_iter=500,
            seed=42,
        )

    unique = set(np.unique(mask))
    assert unique <= {0, 255}, f"Mask contains non-binary values: {unique}"


def test_probability_range():
    from backend.nodes.logistic_classification import LogisticClassification
    node = LogisticClassification()

    data = np.random.default_rng(7).standard_normal((48, 48))
    field = make_field(data=data)

    with execution_callbacks(preview=lambda nid, uri: None), active_node("test"):
        _, prob = node.process(
            field,
            use_gaussians=True,
            n_gaussians=3,
            use_sobel=True,
            use_laplacian=True,
            regularization=1.0,
            max_iter=500,
            seed=42,
        )

    assert prob.data.min() >= 0.0, f"Probability min {prob.data.min()} < 0"
    assert prob.data.max() <= 1.0, f"Probability max {prob.data.max()} > 1"


def test_with_training():
    from backend.nodes.logistic_classification import LogisticClassification
    node = LogisticClassification()

    # Create a field with two distinct regions
    data = np.zeros((64, 64))
    data[:, 32:] = 2.0
    data += np.random.default_rng(1).standard_normal((64, 64)) * 0.1
    field = make_field(data=data)

    # Create a training mask marking the right half as positive
    training_mask = np.zeros((64, 64), dtype=np.uint8)
    training_mask[:, 32:] = 255

    with execution_callbacks(preview=lambda nid, uri: None), active_node("test"):
        mask, prob = node.process(
            field,
            use_gaussians=True,
            n_gaussians=3,
            use_sobel=True,
            use_laplacian=True,
            regularization=1.0,
            max_iter=500,
            seed=42,
            training_mask=training_mask,
        )

    assert mask.dtype == np.uint8
    assert mask.shape == field.data.shape
    # The classifier should learn that the right half is positive
    right_positive = np.count_nonzero(mask[:, 32:] == 255)
    left_positive = np.count_nonzero(mask[:, :32] == 255)
    assert right_positive > left_positive, (
        f"Expected more positives on right ({right_positive}) than left ({left_positive})"
    )
