use asteroidsfactory::types::*;

#[test]
fn near_earth_apollo_basic() {
    let a = near_earth::NearEarthAsteroid::new(500.0, 2500.0, 1.5, 0.3);
    assert!(a.mass > 0.0);
    assert!(a.surface_gravity() > 0.0);
    assert!(a.escape_velocity() > 0.0);
    assert!(a.perihelion_au() < a.semi_major_axis);
    assert!(a.aphelion_au() > a.semi_major_axis);
}

#[test]
fn near_earth_group_classification() {
    let apollo = near_earth::NearEarthAsteroid::new(100.0, 3000.0, 1.5, 0.6);
    assert!(apollo.perihelion_au() < 1.017);
    assert!(apollo.semi_major_axis >= 1.0);

    let amor = near_earth::NearEarthAsteroid::new(100.0, 3000.0, 1.2, 0.1);
    assert!(amor.perihelion_au() > 1.017);
}

#[test]
fn near_earth_tisserand() {
    let a = near_earth::NearEarthAsteroid::new(200.0, 2500.0, 2.0, 0.5);
    let tj = a.tisserand_jupiter();
    assert!(tj > 0.0);
    assert!(tj < 6.0);
}

#[test]
fn near_earth_velocity_at_perihelion_greater_than_aphelion() {
    let a = near_earth::NearEarthAsteroid::new(300.0, 2500.0, 1.5, 0.4);
    assert!(a.velocity_at_perihelion() > a.velocity_at_aphelion());
}

#[test]
fn near_earth_equilibrium_temperature() {
    let a = near_earth::NearEarthAsteroid::new(200.0, 2500.0, 1.0, 0.1).with_albedo(0.15);
    let t = a.equilibrium_temp_perihelion();
    assert!(t > 200.0);
    assert!(t < 400.0);
}

#[test]
fn near_earth_absolute_magnitude() {
    let a = near_earth::NearEarthAsteroid::new(500.0, 2500.0, 1.5, 0.3).with_albedo(0.15);
    let h = a.absolute_magnitude();
    assert!(h > 10.0);
    assert!(h < 30.0);
}

#[test]
fn main_belt_basic() {
    let a = main_belt::MainBeltAsteroid::new(50000.0, 2700.0, 2.7);
    assert!(a.mass > 0.0);
    assert!(a.surface_gravity() > 0.0);
    assert!(a.orbital_period() > 0.0);
}

#[test]
fn main_belt_kirkwood_gap() {
    let in_gap = main_belt::MainBeltAsteroid::new(1000.0, 2700.0, 2.502);
    assert!(in_gap.is_in_kirkwood_gap());

    let not_gap = main_belt::MainBeltAsteroid::new(1000.0, 2700.0, 2.7);
    assert!(!not_gap.is_in_kirkwood_gap());
}

#[test]
fn main_belt_spectral_type() {
    let a = main_belt::MainBeltAsteroid::new(10000.0, 2700.0, 2.7)
        .with_spectral_type(main_belt::SpectralType::M);
    assert!(matches!(a.spectral_type, main_belt::SpectralType::M));
}

#[test]
fn main_belt_centrifugal_limit() {
    let a = main_belt::MainBeltAsteroid::new(5000.0, 2700.0, 2.7);
    let min_density = a.centrifugal_limit_density();
    assert!(min_density > 0.0);
}

#[test]
fn main_belt_hill_sphere() {
    let a = main_belt::MainBeltAsteroid::new(50000.0, 3000.0, 2.5);
    let rh = a.hill_sphere_radius();
    assert!(rh > 0.0);
    assert!(rh > a.radius);
}

#[test]
fn trojan_l4_basic() {
    let t = trojan::Trojan::new(20000.0, 2000.0, 5.2).with_lagrange_point(trojan::TrojanPoint::L4);
    assert!(t.mass > 0.0);
    let angle = t.lagrange_angle();
    assert!((angle - std::f64::consts::PI / 3.0).abs() < 0.01);
}

#[test]
fn trojan_l5_angle() {
    let t = trojan::Trojan::new(10000.0, 2000.0, 5.2).with_lagrange_point(trojan::TrojanPoint::L5);
    let angle = t.lagrange_angle();
    assert!((angle - (-std::f64::consts::PI / 3.0)).abs() < 0.01);
}

#[test]
fn trojan_libration() {
    let t = trojan::Trojan::new(15000.0, 2000.0, 5.2).with_libration_amplitude(15.0);
    let period = t.libration_period();
    assert!(period > 0.0);
}

#[test]
fn trojan_stability() {
    use asteroidsfactory::config::parameters::SOLAR_MASS;
    let t = trojan::Trojan::new(10000.0, 2000.0, 5.2)
        .with_eccentricity(0.05)
        .with_inclination(10.0);
    let s = t.stability_parameter(SOLAR_MASS);
    assert!(s > 0.0);
}

#[test]
fn centaur_basic() {
    let c = centaur::Centaur::new(50000.0, 1500.0, 13.0, 0.3);
    assert!(c.mass > 0.0);
    assert!(c.surface_gravity() > 0.0);
    assert!(c.orbital_period() > 0.0);
}

#[test]
fn centaur_tisserand_values() {
    let c = centaur::Centaur::new(30000.0, 1500.0, 13.0, 0.3);
    let tj = c.tisserand_jupiter();
    let tn = c.tisserand_neptune();
    assert!(tj > 0.0);
    assert!(tn > 0.0);
}

#[test]
fn centaur_dynamical_lifetime() {
    let c = centaur::Centaur::new(50000.0, 1500.0, 15.0, 0.4);
    let lt = c.dynamical_lifetime_estimate();
    assert!(lt > 0.0);
}

#[test]
fn centaur_cometary_activity() {
    let inside = centaur::Centaur::new(50000.0, 1500.0, 5.0, 0.5);
    assert!(inside.is_inside_snow_line());

    let outside = centaur::Centaur::new(50000.0, 1500.0, 20.0, 0.1);
    assert!(!outside.is_inside_snow_line());
}

#[test]
fn binary_basic() {
    let b = binary::BinaryAsteroid::new(5000.0, 1000.0, 2000.0, 20000.0);
    assert!(b.mutual_orbital_period() > 0.0);
    assert!(b.mutual_orbital_velocity_primary() > 0.0);
}

#[test]
fn binary_stability() {
    let stable = binary::BinaryAsteroid::new(5000.0, 2000.0, 2000.0, 50000.0)
        .with_heliocentric_orbit(2.5, 0.1);
    assert!(stable.is_stable());
}

#[test]
fn binary_contact() {
    let separated = binary::BinaryAsteroid::new(5000.0, 3000.0, 2000.0, 50000.0);
    assert!(!separated.is_contact_binary());

    let close = binary::BinaryAsteroid::new(5000.0, 3000.0, 2000.0, 7500.0);
    assert!(!close.is_contact_binary());
}

#[test]
fn binary_roche_limit() {
    let b = binary::BinaryAsteroid::new(5000.0, 3000.0, 2000.0, 30000.0);
    let roche = b.roche_limit();
    assert!(roche > 0.0);
}

#[test]
fn rubble_pile_basic() {
    let r = rubble_pile::RubblePile::new(500.0, 3000.0, 0.4);
    assert!(r.mass > 0.0);
    assert!(r.bulk_density < r.grain_density);
}

#[test]
fn rubble_pile_spin_barrier() {
    let r = rubble_pile::RubblePile::new(500.0, 3000.0, 0.4);
    let limit = r.spin_limit_period();
    assert!(limit > 0.0);
}

#[test]
fn rubble_pile_spin_stability() {
    let slow = rubble_pile::RubblePile::new(500.0, 3000.0, 0.3).with_rotation_period(20000.0);
    assert!(slow.is_spin_stable());

    let fast = rubble_pile::RubblePile::new(500.0, 3000.0, 0.3).with_rotation_period(1.0);
    assert!(!fast.is_spin_stable());
}

#[test]
fn rubble_pile_porosity_estimate() {
    let r = rubble_pile::RubblePile::new(10000.0, 3000.0, 0.5);
    let p = r.macro_porosity_estimate();
    assert!(p > 0.0);
    assert!(p <= 1.0);
}

#[test]
fn rubble_pile_yorp() {
    let r = rubble_pile::RubblePile::new(500.0, 3000.0, 0.4).with_orbit(2.5, 0.1);
    let t = r.yorp_timescale(2.5);
    assert!(t > 0.0);
}

#[test]
fn metallic_basic() {
    let m = metallic::MetallicAsteroid::new(50000.0, 0.85);
    assert!(m.mass > 0.0);
    assert!(m.surface_gravity() > 0.0);
    assert!(m.iron_fraction > 0.0);
}

#[test]
fn metallic_composition() {
    let m = metallic::MetallicAsteroid::new(10000.0, 0.9).with_nickel_fraction(0.08);
    let content = m.metal_content_mass();
    assert!(content > 0.0);
    assert!(content < m.mass);
    let sil = m.silicate_fraction();
    assert!(sil >= 0.0);
    assert!(sil < 1.0);
}

#[test]
fn metallic_differentiation() {
    let large = metallic::MetallicAsteroid::new(100000.0, 0.9).with_orbit(2.7, 0.1);
    let small = metallic::MetallicAsteroid::new(100.0, 0.5).with_orbit(2.7, 0.1);
    assert!(large.is_differentiated());
    assert!(!small.is_differentiated());
}

#[test]
fn metallic_core_radius() {
    let m = metallic::MetallicAsteroid::new(50000.0, 0.9);
    let core = m.core_radius_estimate();
    assert!(core > 0.0);
    assert!(core <= m.radius);
}

#[test]
fn metallic_thermal_conductivity() {
    let m = metallic::MetallicAsteroid::new(10000.0, 0.85);
    let k = m.thermal_conductivity_estimate();
    assert!(k > 0.0);
}

#[test]
fn pha_basic() {
    let p = pha::PotentiallyHazardousAsteroid::new(100.0, 3000.0, 1.2, 0.5, 0.03);
    assert!(p.mass > 0.0);
    assert!(p.surface_gravity() > 0.0);
}

#[test]
fn pha_classification() {
    let is_pha =
        pha::PotentiallyHazardousAsteroid::new(100.0, 3000.0, 1.5, 0.5, 0.03).with_albedo(0.15);
    assert!(is_pha.is_pha());

    let not_pha =
        pha::PotentiallyHazardousAsteroid::new(1.0, 3000.0, 3.0, 0.1, 0.1).with_albedo(0.15);
    assert!(!not_pha.is_pha());
}

#[test]
fn pha_impact_energy() {
    let p = pha::PotentiallyHazardousAsteroid::new(500.0, 3000.0, 1.2, 0.4, 0.02);
    let j = p.impact_energy_joules();
    let mt = p.impact_energy_megatons();
    assert!(j > 0.0);
    assert!(mt > 0.0);
}

#[test]
fn pha_torino_scale() {
    let p = pha::PotentiallyHazardousAsteroid::new(5000.0, 3000.0, 1.1, 0.5, 0.01);
    let scale = p.torino_scale_estimate();
    assert!(scale <= 10);
}

#[test]
fn pha_crater_diameter() {
    let p = pha::PotentiallyHazardousAsteroid::new(500.0, 3000.0, 1.2, 0.5, 0.02);
    let crater = p.crater_diameter_estimate();
    assert!(crater > 0.0);
}

#[test]
fn pha_deflection() {
    let p = pha::PotentiallyHazardousAsteroid::new(200.0, 3000.0, 1.3, 0.4, 0.03);
    let dv = p.deflection_delta_v(10.0);
    assert!(dv > 0.0);
}

#[test]
fn generated_asteroid_enum() {
    let nea = near_earth::NearEarthAsteroid::new(100.0, 2500.0, 1.5, 0.4);
    let generated = GeneratedAsteroid::NearEarth(nea);
    match generated {
        GeneratedAsteroid::NearEarth(_) => {}
        _ => panic!("wrong variant"),
    }
}

#[test]
fn generated_asteroid_all_variants() {
    let variants = vec![
        GeneratedAsteroid::NearEarth(near_earth::NearEarthAsteroid::new(100.0, 2500.0, 1.5, 0.4)),
        GeneratedAsteroid::MainBelt(main_belt::MainBeltAsteroid::new(5000.0, 2700.0, 2.7)),
        GeneratedAsteroid::Trojan(trojan::Trojan::new(10000.0, 2000.0, 5.2)),
        GeneratedAsteroid::Centaur(centaur::Centaur::new(30000.0, 1500.0, 13.0, 0.3)),
        GeneratedAsteroid::Binary(binary::BinaryAsteroid::new(5000.0, 2000.0, 2000.0, 30000.0)),
        GeneratedAsteroid::RubblePile(rubble_pile::RubblePile::new(500.0, 3000.0, 0.4)),
        GeneratedAsteroid::Metallic(metallic::MetallicAsteroid::new(50000.0, 0.85)),
        GeneratedAsteroid::PHA(pha::PotentiallyHazardousAsteroid::new(
            100.0, 3000.0, 1.2, 0.5, 0.03,
        )),
    ];
    assert_eq!(variants.len(), 8);
    let mut count = 0;
    for v in &variants {
        match v {
            GeneratedAsteroid::NearEarth(_) => count += 1,
            GeneratedAsteroid::MainBelt(_) => count += 1,
            GeneratedAsteroid::Trojan(_) => count += 1,
            GeneratedAsteroid::Centaur(_) => count += 1,
            GeneratedAsteroid::Binary(_) => count += 1,
            GeneratedAsteroid::RubblePile(_) => count += 1,
            GeneratedAsteroid::Metallic(_) => count += 1,
            GeneratedAsteroid::PHA(_) => count += 1,
        }
    }
    assert_eq!(count, 8);
}
