import numpy as np

from m3m_twin.mission import VirtualField, plan_virtual_mission
from m3m_twin.simulator import (
    analyse_virtual_mosaic,
    coverage_grid,
    simulate_capture_qc,
    simulate_dn_and_calibrate,
    virtual_canopy,
)


def _fixture():
    field = VirtualField()
    mission = plan_virtual_mission(field)
    records, summary = simulate_capture_qc(mission["captureEvents"])
    coverage = coverage_grid(
        records,
        field,
        mission["cameraGeometry"]["multispectralFootprintAlongTrackM"],
        mission["cameraGeometry"]["multispectralFootprintCrossTrackM"],
    )
    canopy = virtual_canopy(field)
    raw, calibrated, metadata = simulate_dn_and_calibrate({name: canopy[name] for name in ("green", "red", "red_edge", "nir")})
    arrays, result = analyse_virtual_mosaic(canopy, calibrated, coverage)
    return mission, records, summary, coverage, canopy, raw, calibrated, metadata, arrays, result


def test_synthetic_capture_qc_is_deterministic():
    mission = plan_virtual_mission()
    first = simulate_capture_qc(mission["captureEvents"])
    second = simulate_capture_qc(mission["captureEvents"])
    assert first == second


def test_qc_rejects_injected_faults():
    _, records, summary, *_ = _fixture()
    assert summary["rejectedEventCount"] == 12
    assert any(row["qcFailures"] == "rtk_not_fixed" for row in records)
    assert any("missing_band" in row["qcFailures"] for row in records)


def test_each_capture_lists_expected_files_and_missing_band_is_actually_absent():
    _, records, *_ = _fixture()
    complete = [row for row in records if not row["missingBand"]]
    missing = [row for row in records if row["missingBand"] == "red_edge"]
    assert all(len(row["filenames"].split("|")) == 5 for row in complete)
    assert all(row["fileCountObserved"] == 5 for row in complete)
    assert len(missing) == 2
    assert all(len(row["filenames"].split("|")) == 4 for row in missing)
    assert all(row["fileCountObserved"] == 4 for row in missing)
    assert all("_MS_RE.TIF" not in row["filenames"] for row in missing)
    assert all(row["fileCountExpected"] == 5 for row in missing)


def test_failed_captures_do_not_destroy_coverage():
    *_, coverage, canopy, raw, calibrated, metadata, arrays, result = _fixture()
    assert coverage.min() >= 1
    assert result["coveredAreaFraction"] == 1.0


def test_raw_dn_are_unsigned_sixteen_bit():
    *_, raw, calibrated, metadata, arrays, result = _fixture()
    assert all(value.dtype == np.uint16 for value in raw.values())


def test_calibration_recovers_relative_reflectance():
    _, _, _, _, canopy, _, calibrated, *_ = _fixture()
    for name in ("green", "red", "red_edge", "nir"):
        error = np.mean(np.abs(canopy[name] - calibrated[name]))
        assert error < 0.001


def test_processing_receipt_does_not_claim_dji_terra_equivalence():
    *_, metadata, arrays, result = _fixture()
    assert metadata["notDjiTerraAlgorithm"] is True


def test_indices_stay_within_normalised_difference_bounds():
    *_, arrays, result = _fixture()
    for name in ("ndvi", "ndre", "gndvi"):
        assert np.nanmin(arrays[name]) >= -1
        assert np.nanmax(arrays[name]) <= 1


def test_two_synthetic_review_zones_are_recovered():
    *_, arrays, result = _fixture()
    assert result["reviewZoneCount"] == 2
    assert result["reviewAreaM2"] == int(arrays["review_mask"].sum())
    assert result["reviewAreaM2"] == sum(zone["area_m2"] for zone in result["reviewZones"])
    assert result["syntheticFunctionalTest"]["precision"] == 1.0
    assert result["syntheticFunctionalTest"]["recall"] == 1.0
    assert result["syntheticFunctionalTest"]["intersectionOverUnion"] == 1.0


def test_result_never_claims_field_or_disease_confirmation():
    *_, result = _fixture()
    assert result["fieldFlightExecuted"] is False
    assert result["realMavic3MFilesProcessed"] is False
    assert result["diseaseConfirmed"] is False
    assert result["agronomicCauseAssigned"] is False
    assert result["automationAuthorized"] is False


def test_repeat_run_arrays_are_identical():
    first = _fixture()
    second = _fixture()
    for name in ("ndvi", "ndre", "gndvi", "anomaly_score", "review_mask", "coverage"):
        assert np.array_equal(first[-2][name], second[-2][name], equal_nan=True)
