use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;

use freecs::Entity;
use petgraph::graph::NodeIndex;

use crate::ecs::audio::components::AudioSource;
#[cfg(feature = "audio")]
use crate::ecs::audio::systems::load_sound_from_cursor;
use crate::ecs::bounding_volume::components::BoundingVolume;
use crate::ecs::generational_registry::registry_entry_by_name;
use crate::ecs::lines::components::Lines;
use crate::ecs::material::resources::material_registry_insert;
use crate::ecs::mesh::components::InstanceTransform;
#[cfg(feature = "physics")]
use crate::ecs::physics::{ColliderComponent, RigidBodyComponent};
use crate::ecs::prefab::resources::mesh_cache_insert;
use crate::ecs::prefab::{Prefab, import_gltf_from_path, spawn_prefab};
use crate::ecs::world::commands::{
    WorldCommand, spawn_instanced_mesh, spawn_instanced_mesh_with_material,
};
use crate::ecs::world::components::{
    CastsShadow, GlobalTransform, LocalTransformDirty, MaterialRef, Name, Parent, RenderMesh,
};
use crate::ecs::world::{
    AUDIO_SOURCE, BOUNDING_VOLUME, CAMERA, CASTS_SHADOW, GLOBAL_TRANSFORM, LIGHT, LINES,
    LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, MATERIAL_REF, NAME, PARENT, RENDER_MESH, SCRIPT, World,
};
#[cfg(feature = "physics")]
use crate::ecs::world::{COLLIDER, PHYSICS_INTERPOLATION, RIGID_BODY};
use crate::render::wgpu::texture_cache::texture_cache_add_reference;

use super::components::{Map, MapHdrSkybox, MapNode, MapPrefab};
#[cfg(feature = "physics")]
use super::components::{MapBodyType, MapCollider, MapPhysics};

#[derive(Debug, thiserror::Error)]
pub enum MapError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("Binary error: {0}")]
    Binary(#[from] bincode::Error),

    #[error("Prefab not found: {0}")]
    PrefabNotFound(String),

    #[error("Failed to load prefab: {0}")]
    PrefabLoadError(String),

    #[error("Invalid graph structure")]
    InvalidGraph,

    #[error("Expected Entity node at root, found component")]
    ExpectedEntityNode,
}

pub fn save_map_json(map: &Map, path: &Path) -> Result<(), MapError> {
    let file = File::create(path)?;
    let writer = BufWriter::new(file);
    serde_json::to_writer_pretty(writer, map)?;
    Ok(())
}

pub fn load_map_json(path: &Path) -> Result<Map, MapError> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let map = serde_json::from_reader(reader)?;
    Ok(map)
}

pub fn save_map_binary(map: &Map, path: &Path) -> Result<(), MapError> {
    let file = File::create(path)?;
    let writer = BufWriter::new(file);
    bincode::serialize_into(writer, map)?;
    Ok(())
}

pub fn load_map_binary(path: &Path) -> Result<Map, MapError> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let map = bincode::deserialize_from(reader)?;
    Ok(map)
}

pub fn save_map(map: &Map, path: &Path) -> Result<(), MapError> {
    match path.extension().and_then(|e| e.to_str()) {
        Some("bin") => save_map_binary(map, path),
        _ => save_map_json(map, path),
    }
}

pub fn load_map(path: &Path) -> Result<Map, MapError> {
    match path.extension().and_then(|e| e.to_str()) {
        Some("bin") => load_map_binary(path),
        _ => load_map_json(path),
    }
}

pub struct SpawnMapResult {
    pub node_to_entity: HashMap<NodeIndex, Entity>,
    pub warnings: Vec<String>,
}

pub fn spawn_map(world: &mut World, map: &Map) -> Result<SpawnMapResult, MapError> {
    if !map.is_valid() {
        return Err(MapError::InvalidGraph);
    }

    world.resources.graphics.atmosphere = map.atmosphere;

    if let Some(hdr_skybox) = &map.hdr_skybox {
        match hdr_skybox {
            MapHdrSkybox::Embedded { data } => {
                world.queue_command(WorldCommand::LoadHdrSkybox {
                    hdr_data: data.clone(),
                });
            }
            MapHdrSkybox::Reference { path } => {
                world.queue_command(WorldCommand::LoadHdrSkyboxFromPath {
                    path: std::path::PathBuf::from(path),
                });
            }
        }
    }

    for (texture_name, embedded_texture) in &map.textures {
        if let Some((rgba_data, width, height)) = embedded_texture.to_rgba() {
            world.queue_command(WorldCommand::LoadTexture {
                name: texture_name.clone(),
                rgba_data,
                width,
                height,
            });
        }
    }

    let prefab_result = load_referenced_prefabs(world, map);
    let mut all_warnings = prefab_result.warnings;

    let mut node_to_entity: HashMap<NodeIndex, Entity> = HashMap::new();
    let mut spawn_warnings: Vec<String> = Vec::new();

    for root_index in map.root_nodes() {
        let root_node = &map.graph[root_index];
        if !root_node.is_entity() {
            return Err(MapError::ExpectedEntityNode);
        }

        spawn_entity_recursive(
            world,
            map,
            &prefab_result.prefabs,
            root_index,
            None,
            &mut node_to_entity,
            &mut spawn_warnings,
        );
    }

    all_warnings.extend(spawn_warnings);

    if let Some(map_navmesh) = &map.navmesh {
        world.resources.navmesh = map_navmesh.to_navmesh_world();
    }

    Ok(SpawnMapResult {
        node_to_entity,
        warnings: all_warnings,
    })
}

pub struct LoadedPrefabsResult {
    pub prefabs: HashMap<String, Prefab>,
    pub warnings: Vec<String>,
}

fn load_referenced_prefabs(world: &mut World, map: &Map) -> LoadedPrefabsResult {
    let mut loaded_prefabs = HashMap::new();
    let mut warnings = Vec::new();

    for (name, map_prefab) in &map.prefabs {
        match map_prefab {
            MapPrefab::Reference { path } => {
                let gltf_path = Path::new(path);
                match import_gltf_from_path(gltf_path) {
                    Ok(result) => {
                        for (mesh_name, mesh) in result.meshes {
                            mesh_cache_insert(&mut world.resources.mesh_cache, mesh_name, mesh);
                        }

                        for (texture_name, (rgba_data, width, height)) in result.textures {
                            world.queue_command(WorldCommand::LoadTexture {
                                name: texture_name,
                                rgba_data,
                                width,
                                height,
                            });
                        }

                        if let Some(prefab) = result.prefabs.into_iter().next() {
                            loaded_prefabs.insert(name.clone(), prefab);
                        } else {
                            warnings.push(format!(
                                "No prefabs found in file '{}' for '{}'",
                                path, name
                            ));
                        }
                    }
                    Err(error) => {
                        warnings.push(format!(
                            "Failed to load prefab '{}' from '{}': {}",
                            name, path, error
                        ));
                    }
                }
            }
            MapPrefab::Embedded {
                prefab: _,
                meshes,
                textures,
            } => {
                for (mesh_name, mesh) in meshes {
                    mesh_cache_insert(
                        &mut world.resources.mesh_cache,
                        mesh_name.clone(),
                        mesh.clone(),
                    );
                }

                for (texture_name, embedded_texture) in textures {
                    if let Some((rgba_data, width, height)) = embedded_texture.to_rgba() {
                        world.queue_command(WorldCommand::LoadTexture {
                            name: texture_name.clone(),
                            rgba_data,
                            width,
                            height,
                        });
                    }
                }
            }
        }
    }

    LoadedPrefabsResult {
        prefabs: loaded_prefabs,
        warnings,
    }
}

fn spawn_entity_recursive(
    world: &mut World,
    map: &Map,
    loaded_prefabs: &HashMap<String, Prefab>,
    node_index: NodeIndex,
    parent_entity: Option<Entity>,
    node_to_entity: &mut HashMap<NodeIndex, Entity>,
    warnings: &mut Vec<String>,
) -> Option<Entity> {
    let node = &map.graph[node_index];

    let (name, transform, script) = match node {
        MapNode::Entity {
            name,
            transform,
            script,
        } => (name.clone(), *transform, script.clone()),
        _ => {
            warnings.push("Expected entity node, found component".to_string());
            return None;
        }
    };

    let mut component_mask = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY;
    if name.is_some() {
        component_mask |= NAME;
    }
    if script.is_some() {
        component_mask |= SCRIPT;
    }
    if parent_entity.is_some() {
        component_mask |= PARENT;
    }

    let entity = world.spawn_entities(component_mask, 1)[0];

    world.set_local_transform(entity, transform);
    world.set_global_transform(entity, GlobalTransform::default());
    world.set_local_transform_dirty(entity, LocalTransformDirty);

    if let Some(name_str) = name {
        world.set_name(entity, Name(name_str));
    }

    if let Some(script_component) = script {
        world.set_script(entity, script_component);
    }

    if let Some(parent) = parent_entity {
        world.set_parent(entity, Parent(Some(parent)));
        world
            .resources
            .children_cache
            .entry(parent)
            .or_default()
            .push(entity);
    }

    node_to_entity.insert(node_index, entity);

    let children: Vec<NodeIndex> = map.children(node_index);

    for child_index in children {
        let child_node = &map.graph[child_index];

        if child_node.is_entity() {
            spawn_entity_recursive(
                world,
                map,
                loaded_prefabs,
                child_index,
                Some(entity),
                node_to_entity,
                warnings,
            );
        } else {
            apply_component(world, map, loaded_prefabs, entity, child_node, warnings);
        }
    }

    Some(entity)
}

fn apply_component(
    world: &mut World,
    map: &Map,
    loaded_prefabs: &HashMap<String, Prefab>,
    entity: Entity,
    component_node: &MapNode,
    warnings: &mut Vec<String>,
) {
    match component_node {
        MapNode::Entity { .. } => {
            warnings.push("Unexpected entity node in component position".to_string());
        }

        MapNode::Light(map_light) => {
            world.add_components(entity, LIGHT);
            world.set_light(entity, map_light.to_light());
        }

        MapNode::Camera(map_camera) => {
            world.add_components(entity, CAMERA);
            world.set_camera(entity, map_camera.to_camera());
        }

        MapNode::Mesh {
            mesh_name,
            material,
            casts_shadow,
            bounding_volume,
            #[cfg(feature = "physics")]
            physics,
            #[cfg(not(feature = "physics"))]
                physics: _,
        } => {
            world.add_components(entity, RENDER_MESH | MATERIAL_REF | CASTS_SHADOW);

            let render_mesh = RenderMesh::new(mesh_name.clone());
            if let Some(&index) = world
                .resources
                .mesh_cache
                .registry
                .name_to_index
                .get(&render_mesh.name)
            {
                world.resources.mesh_cache.registry.add_reference(index);
            }
            world.set_render_mesh(entity, render_mesh);

            let material_name = if let Some(map_material) = material {
                let mat = map_material.to_material();
                let name = format!("Map_{}_{}", mesh_name, entity.id);
                material_registry_insert(&mut world.resources.material_registry, name.clone(), mat);
                name
            } else {
                "Default".to_string()
            };
            if let Some(&index) = world
                .resources
                .material_registry
                .registry
                .name_to_index
                .get(&material_name)
            {
                world
                    .resources
                    .material_registry
                    .registry
                    .add_reference(index);
            }

            if let Some(mat) =
                registry_entry_by_name(&world.resources.material_registry.registry, &material_name)
            {
                if let Some(base_texture) = &mat.base_texture {
                    texture_cache_add_reference(&mut world.resources.texture_cache, base_texture);
                }
                if let Some(emissive_texture) = &mat.emissive_texture {
                    texture_cache_add_reference(
                        &mut world.resources.texture_cache,
                        emissive_texture,
                    );
                }
            }

            world.set_material_ref(entity, MaterialRef::new(material_name));

            world.set_casts_shadow(entity, CastsShadow);
            if !*casts_shadow {
                world.remove_components(entity, CASTS_SHADOW);
            }

            if let Some(bv) = bounding_volume {
                world.add_components(entity, BOUNDING_VOLUME);
                world.set_bounding_volume(entity, *bv);
            }

            #[cfg(feature = "physics")]
            if let Some(map_physics) = physics {
                apply_physics_to_entity(world, entity, map_physics);
            }
        }

        MapNode::InstancedMesh {
            mesh_name,
            instances,
            material,
            casts_shadow,
            bounding_volume,
        } => {
            let instance_transforms: Vec<InstanceTransform> = instances
                .iter()
                .map(|inst| {
                    let local = inst.to_local_transform();
                    InstanceTransform {
                        translation: local.translation,
                        rotation: local.rotation,
                        scale: local.scale,
                    }
                })
                .collect();

            let instanced_entity = if let Some(map_material) = material {
                let mat = map_material.to_material();
                let material_name = format!("Map_Instanced_{}_{}", mesh_name, entity.id);
                material_registry_insert(
                    &mut world.resources.material_registry,
                    material_name.clone(),
                    mat,
                );

                if let Some(mat) = registry_entry_by_name(
                    &world.resources.material_registry.registry,
                    &material_name,
                ) {
                    if let Some(base_texture) = &mat.base_texture {
                        texture_cache_add_reference(
                            &mut world.resources.texture_cache,
                            base_texture,
                        );
                    }
                    if let Some(emissive_texture) = &mat.emissive_texture {
                        texture_cache_add_reference(
                            &mut world.resources.texture_cache,
                            emissive_texture,
                        );
                    }
                }

                spawn_instanced_mesh_with_material(
                    world,
                    mesh_name,
                    instance_transforms,
                    &material_name,
                )
            } else {
                spawn_instanced_mesh(world, mesh_name, instance_transforms)
            };

            if !*casts_shadow {
                world.remove_components(instanced_entity, CASTS_SHADOW);
            }

            let bv = if let Some(bv) = bounding_volume {
                *bv
            } else {
                compute_instanced_mesh_bounding_volume(mesh_name, instances)
            };
            world.add_components(instanced_entity, BOUNDING_VOLUME);
            world.set_bounding_volume(instanced_entity, bv);

            world.add_components(instanced_entity, PARENT);
            world.set_parent(instanced_entity, Parent(Some(entity)));
            world
                .resources
                .children_cache
                .entry(entity)
                .or_default()
                .push(instanced_entity);
        }

        MapNode::Lines(lines_data) => {
            world.add_components(entity, LINES);
            let mut lines = Lines::new(lines_data.clone());
            lines.mark_dirty();
            world.set_lines(entity, lines);
        }

        MapNode::PrefabInstance { prefab_name } => {
            if let Some(prefab) = get_prefab(map, loaded_prefabs, prefab_name) {
                let transform = world
                    .get_local_transform(entity)
                    .copied()
                    .unwrap_or_default();
                let prefab_root = spawn_prefab(world, prefab, transform.translation);

                world.add_components(prefab_root, PARENT);
                world.set_parent(prefab_root, Parent(Some(entity)));
                world
                    .resources
                    .children_cache
                    .entry(entity)
                    .or_default()
                    .push(prefab_root);
            } else {
                warnings.push(format!("Missing prefab: '{}'", prefab_name));
            }
        }

        MapNode::AudioSource(audio_source) => {
            world.add_components(entity, AUDIO_SOURCE);

            #[cfg(feature = "audio")]
            {
                if let Some(embedded_audio) = map.audio.get(&audio_source.audio_ref) {
                    if let Ok(sound_data) = load_sound_from_cursor(embedded_audio.data.clone()) {
                        world
                            .resources
                            .audio
                            .load_sound(audio_source.audio_ref.clone(), sound_data);
                    }
                } else {
                    warnings.push(format!(
                        "Audio '{}' not found in map",
                        audio_source.audio_ref
                    ));
                }
            }

            #[cfg(not(feature = "audio"))]
            {
                let _ = &audio_source.audio_ref;
                warnings
                    .push("Audio feature is not enabled - audio source will not play".to_string());
            }

            world.set_audio_source(
                entity,
                AudioSource {
                    audio_ref: Some(audio_source.audio_ref.clone()),
                    volume: audio_source.volume,
                    looping: audio_source.looping,
                    playing: audio_source.playing,
                    spatial: audio_source.spatial,
                    reverb: audio_source.reverb,
                },
            );
        }

        MapNode::Physics(physics) => {
            #[cfg(feature = "physics")]
            {
                apply_physics_to_entity(world, entity, physics);
            }
            #[cfg(not(feature = "physics"))]
            {
                let _ = physics;
                warnings.push(
                    "Physics feature is not enabled - collider will not be created".to_string(),
                );
            }
        }

        MapNode::Custom { .. } => {}
    }
}

fn get_prefab<'a>(
    map: &'a Map,
    loaded_prefabs: &'a HashMap<String, Prefab>,
    prefab_name: &str,
) -> Option<&'a Prefab> {
    let map_prefab = map.prefabs.get(prefab_name)?;

    match map_prefab {
        MapPrefab::Embedded { prefab, .. } => Some(prefab),
        MapPrefab::Reference { .. } => loaded_prefabs.get(prefab_name),
    }
}

fn compute_instanced_mesh_bounding_volume(
    mesh_name: &str,
    instances: &[super::components::MeshInstance],
) -> BoundingVolume {
    use crate::ecs::bounding_volume::components::OrientedBoundingBox;

    let base_bv = BoundingVolume::from_mesh_type(mesh_name);
    let base_half = base_bv.obb.half_extents;

    if instances.is_empty() {
        return base_bv;
    }

    let mut min_x = f32::MAX;
    let mut min_y = f32::MAX;
    let mut min_z = f32::MAX;
    let mut max_x = f32::MIN;
    let mut max_y = f32::MIN;
    let mut max_z = f32::MIN;

    let corners = [
        nalgebra_glm::vec3(-1.0, -1.0, -1.0),
        nalgebra_glm::vec3(1.0, -1.0, -1.0),
        nalgebra_glm::vec3(-1.0, 1.0, -1.0),
        nalgebra_glm::vec3(1.0, 1.0, -1.0),
        nalgebra_glm::vec3(-1.0, -1.0, 1.0),
        nalgebra_glm::vec3(1.0, -1.0, 1.0),
        nalgebra_glm::vec3(-1.0, 1.0, 1.0),
        nalgebra_glm::vec3(1.0, 1.0, 1.0),
    ];

    for instance in instances {
        let translation = nalgebra_glm::vec3(
            instance.translation[0],
            instance.translation[1],
            instance.translation[2],
        );
        let rotation = nalgebra_glm::quat(
            instance.rotation[0],
            instance.rotation[1],
            instance.rotation[2],
            instance.rotation[3],
        );
        let scale = nalgebra_glm::vec3(instance.scale[0], instance.scale[1], instance.scale[2]);

        let scaled_half = nalgebra_glm::vec3(
            base_half.x * scale.x,
            base_half.y * scale.y,
            base_half.z * scale.z,
        );

        for corner in &corners {
            let local_corner = nalgebra_glm::vec3(
                corner.x * scaled_half.x,
                corner.y * scaled_half.y,
                corner.z * scaled_half.z,
            );
            let rotated = nalgebra_glm::quat_rotate_vec3(&rotation, &local_corner);
            let world_corner = translation + rotated;

            min_x = min_x.min(world_corner.x);
            min_y = min_y.min(world_corner.y);
            min_z = min_z.min(world_corner.z);
            max_x = max_x.max(world_corner.x);
            max_y = max_y.max(world_corner.y);
            max_z = max_z.max(world_corner.z);
        }
    }

    let center = nalgebra_glm::vec3(
        (min_x + max_x) * 0.5,
        (min_y + max_y) * 0.5,
        (min_z + max_z) * 0.5,
    );
    let half_extents = nalgebra_glm::vec3(
        (max_x - min_x) * 0.5,
        (max_y - min_y) * 0.5,
        (max_z - min_z) * 0.5,
    );

    let sphere_radius = half_extents.magnitude();

    BoundingVolume {
        obb: OrientedBoundingBox {
            center,
            half_extents,
            orientation: nalgebra_glm::Quat::identity(),
        },
        sphere_radius,
    }
}

#[cfg(feature = "physics")]
fn apply_physics_to_entity(world: &mut World, entity: Entity, physics: &MapPhysics) {
    let transform = world
        .get_local_transform(entity)
        .copied()
        .unwrap_or_default();

    let is_dynamic = matches!(physics.body_type, MapBodyType::Dynamic);

    let mut components = RIGID_BODY | COLLIDER;
    if is_dynamic {
        components |= PHYSICS_INTERPOLATION;
    }
    world.add_components(entity, components);

    let mut rigid_body = match physics.body_type {
        MapBodyType::Static => RigidBodyComponent::new_static(),
        MapBodyType::Dynamic => RigidBodyComponent::new_dynamic(),
        MapBodyType::Kinematic => RigidBodyComponent::new_kinematic(),
    };
    rigid_body = rigid_body.with_translation(
        transform.translation.x,
        transform.translation.y,
        transform.translation.z,
    );
    if let Some(mass) = physics.mass {
        rigid_body = rigid_body.with_mass(mass);
    }
    world.set_rigid_body(entity, rigid_body);

    let collider = match &physics.collider {
        MapCollider::Cuboid { half_extents } => {
            ColliderComponent::new_cuboid(half_extents[0], half_extents[1], half_extents[2])
        }
        MapCollider::Ball { radius } => ColliderComponent::new_ball(*radius),
        MapCollider::Cylinder {
            half_height,
            radius,
        } => ColliderComponent::new_cylinder(*half_height, *radius),
        MapCollider::Capsule {
            half_height,
            radius,
        } => ColliderComponent::new_capsule(*half_height, *radius),
        MapCollider::TriMesh { vertices, indices } => ColliderComponent {
            shape: crate::ecs::physics::ColliderShape::TriMesh {
                vertices: vertices.clone(),
                indices: indices.clone(),
            },
            ..Default::default()
        },
        MapCollider::FromMesh => ColliderComponent::new_cuboid(
            transform.scale.x / 2.0,
            transform.scale.y / 2.0,
            transform.scale.z / 2.0,
        ),
    };
    let collider = collider
        .with_friction(physics.friction)
        .with_restitution(physics.restitution);
    world.set_collider(entity, collider);

    let rigid_body_comp = world.get_rigid_body(entity).cloned().unwrap();
    let collider_comp = world.get_collider(entity).cloned();
    let rapier_body = rigid_body_comp.to_rapier_rigid_body();
    let rapier_handle = world.resources.physics.add_rigid_body(rapier_body);
    if let Some(collider_comp) = collider_comp {
        let rapier_collider = collider_comp.to_rapier_collider();
        world
            .resources
            .physics
            .add_collider(rapier_collider, rapier_handle);
    }
    if let Some(rigid_body_mut) = world.get_rigid_body_mut(entity) {
        rigid_body_mut.handle = Some(rapier_handle.into());
    }
}
