import numpy as np
import pytest

from m3m_twin.spectral import (
    compute_indices,
    connected_zones,
    filter_connected_zones,
    normalised_difference,
    robust_low_score,
)


def test_normalised_difference_equation():
    high = np.array([[0.8]], dtype=np.float32)
    low = np.array([[0.2]], dtype=np.float32)
    assert normalised_difference(high, low)[0, 0] == pytest.approx(0.6)


def test_zero_denominator_becomes_nan():
    result = normalised_difference(np.zeros((1, 1), dtype=np.float32), np.zeros((1, 1), dtype=np.float32))
    assert np.isnan(result[0, 0])


def test_missing_band_is_rejected():
    bands = {name: np.ones((10, 10), dtype=np.float32) for name in ("green", "red", "nir")}
    with pytest.raises(ValueError):
        compute_indices(bands)


def test_misaligned_band_is_rejected():
    bands = {name: np.ones((10, 10), dtype=np.float32) for name in ("green", "red", "red_edge", "nir")}
    bands["nir"] = np.ones((9, 10), dtype=np.float32)
    with pytest.raises(ValueError):
        compute_indices(bands)


def test_robust_score_requires_enough_valid_pixels():
    indices = {name: np.ones((9, 9), dtype=np.float32) for name in ("ndvi", "ndre", "gndvi")}
    with pytest.raises(ValueError):
        robust_low_score(indices, np.ones((9, 9), dtype=bool))


def test_small_connected_regions_are_removed():
    mask = np.zeros((30, 30), dtype=bool)
    score = np.zeros((30, 30), dtype=np.float32)
    mask[2:5, 2:5] = True
    mask[10:25, 10:25] = True
    score[mask] = 0.8
    zones = connected_zones(mask, score, minimum_area_m2=100)
    assert len(zones) == 1
    assert zones[0].area_m2 == 225


def test_filtered_mask_contains_exactly_the_reported_zones():
    mask = np.zeros((30, 30), dtype=bool)
    score = np.zeros((30, 30), dtype=np.float32)
    mask[2:5, 2:5] = True
    mask[10:25, 10:25] = True
    score[mask] = 0.8
    retained, zones = filter_connected_zones(mask, score, minimum_area_m2=100)
    assert retained.sum() == 225
    assert retained[10:25, 10:25].all()
    assert not retained[2:5, 2:5].any()
    assert retained.sum() == sum(zone.area_m2 for zone in zones)
