//! Integration tests for monge-rs — end-to-end verification of
//! Monge's theorem, the zero-holonomy consensus protocol, and
//! Pythagorean48 consistency.

use nalgebra::{Vector2, Vector3, Vector4};
use monge_rs::geometry::Circle;
use monge_rs::homothetic::{
    external_homothetic_center, external_triple, internal_homothetic_center,
    monge_collinear_area, triangle_area,
};
use monge_rs::radical_axis::RadicalAxis;
use monge_rs::consensus::{zero_holonomy_area, Agent, simulate_consensus, consensus_step};
use monge_rs::p48::{verify_all_pairs, verify_zero_drift, all_48_directions, int_det};

// ---------------------------------------------------------------------------
// Monge's theorem — core verification
// ---------------------------------------------------------------------------

#[test]
fn test_monge_classic_configuration() {
    // The classic three-circle configuration for Monge
    let circles = [
        Circle::new(Vector2::new(0.0, 0.0), 1.0),
        Circle::new(Vector2::new(5.0, 0.0), 2.0),
        Circle::new(Vector2::new(2.0, 4.0), 1.5),
    ];

    let (s12, s23, s31) = external_triple(&circles[0], &circles[1], &circles[2])
        .expect("All radii distinct");

    // These three points must be collinear
    let area = triangle_area(&s12, &s23, &s31);
    assert!(
        area < 1e-10,
        "Monge collinearity failed: area = {}",
        area
    );
}

#[test]
fn test_monge_internal_external_relationship() {
    // Internal and external centers for each pair lie on a common line
    let circles = [
        Circle::new(Vector2::new(0.0, 0.0), 1.0),
        Circle::new(Vector2::new(5.0, 0.0), 2.0),
        Circle::new(Vector2::new(2.0, 4.0), 1.5),
    ];

    // For each pair, the internal center, external center, and both
    // circle centers are collinear
    for (i, j) in &[(0, 1), (1, 2), (2, 0)] {
        let c1 = &circles[*i];
        let c2 = &circles[*j];
        let ext = external_homothetic_center(c1, c2).unwrap();
        let int = internal_homothetic_center(c1, c2);

        // Both centers should be collinear with the circle centers
        let area = triangle_area(&c1.center, &ext, &int);
        assert!(
            area < 1e-10,
            "Internal/external centers not collinear with circle centers: area = {}",
            area
        );
    }
}

#[test]
fn test_collinear_area_monte_carlo() {
    // Test 10 random circle configurations
    for _ in 0..10 {
        let c1 = Circle::new(
            Vector2::new(
                rand::random::<f64>() * 10.0,
                rand::random::<f64>() * 10.0,
            ),
            0.5 + rand::random::<f64>() * 2.0,
        );
        let c2 = Circle::new(
            Vector2::new(
                rand::random::<f64>() * 10.0,
                rand::random::<f64>() * 10.0,
            ),
            0.5 + rand::random::<f64>() * 2.0,
        );
        let c3 = Circle::new(
            Vector2::new(
                rand::random::<f64>() * 10.0,
                rand::random::<f64>() * 10.0,
            ),
            0.5 + rand::random::<f64>() * 2.0,
        );

        // Skip if any pair has near-equal radii (risk of infinity)
        let eps = 1e-6;
        let d12 = (c1.radius - c2.radius).abs();
        let d23 = (c2.radius - c3.radius).abs();
        let d31 = (c3.radius - c1.radius).abs();
        if d12 < eps || d23 < eps || d31 < eps {
            continue;
        }

        let area = monge_collinear_area(&c1, &c2, &c3).unwrap();
        assert!(
            area < 1e-6,
            "Monge collinearity failed for random configuration: area = {}",
            area
        );
    }
}

// ---------------------------------------------------------------------------
// Zero-holonomy consensus — protocol tests
// ---------------------------------------------------------------------------

#[test]
fn test_holonomy_monge_consistency() {
    // Zero-holonomy should equal Monge triangle area
    let agents = [
        Agent::new(Vector2::new(0.0, 0.0), 1.0),
        Agent::new(Vector2::new(4.0, 0.0), 2.0),
        Agent::new(Vector2::new(2.0, 3.0), 1.5),
    ];

    let circles = [
        Circle::new(Vector2::new(0.0, 0.0), 1.0),
        Circle::new(Vector2::new(4.0, 0.0), 2.0),
        Circle::new(Vector2::new(2.0, 3.0), 1.5),
    ];

    let holonomy = zero_holonomy_area(&agents).unwrap();
    let monge_area = monge_collinear_area(&circles[0], &circles[1], &circles[2]).unwrap();

    assert!(
        (holonomy - monge_area).abs() < 1e-15,
        "Zero-holonomy should match Monge area: holonomy={}, monge={}",
        holonomy,
        monge_area
    );
}

#[test]
fn test_consensus_short_run() {
    let agents = [
        Agent::new(Vector2::new(0.0, 0.0), 1.0),
        Agent::new(Vector2::new(4.0, 0.0), 2.0),
        Agent::new(Vector2::new(2.0, 3.0), 1.5),
    ];

    let holonomies = simulate_consensus(&agents, 10, 0.1);
    assert_eq!(holonomies.len(), 10);

    // Monotonic non-increasing
    for w in holonomies.windows(2) {
        assert!(w[1] <= w[0] + 1e-12, "Holonomy should be non-increasing");
    }
}

#[test]
fn test_equal_radii_agent_consensus() {
    // All agents with equal trust radii
    let agents = [
        Agent::new(Vector2::new(0.0, 0.0), 1.0),
        Agent::new(Vector2::new(3.0, 0.0), 1.0),
        Agent::new(Vector2::new(0.0, 4.0), 1.0),
    ];

    // Equal radii means external centers are at infinity,
    // but the protocol should still handle it gracefully
    let (new_positions, holonomy_opt) = consensus_step(&agents, 0.1);
    assert!(holonomy_opt.unwrap_or(0.0) >= 0.0);

    // Positions should have moved
    for i in 0..3 {
        let diff = (new_positions[i] - agents[i].position).norm();
        assert!(diff > 0.0, "Agent {} should move during consensus step", i);
    }
}

// ---------------------------------------------------------------------------
// Radical axis — 1-cocycle verification
// ---------------------------------------------------------------------------

#[test]
fn test_cocycle_preserved_under_translation() {
    // Cocycle condition should be preserved under rigid translation
    let c1 = Circle::new(Vector2::new(0.0, 0.0), 1.0);
    let c2 = Circle::new(Vector2::new(4.0, 0.0), 2.0);
    let c3 = Circle::new(Vector2::new(2.0, 3.0), 1.5);

    let offset = Vector2::new(10.0, 20.0);
    let c1t = Circle::new(c1.center + offset, c1.radius);
    let c2t = Circle::new(c2.center + offset, c2.radius);
    let c3t = Circle::new(c3.center + offset, c3.radius);

    let pt = Vector2::new(5.0, 7.0);
    assert!(
        RadicalAxis::check_cocycle(&c1, &c2, &c3, &pt),
        "Cocycle should hold for original"
    );
    assert!(
        RadicalAxis::check_cocycle(&c1t, &c2t, &c3t, &(pt + offset)),
        "Cocycle should hold after translation"
    );
}

#[test]
fn test_radical_axis_intersection() {
    // Three circles should have a unique radical center
    let c1 = Circle::new(Vector2::new(0.0, 0.0), 1.0);
    let c2 = Circle::new(Vector2::new(4.0, 0.0), 2.0);
    let c3 = Circle::new(Vector2::new(2.0, 3.0), 1.5);

    let r12 = RadicalAxis::new(&c1, &c2);
    let r23 = RadicalAxis::new(&c2, &c3);
    let r31 = RadicalAxis::new(&c3, &c1);

    // r12 intersect r23
    let p = r12.intersect(&r23).unwrap();
    assert!(r12.contains(&p, 1e-8));
    assert!(r23.contains(&p, 1e-8));
    assert!(r31.contains(&p, 1e-8));
}

// ---------------------------------------------------------------------------
// Pythagorean48 — lattice verification
// ---------------------------------------------------------------------------

#[test]
fn test_p48_all_directions_unique() {
    let dirs = all_48_directions();
    let mut sorted = dirs.clone();
    sorted.sort_by(|a, b| a.x.cmp(&b.x).then(a.y.cmp(&b.y)));
    sorted.dedup();
    assert_eq!(sorted.len(), 48, "All 48 directions should be unique");
}

#[test]
fn test_p48_pair_verification() {
    let (total, _collinear, _noncollinear) = verify_all_pairs();
    assert_eq!(total, 1128, "48 choose 2 = 1128");
}

#[test]
fn test_p48_zero_drift_aggregate() {
    assert!(
        verify_zero_drift(),
        "Zero drift condition must hold for all direction triples"
    );
}

#[test]
fn test_p48_det_antisymmetry_full() {
    let dirs = all_48_directions();
    for i in 0..dirs.len() {
        for j in 0..dirs.len() {
            assert_eq!(
                int_det(&dirs[i], &dirs[j]),
                -int_det(&dirs[j], &dirs[i]),
                "Antisymmetry failed for ({}, {})",
                i,
                j
            );
        }
    }
}

// ---------------------------------------------------------------------------
// Edge cases and invariants
// ---------------------------------------------------------------------------

#[test]
fn test_negative_radius_panics() {
    // Circle with negative radius should panic
    use std::panic::catch_unwind;
    let result = catch_unwind(|| {
        Circle::new(Vector2::new(0.0, 0.0), -1.0);
    });
    assert!(result.is_err(), "Negative radius should panic");
}

#[test]
fn test_zero_radius_circle() {
    // Zero-radius is degenerate but valid (a point)
    let c = Circle::new(Vector2::new(0.0, 0.0), 0.0);
    assert!((c.radius).abs() < 1e-12);
}

#[test]
fn test_zero_trust_agent_panics() {
    use std::panic::catch_unwind;
    let result = catch_unwind(|| {
        Agent::new(Vector2::new(0.0, 0.0), 0.0);
    });
    assert!(result.is_err(), "Zero trust radius should panic");
}

#[test]
fn test_dimension_generic_sphere() {
    use monge_rs::geometry::Sphere;
    // Test 2D, 3D spheres
    let s2: Sphere<2> = Sphere::new(Vector2::new(1.0, 2.0), 3.0);
    assert!((s2.radius - 3.0).abs() < 1e-12);
    assert!((s2.center[0] - 1.0).abs() < 1e-12);

    let s3: Sphere<3> = Sphere::new(Vector3::new(1.0, 2.0, 3.0), 3.0);
    assert!((s3.radius - 3.0).abs() < 1e-12);

    let _s4: Sphere<4> = Sphere::new(Vector4::new(1.0, 2.0, 3.0, 4.0), 3.0);
}
