use mercurys::terrain::heightmap::{Heightmap, mercury_elevation};
use mercurys::terrain::lod::{Face, LodConfig, LodTerrain, QuadTreeNode, lod_level};
use mercurys::terrain::mesh::{TerrainMesh, base_radius, vertex_count};
use mercurys::terrain::texturing::{SplatWeights, SurfaceType, biome, classify_surface};

// ── Heightmap ──

#[test]
fn heightmap_new_creates() {
    let hm = Heightmap::new(16);
    assert_eq!(hm.resolution, 16);
}

#[test]
fn heightmap_sample_finite() {
    let hm = Heightmap::new(16);
    assert!(hm.sample(0.0, 0.0).is_finite());
}

#[test]
fn heightmap_radius_greater_than_base() {
    let mut hm = Heightmap::new(16);
    hm.generate_mercury_topography();
    let r = hm.radius_at(0.0, 0.0);
    assert!(r > 2_000_000.0);
}

#[test]
fn heightmap_procedural_generates() {
    let mut hm = Heightmap::new(16);
    hm.generate_procedural(42, 4, 0.5, 2.0);
    let a = hm.sample(10.0, 10.0);
    let b = hm.sample(50.0, 100.0);
    assert!(a.is_finite() && b.is_finite());
}

#[test]
fn heightmap_min_max_valid() {
    let mut hm = Heightmap::new(16);
    hm.generate_mercury_topography();
    assert!(hm.min_elevation_m <= hm.max_elevation_m);
}

#[test]
fn mercury_elevation_fn_finite() {
    let e = mercury_elevation(30.0, 60.0);
    assert!(e.is_finite());
}

// ── LOD ──

#[test]
fn face_all_returns_6() {
    assert_eq!(Face::all().len(), 6);
}

#[test]
fn lod_config_default() {
    let cfg = LodConfig::default_mercury();
    assert!(cfg.max_level > 0);
    assert!(cfg.split_distance_factor > 0.0);
    assert!(cfg.base_size_m > 0.0);
}

#[test]
fn lod_config_node_size_decreases() {
    let cfg = LodConfig::default_mercury();
    let s0 = cfg.node_size(0);
    let s3 = cfg.node_size(3);
    assert!(s0 > s3);
}

#[test]
fn lod_config_should_split() {
    let cfg = LodConfig::default_mercury();
    let close = cfg.should_split(0, 100.0);
    let far = cfg.should_split(0, 1.0e12);
    assert!(close || !far);
}

#[test]
fn quadtree_new_is_leaf() {
    let node = QuadTreeNode::new(Face::PosX, 0, 0, 0);
    assert!(node.is_leaf());
    assert_eq!(node.leaf_count(), 1);
}

#[test]
fn quadtree_split_makes_4_children() {
    let mut node = QuadTreeNode::new(Face::PosX, 0, 0, 0);
    node.split();
    assert!(!node.is_leaf());
    assert_eq!(node.leaf_count(), 4);
}

#[test]
fn lod_terrain_creates() {
    let cfg = LodConfig::default_mercury();
    let terrain = LodTerrain::new(cfg);
    assert_eq!(terrain.total_leaves(), 6);
}

#[test]
fn lod_level_decreases_with_distance() {
    let close = lod_level(100.0);
    let far = lod_level(1.0e8);
    assert!(close >= far);
}

// ── Mesh ──

#[test]
fn terrain_mesh_from_region() {
    let mesh = TerrainMesh::from_region(-10.0, 10.0, -10.0, 10.0, 4);
    assert!(mesh.vertex_count() > 0);
    assert!(mesh.triangle_count() > 0);
}

#[test]
fn terrain_mesh_vertices_valid() {
    let mesh = TerrainMesh::from_region(-5.0, 5.0, -5.0, 5.0, 2);
    for v in &mesh.vertices {
        assert!(v.position[0].is_finite());
        assert!(v.normal[0].is_finite());
        assert!(v.uv[0].is_finite());
    }
}

#[test]
fn terrain_mesh_indices_valid() {
    let mesh = TerrainMesh::from_region(-5.0, 5.0, -5.0, 5.0, 2);
    let vc = mesh.vertex_count();
    for idx in &mesh.indices {
        assert!((*idx as usize) < vc);
    }
}

#[test]
fn terrain_mesh_triangles_divisible_by_3() {
    let mesh = TerrainMesh::from_region(-5.0, 5.0, -5.0, 5.0, 3);
    assert_eq!(mesh.indices.len() % 3, 0);
}

#[test]
fn vertex_count_fn_increases_with_lod() {
    let v_low = vertex_count(1);
    let v_high = vertex_count(4);
    assert!(v_high > v_low);
}

#[test]
fn base_radius_about_2440km() {
    let r = base_radius();
    assert!((r - 2_439_700.0).abs() < 10_000.0);
}

// ── Texturing ──

#[test]
fn classify_surface_returns_variant() {
    let st = classify_surface(0.0, 0.0, 0.0);
    let name = std::format!("{:?}", st);
    assert!(!name.is_empty(), "SurfaceType debug repr must not be empty");
}

#[test]
fn surface_type_base_albedo_range() {
    let types = [
        SurfaceType::IntercraterPlains,
        SurfaceType::SmoothPlains,
        SurfaceType::CrateredHighlands,
        SurfaceType::CraterFloor,
        SurfaceType::CraterRim,
        SurfaceType::LobateScarp,
        SurfaceType::Hollows,
        SurfaceType::PolarIce,
    ];
    for st in &types {
        let a = st.base_albedo();
        assert!((0.0..=1.0).contains(&a), "{:?} albedo {}", st, a);
    }
}

#[test]
fn surface_type_roughness_range() {
    let types = [
        SurfaceType::IntercraterPlains,
        SurfaceType::SmoothPlains,
        SurfaceType::Hollows,
        SurfaceType::PolarIce,
    ];
    for st in &types {
        let r = st.roughness();
        assert!((0.0..=1.0).contains(&r), "{:?} roughness {}", st, r);
    }
}

#[test]
fn hollows_brighter_than_regolith() {
    assert!(SurfaceType::Hollows.base_albedo() > SurfaceType::IntercraterPlains.base_albedo());
}

#[test]
fn polar_ice_brightest() {
    let ice = SurfaceType::PolarIce.base_albedo();
    assert!(ice > SurfaceType::SmoothPlains.base_albedo());
}

#[test]
fn biome_returns_string() {
    let b = biome(0.0, 0.0);
    assert!(!b.is_empty());
}

#[test]
fn splat_weights_normalize() {
    let mut sw = SplatWeights {
        intercrater: 2.0,
        smooth: 1.0,
        highlands: 0.5,
        ice: 0.5,
    };
    sw.normalize();
    let sum = sw.intercrater + sw.smooth + sw.highlands + sw.ice;
    assert!((sum - 1.0).abs() < 0.01);
}
