use neptunes::terrain::heightmap::*;
use neptunes::terrain::lod::*;
use neptunes::terrain::mesh::*;
use neptunes::terrain::texturing::*;

// ── Heightmap ────────────────────────────────────────────────────────────

#[test]
fn neptune_cloud_top_elevation() {
    let e = neptunecloudtop(0.0, 0.0);
    assert!(e.is_finite());
}

#[test]
fn neptune_cloud_top_at_pole() {
    let e = neptunecloudtop(90.0, 0.0);
    assert!(e.is_finite());
}

#[test]
fn heightmap_new() {
    let hm = Heightmap::new(64);
    assert_eq!(hm.resolution, 64);
    assert!(!hm.data.is_empty());
}

#[test]
fn heightmap_sample() {
    let mut hm = Heightmap::new(32);
    hm.generateprocedural(42, 4, 0.5, 2.0);
    let s = hm.sample(0.0, 0.0);
    assert!(s.is_finite());
}

#[test]
fn heightmap_radius_at() {
    let mut hm = Heightmap::new(32);
    hm.generateprocedural(42, 4, 0.5, 2.0);
    let r = hm.radiusat(0.0, 0.0);
    assert!(r.is_finite());
    assert!(r > 0.0);
}

#[test]
fn heightmap_generate_neptune_cloud_topography() {
    let mut hm = Heightmap::new(32);
    hm.generateneptunecloudtopography();
    let s = hm.sample(45.0, 90.0);
    assert!(s.is_finite());
}

#[test]
fn heightmap_procedural_different_seeds() {
    let mut hm1 = Heightmap::new(16);
    let mut hm2 = Heightmap::new(16);
    hm1.generateprocedural(1, 4, 0.5, 2.0);
    hm2.generateprocedural(999, 4, 0.5, 2.0);
    let s1 = hm1.sample(10.0, 20.0);
    let s2 = hm2.sample(10.0, 20.0);
    assert!(s1.is_finite() && s2.is_finite());
}

#[test]
fn heightmap_min_max_elevation() {
    let mut hm = Heightmap::new(32);
    hm.generateneptunecloudtopography();
    assert!(hm.maxelevationm >= hm.minelevationm);
}

// ── LOD ──────────────────────────────────────────────────────────────────

#[test]
fn face_variants() {
    assert!(matches!(Face::PosX, Face::PosX));
    assert!(matches!(Face::NegX, Face::NegX));
    assert!(matches!(Face::PosY, Face::PosY));
    assert!(matches!(Face::NegY, Face::NegY));
    assert!(matches!(Face::PosZ, Face::PosZ));
    assert!(matches!(Face::NegZ, Face::NegZ));
}

#[test]
fn quadtree_node_new() {
    let node = QuadTreeNode::new(Face::PosX, 0, 0, 0);
    assert_eq!(node.face, Face::PosX);
    assert_eq!(node.level, 0);
    assert!(node.isleaf());
}

#[test]
fn quadtree_node_split() {
    let mut node = QuadTreeNode::new(Face::PosZ, 0, 0, 0);
    assert!(node.isleaf());
    node.split();
    assert!(!node.isleaf());
}

#[test]
fn quadtree_should_split() {
    let node = QuadTreeNode::new(Face::PosX, 0, 0, 0);
    let config = LodConfig::default();
    // level 0 → nodesize=1.0, 100.0 is far → no split
    assert!(!node.shouldsplit(100.0, &config));
}

#[test]
fn lod_config_default() {
    let config = LodConfig::default();
    assert!(config.maxlevel > 0);
    assert!(config.splitdistancefactor > 0.0);
    assert!(config.baseresolution > 0);
}

#[test]
fn lod_terrain_new() {
    let config = LodConfig::default();
    let terrain = LodTerrain::new(config);
    assert_eq!(terrain.roots.len(), 6, "6 cube faces");
}

#[test]
fn lod_terrain_update() {
    let config = LodConfig::default();
    let mut terrain = LodTerrain::new(config);
    terrain.update([0.0, 0.0, 30_000_000.0]);
}

// ── Mesh ─────────────────────────────────────────────────────────────────

#[test]
fn terrain_mesh_from_region() {
    let mesh = TerrainMesh::fromregion(-10.0, 10.0, -10.0, 10.0, 4, &|lat, lon| {
        neptunecloudtop(lat, lon)
    });
    assert!(mesh.vertexcount() > 0);
    assert!(mesh.trianglecount() > 0);
}

#[test]
fn terrain_mesh_indices_valid() {
    let mesh = TerrainMesh::fromregion(0.0, 5.0, 0.0, 5.0, 2, &|_, _| 0.0);
    for &idx in &mesh.indices {
        assert!((idx as usize) < mesh.vertexcount());
    }
}

#[test]
fn terrain_mesh_vertex_positions_finite() {
    let mesh = TerrainMesh::fromregion(-5.0, 5.0, -5.0, 5.0, 3, &|_, _| 100.0);
    for v in &mesh.vertices {
        assert!(v.position[0].is_finite());
        assert!(v.position[1].is_finite());
        assert!(v.position[2].is_finite());
    }
}

#[test]
fn terrain_mesh_normals_finite() {
    let mesh = TerrainMesh::fromregion(-5.0, 5.0, -5.0, 5.0, 3, &|_, _| 0.0);
    for v in &mesh.vertices {
        assert!(v.normal[0].is_finite());
        assert!(v.normal[1].is_finite());
        assert!(v.normal[2].is_finite());
    }
}

// ── Texturing ────────────────────────────────────────────────────────────

#[test]
fn cloud_biome_variants() {
    assert!(matches!(CloudBiome::UpperHaze, CloudBiome::UpperHaze));
    assert!(matches!(
        CloudBiome::MethaneIceCloud,
        CloudBiome::MethaneIceCloud
    ));
    assert!(matches!(CloudBiome::H2SCloud, CloudBiome::H2SCloud));
    assert!(matches!(CloudBiome::DeepCloud, CloudBiome::DeepCloud));
    assert!(matches!(CloudBiome::ClearBand, CloudBiome::ClearBand));
    assert!(matches!(CloudBiome::StormRegion, CloudBiome::StormRegion));
    assert!(matches!(CloudBiome::PolarVortex, CloudBiome::PolarVortex));
}

#[test]
fn cloud_biome_classifier_default() {
    let cbc = CloudBiomeClassifier::default();
    assert!(cbc.onebarlevel.is_finite());
}

#[test]
fn cloud_biome_classifier_classify() {
    let cbc = CloudBiomeClassifier::default();
    let biome = cbc.classify(50000.0, 0.0, 200.0);
    assert!(matches!(
        biome,
        CloudBiome::UpperHaze
            | CloudBiome::MethaneIceCloud
            | CloudBiome::H2SCloud
            | CloudBiome::DeepCloud
            | CloudBiome::ClearBand
            | CloudBiome::StormRegion
            | CloudBiome::PolarVortex
    ));
}

#[test]
fn cloud_biome_classifier_classify_polar() {
    let cbc = CloudBiomeClassifier::default();
    let biome = cbc.classify(30000.0, 85.0, 50.0);
    assert!(matches!(
        biome,
        CloudBiome::UpperHaze
            | CloudBiome::MethaneIceCloud
            | CloudBiome::H2SCloud
            | CloudBiome::DeepCloud
            | CloudBiome::ClearBand
            | CloudBiome::StormRegion
            | CloudBiome::PolarVortex
    ));
}

#[test]
fn cloud_biome_splat_weights() {
    let cbc = CloudBiomeClassifier::default();
    let sw = cbc.splat(40000.0, 30.0, 150.0);
    let total: f32 = sw.weights.iter().map(|(_, w)| w).sum();
    assert!(
        (total - 1.0).abs() < 0.01,
        "Splat weights should sum to ~1.0"
    );
}

#[test]
fn cloud_biome_splat_nonnegative() {
    let cbc = CloudBiomeClassifier::default();
    let sw = cbc.splat(60000.0, 10.0, 100.0);
    for (_, w) in &sw.weights {
        assert!(*w >= 0.0);
    }
}
