use crate::ecs::material::components::AlphaMode;
use crate::ecs::mesh::components::{Mesh, Vertex};
use crate::ecs::world::components::{
    Camera, LocalTransform, Material, Name, OrthographicCamera, PerspectiveCamera, Projection,
    RenderMesh, Visibility,
};
use gltf::{Document, Node, Primitive, buffer, import, import_slice};
use nalgebra_glm::{Quat, Vec2, Vec3, vec2, vec3};
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

use super::super::components::{Prefab, PrefabComponents, PrefabNode};

pub struct GltfLoadResult {
    pub prefabs: Vec<Prefab>,
    pub meshes: HashMap<String, Mesh>,
    pub materials: Vec<Material>,
    pub textures: HashMap<String, (Vec<u8>, u32, u32)>,
}

pub fn import_gltf_from_path(
    path: &std::path::Path,
) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
    let (doc, buffers, images) = import(path)?;

    let canonical_path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
    let namespace = canonical_path
        .to_str()
        .unwrap_or("unnamed")
        .replace('\\', "/")
        .replace('/', "::");

    process_gltf_data(doc, &buffers, &images, &namespace)
}

pub fn import_gltf_from_bytes(bytes: &[u8]) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
    let (doc, buffers, images) = import_slice(bytes)?;
    let mut hasher = DefaultHasher::new();
    bytes.hash(&mut hasher);
    let namespace = format!("bytes_{:016x}", hasher.finish());
    process_gltf_data(doc, &buffers, &images, &namespace)
}

fn process_gltf_data(
    doc: Document,
    buffers: &[buffer::Data],
    images: &[gltf::image::Data],
    namespace: &str,
) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
    let mut meshes = HashMap::new();
    let mut materials = Vec::new();
    let mut textures = HashMap::new();
    let mut mesh_index_to_primitives: HashMap<usize, Vec<(String, usize)>> = HashMap::new();
    let mut gltf_material_index_to_deduplicated_index: HashMap<usize, usize> = HashMap::new();

    let mut image_index_to_name = HashMap::new();
    for image in doc.images() {
        let base_name = image
            .name()
            .unwrap_or(&format!("Image_{}", image.index()))
            .to_string();
        let name = format!("{}::{}", namespace, base_name);
        image_index_to_name.insert(image.index(), name);
    }

    for material in doc.materials() {
        let pbr = material.pbr_metallic_roughness();
        let base_color = pbr.base_color_factor();
        let emissive = material.emissive_factor();

        let base_texture = pbr.base_color_texture().and_then(|tex| {
            let image_index = tex.texture().source().index();
            image_index_to_name.get(&image_index).cloned()
        });

        let emissive_texture = material.emissive_texture().and_then(|tex| {
            let image_index = tex.texture().source().index();
            image_index_to_name.get(&image_index).cloned()
        });

        let world_material = Material {
            base_color: [base_color[0], base_color[1], base_color[2], base_color[3]],
            emissive_factor: [emissive[0], emissive[1], emissive[2]],
            alpha_mode: match material.alpha_mode() {
                gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
                gltf::material::AlphaMode::Mask => AlphaMode::Mask,
                gltf::material::AlphaMode::Blend => AlphaMode::Blend,
            },
            alpha_cutoff: material.alpha_cutoff().unwrap_or(0.5),
            base_texture,
            emissive_texture,
            roughness: pbr.roughness_factor(),
            metallic: pbr.metallic_factor(),
            unlit: false,
            cache_id: None,
        };

        let deduplicated_index =
            if let Some(existing_index) = materials.iter().position(|m| m == &world_material) {
                if let Some(mat_index) = material.index() {
                    log::debug!(
                        "Material {} is identical to material {}, reusing",
                        mat_index,
                        existing_index
                    );
                }
                existing_index
            } else {
                let new_index = materials.len();
                materials.push(world_material);
                new_index
            };

        if let Some(mat_index) = material.index() {
            gltf_material_index_to_deduplicated_index.insert(mat_index, deduplicated_index);
        }
    }

    for mesh in doc.meshes() {
        let base_mesh_name = mesh
            .name()
            .unwrap_or(&format!("Mesh_{}", mesh.index()))
            .to_string();

        let mut primitive_names = Vec::new();

        for (primitive_index, primitive) in mesh.primitives().enumerate() {
            if let Some(mesh_primitive) = load_gltf_primitive(&primitive, buffers) {
                let base_primitive_name = if mesh.primitives().len() > 1 {
                    format!("{}_{}", base_mesh_name, primitive_index)
                } else {
                    base_mesh_name.clone()
                };
                let primitive_name = format!("{}::{}", namespace, base_primitive_name);

                let gltf_material_index = primitive.material().index().unwrap_or(0);
                let material_index = gltf_material_index_to_deduplicated_index
                    .get(&gltf_material_index)
                    .copied()
                    .unwrap_or(0);
                primitive_names.push((primitive_name.clone(), material_index));
                meshes.insert(primitive_name, mesh_primitive);
            }
        }

        if !primitive_names.is_empty() {
            mesh_index_to_primitives.insert(mesh.index(), primitive_names);
        }
    }

    let mut prefabs = Vec::new();

    for scene in doc.scenes() {
        let scene_name = scene
            .name()
            .unwrap_or(&format!("Scene_{}", scene.index()))
            .to_string();

        let mut root_nodes = Vec::new();

        for node in scene.nodes() {
            convert_node_to_prefab_nodes(
                &node,
                &mesh_index_to_primitives,
                &materials,
                &mut root_nodes,
            );
        }

        prefabs.push(Prefab {
            name: scene_name,
            root_nodes,
        });
    }

    for image in doc.images() {
        if let Some(image_data) = images.get(image.index()) {
            let base_name = image
                .name()
                .unwrap_or(&format!("Image_{}", image.index()))
                .to_string();
            let name = format!("{}::{}", namespace, base_name);

            let (width, height) = match image_data.format {
                gltf::image::Format::R8G8B8 => {
                    let mut rgba_data = Vec::with_capacity(image_data.pixels.len() / 3 * 4);
                    for chunk in image_data.pixels.chunks(3) {
                        rgba_data.push(chunk[0]);
                        rgba_data.push(chunk[1]);
                        rgba_data.push(chunk[2]);
                        rgba_data.push(255);
                    }
                    textures.insert(name, (rgba_data, image_data.width, image_data.height));
                    continue;
                }
                gltf::image::Format::R8G8B8A8 => (image_data.width, image_data.height),
                _ => {
                    log::warn!("Unsupported texture format for {}", name);
                    continue;
                }
            };

            textures.insert(name, (image_data.pixels.clone(), width, height));
        }
    }

    Ok(GltfLoadResult {
        prefabs,
        meshes,
        materials,
        textures,
    })
}

fn convert_node_to_prefab_nodes(
    node: &Node,
    mesh_index_to_primitives: &HashMap<usize, Vec<(String, usize)>>,
    materials: &[Material],
    output_nodes: &mut Vec<PrefabNode>,
) {
    let (translation, rotation, scale) = node.transform().decomposed();

    let quat = Quat::new(rotation[3], rotation[0], rotation[1], rotation[2]);

    let local_transform = LocalTransform {
        translation: Vec3::new(translation[0], translation[1], translation[2]),
        rotation: quat,
        scale: Vec3::new(scale[0], scale[1], scale[2]),
    };

    let node_name = node.name().map(|s| s.to_string());

    if let Some(mesh) = node.mesh() {
        if let Some(primitives) = mesh_index_to_primitives.get(&mesh.index()) {
            if primitives.len() == 1 {
                let (mesh_name, material_index) = &primitives[0];
                let mut components = PrefabComponents::default();

                if let Some(name_str) = &node_name {
                    components.name = Some(Name(name_str.clone()));
                }

                components.render_mesh = Some(RenderMesh {
                    name: mesh_name.clone(),
                });

                components.material = Some(
                    materials
                        .get(*material_index)
                        .cloned()
                        .unwrap_or_else(Material::default),
                );

                if let Some(camera) = node.camera() {
                    components.camera = Some(load_gltf_camera(&camera));
                }

                components.visibility = Some(Visibility { visible: true });

                let mut children = Vec::new();
                for child in node.children() {
                    convert_node_to_prefab_nodes(
                        &child,
                        mesh_index_to_primitives,
                        materials,
                        &mut children,
                    );
                }

                output_nodes.push(PrefabNode {
                    local_transform,
                    components,
                    children,
                });
            } else {
                let mut parent_components = PrefabComponents::default();
                if let Some(name_str) = &node_name {
                    parent_components.name = Some(Name(name_str.clone()));
                }

                if let Some(camera) = node.camera() {
                    parent_components.camera = Some(load_gltf_camera(&camera));
                }

                let mut children = Vec::new();

                for (index, (mesh_name, material_index)) in primitives.iter().enumerate() {
                    let mut primitive_components = PrefabComponents {
                        name: Some(Name(format!("Primitive_{}", index))),
                        render_mesh: Some(RenderMesh {
                            name: mesh_name.clone(),
                        }),
                        visibility: Some(Visibility { visible: true }),
                        ..Default::default()
                    };

                    primitive_components.material = Some(
                        materials
                            .get(*material_index)
                            .cloned()
                            .unwrap_or_else(Material::default),
                    );

                    children.push(PrefabNode {
                        local_transform: LocalTransform::default(),
                        components: primitive_components,
                        children: Vec::new(),
                    });
                }

                for child in node.children() {
                    convert_node_to_prefab_nodes(
                        &child,
                        mesh_index_to_primitives,
                        materials,
                        &mut children,
                    );
                }

                output_nodes.push(PrefabNode {
                    local_transform,
                    components: parent_components,
                    children,
                });
            }
        } else {
            let mut components = PrefabComponents::default();
            if let Some(name_str) = &node_name {
                components.name = Some(Name(name_str.clone()));
            }

            if let Some(camera) = node.camera() {
                components.camera = Some(load_gltf_camera(&camera));
            }

            let mut children = Vec::new();
            for child in node.children() {
                convert_node_to_prefab_nodes(
                    &child,
                    mesh_index_to_primitives,
                    materials,
                    &mut children,
                );
            }

            output_nodes.push(PrefabNode {
                local_transform,
                components,
                children,
            });
        }
    } else {
        let mut components = PrefabComponents::default();
        if let Some(name_str) = &node_name {
            components.name = Some(Name(name_str.clone()));
        }

        if let Some(camera) = node.camera() {
            components.camera = Some(load_gltf_camera(&camera));
        }

        let mut children = Vec::new();
        for child in node.children() {
            convert_node_to_prefab_nodes(
                &child,
                mesh_index_to_primitives,
                materials,
                &mut children,
            );
        }

        output_nodes.push(PrefabNode {
            local_transform,
            components,
            children,
        });
    }
}

fn load_gltf_camera(camera: &gltf::Camera<'_>) -> Camera {
    let projection = match camera.projection() {
        gltf::camera::Projection::Perspective(persp) => {
            Projection::Perspective(PerspectiveCamera {
                aspect_ratio: persp.aspect_ratio(),
                y_fov_rad: persp.yfov(),
                z_far: persp.zfar(),
                z_near: persp.znear(),
            })
        }
        gltf::camera::Projection::Orthographic(ortho) => {
            Projection::Orthographic(OrthographicCamera {
                x_mag: ortho.xmag(),
                y_mag: ortho.ymag(),
                z_far: ortho.zfar(),
                z_near: ortho.znear(),
            })
        }
    };

    Camera {
        projection,
        smoothing: None,
    }
}

fn load_gltf_primitive(primitive: &Primitive, buffers: &[buffer::Data]) -> Option<Mesh> {
    let reader = primitive.reader(|buffer| buffers.get(buffer.index()).map(|b| &b.0[..]));

    let positions: Vec<Vec3> = reader
        .read_positions()?
        .map(|p| vec3(p[0], p[1], p[2]))
        .collect();

    if positions.is_empty() {
        log::warn!("glTF primitive has no positions, skipping");
        return None;
    }

    let normals: Vec<Vec3> = if let Some(normals_iter) = reader.read_normals() {
        let normals: Vec<_> = normals_iter.map(|n| vec3(n[0], n[1], n[2])).collect();

        if normals.len() != positions.len() {
            log::warn!("glTF primitive has mismatched normal count, generating normals");
            generate_normals(&positions, &reader.read_indices())
        } else {
            normals
        }
    } else {
        generate_normals(&positions, &reader.read_indices())
    };

    let mut texcoords: Vec<Vec2> = vec![vec2(0.0, 0.0); positions.len()];

    if let Some(tex_iter) = reader.read_tex_coords(0) {
        let coords: Vec<_> = tex_iter.into_f32().map(|t| vec2(t[0], t[1])).collect();

        if coords.len() == positions.len() {
            texcoords = coords;
        } else {
            log::warn!("glTF primitive has mismatched texcoord count, using defaults");
        }
    }

    let indices: Vec<u32> = if let Some(indices_iter) = reader.read_indices() {
        let mut indices: Vec<u32> = indices_iter.into_u32().collect();

        if !indices.len().is_multiple_of(3) {
            log::warn!("glTF primitive has non-triangular indices, truncating");
            indices.truncate((indices.len() / 3) * 3);
        }

        indices
    } else if positions.len().is_multiple_of(3) {
        (0..positions.len() as u32).collect()
    } else {
        log::warn!("glTF primitive without indices has non-triangular vertex count, truncating");
        let vertex_count = (positions.len() / 3) * 3;
        (0..vertex_count as u32).collect()
    };

    if indices.is_empty() {
        log::warn!("glTF primitive has no valid indices, skipping");
        return None;
    }

    if positions.len() != normals.len() || positions.len() != texcoords.len() {
        log::error!("glTF primitive has inconsistent vertex attribute lengths");
        return None;
    }

    let vertices: Vec<Vertex> = positions
        .iter()
        .zip(&normals)
        .zip(&texcoords)
        .map(|((position, normal), tex_coords)| Vertex {
            position: [position.x, position.y, position.z],
            normal: [normal.x, normal.y, normal.z],
            tex_coords: [tex_coords.x, tex_coords.y],
        })
        .collect();

    let mut min = nalgebra_glm::vec3(f32::MAX, f32::MAX, f32::MAX);
    let mut max = nalgebra_glm::vec3(f32::MIN, f32::MIN, f32::MIN);

    for position in &positions {
        min.x = min.x.min(position.x);
        min.y = min.y.min(position.y);
        min.z = min.z.min(position.z);

        max.x = max.x.max(position.x);
        max.y = max.y.max(position.y);
        max.z = max.z.max(position.z);
    }

    let center = (min + max) * 0.5;
    let half_extents = (max - min) * 0.5;
    let sphere_radius = nalgebra_glm::length(&half_extents);

    let bounding_volume = crate::ecs::bounding_volume::components::BoundingVolume {
        obb: crate::ecs::bounding_volume::components::OrientedBoundingBox {
            center,
            half_extents,
            orientation: nalgebra_glm::quat_identity(),
        },
        sphere_radius,
    };

    Some(Mesh::with_bounding_volume(
        vertices,
        indices,
        bounding_volume,
    ))
}

fn generate_normals(
    positions: &[Vec3],
    indices_reader: &Option<gltf::mesh::util::ReadIndices>,
) -> Vec<Vec3> {
    let mut normals = vec![Vec3::zeros(); positions.len()];
    let mut face_counts = vec![0u32; positions.len()];

    let indices: Vec<u32> = if let Some(indices_iter) = indices_reader {
        indices_iter.clone().into_u32().collect()
    } else {
        (0..positions.len() as u32).collect()
    };

    for triangle in indices.chunks(3) {
        if triangle.len() != 3 {
            continue;
        }

        let i0 = triangle[0] as usize;
        let i1 = triangle[1] as usize;
        let i2 = triangle[2] as usize;

        if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
            continue;
        }

        let v0 = positions[i0];
        let v1 = positions[i1];
        let v2 = positions[i2];

        let edge1 = v1 - v0;
        let edge2 = v2 - v0;
        let face_normal = nalgebra_glm::cross(&edge1, &edge2);

        let face_normal = if nalgebra_glm::length(&face_normal) > 1e-6 {
            nalgebra_glm::normalize(&face_normal)
        } else {
            vec3(0.0, 1.0, 0.0)
        };

        normals[i0] += face_normal;
        normals[i1] += face_normal;
        normals[i2] += face_normal;

        face_counts[i0] += 1;
        face_counts[i1] += 1;
        face_counts[i2] += 1;
    }

    for (index, normal) in normals.iter_mut().enumerate() {
        if face_counts[index] > 0 {
            *normal /= face_counts[index] as f32;
            let magnitude = nalgebra_glm::length(normal);
            if magnitude > 1e-6 {
                *normal = nalgebra_glm::normalize(normal);
            } else {
                *normal = vec3(0.0, 1.0, 0.0);
            }
        } else {
            *normal = vec3(0.0, 1.0, 0.0);
        }
    }

    normals
}
