use std::collections::HashMap;

use bevy::prelude::*;
use bevy_rapier3d::prelude::*;
use rand::Rng;

use crate::{
    GameState,
    asset_loading::{GltfAssets, ImageAssets},
    interaction::Interact,
    player::toolbar::ItemIcon,
};

pub fn map_plugin(app: &mut App) {
    app.add_systems(
        OnEnter(GameState::Playing),
        (spawn_level, spawn_objects).chain(),
    );
}


fn spawn_level(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    models: Res<Assets<Gltf>>,
    gltf_assets: Res<GltfAssets>
) {
    println!("LIBRARY: {:?}", gltf_assets.library);
    let mesh_names = [
        "corner_inside",
        "corner_outside",
        "wall",
        "door",
        "round_door",
        "round_hole",
    ];
    let shapes = mesh_names.map(|mesh_name| {
        let collider: Vec<(Collider, Transform)> = match mesh_name {
            "corner_inside" => vec![(
                Collider::cuboid(0.2, 1.0, 0.2),
                Transform::from_xyz(1.0, 1.0, 1.0)
                    .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())),
            )],
            "corner_outside" => vec![
                (
                    Collider::cuboid(1.0, 1.0, 0.1),
                    Transform::from_xyz(0.0, 1.0, -1.0),
                ),
                (
                    Collider::cuboid(1.0, 1.0, 0.1),
                    Transform::from_xyz(-1.0, 1.0, 0.0)
                        .with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())),
                ),
            ],
            "wall" => vec![(
                Collider::cuboid(1.0, 1.0, 0.1),
                Transform::from_xyz(0.0, 1.0, -1.0),
            )],
            "door" => vec![(
                Collider::cuboid(1.0, 1.0, 0.1),
                Transform::from_xyz(0.0, 1.0, -1.0),
            )],
            "round_door" => vec![(
                Collider::cuboid(1.0, 1.0, 0.1),
                Transform::from_xyz(0.0, 1.0, -1.0),
            )],
            "round_hole" => vec![
                (
                    Collider::cuboid(0.2, 1.0, 0.2),
                    Transform::from_xyz(1.0, 1.0, -1.0)
                        .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())),
                ),
                (
                    Collider::cuboid(0.2, 1.0, 0.2),
                    Transform::from_xyz(-1.0, 1.0, -1.0)
                        .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())),
                ),
            ],
            _ => vec![(
                Collider::cuboid(1.0, 0.1, 1.0),
                Transform::from_xyz(0.0, 0.0, 0.0),
            )],
        };
        let path = format!("meshes/library/space_{}.glb", mesh_name);
        let handle = gltf_assets
            .library
            .get(&path)
            .expect(&format!("Couldn't find {} in library", mesh_name));
        let gltf = models
            .get(handle)
            .expect(&format!("No model for {}", mesh_name));

        let asset = gltf
            .default_scene
            .as_ref()
            .expect(&format!("No scene in {}", mesh_name));
        (SceneRoot(asset.clone()), collider)
    });
    let [
        corner_inside,
        corner_outside,
        wall,
        door,
        round_door,
        round_hole,
    ] = shapes.clone();

    // huge floor
    commands.spawn((
        RigidBody::Fixed,
        Collider::cuboid(1000.0, 0.1, 1000.0),
        Transform::from_xyz(-500.0, 0.0, -500.0),
    ));
    // huge roof
    // commands.spawn((
    //     RigidBody::Fixed,
    //     Collider::cuboid(1000.0, 0.1, 1000.0),
    //     Transform::from_xyz(-500.0, 3.0, -500.0),
    // ));
    // let map = GameMap::test();

    let maps = create_maps(3);

    for map in maps {
        for ((x, z), node) in map.nodes.into_iter() {
            let values = {
                // corners are handled later, for now just a wall to test
                let node = node.clone();
                let pos = Transform::from_xyz(2.0*x as f32, 0.0, -2.0*z as f32);

                use Side::{Connection, Closed};
                match (node.north, node.east, node.south, node.west) {
                    // hallway horizontal
                    (Closed, Connection, Closed, Connection) => {
                        vec![
                            (wall.clone(), pos),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())))
                        ]
                    },
                    // hallway vertical
                    (Connection, Closed, Connection, Closed) => {
                        vec![
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())))
                        ]
                    }
                    // dead ends
                    (Connection, Closed, Closed, Closed) => {
                        vec![
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())))
                        ]
                    }
                    (Closed, Closed, Connection, Closed) => {
                        vec![
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())))
                        ]
                    }
                    (Closed, Connection, Closed, Closed) => {
                        vec![
                            (wall.clone(), pos),
                            (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())))
                        ]
                    },
                    (Closed, Closed, Closed, Connection) => {
                        vec![
                            (wall.clone(), pos),
                            (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())))
                        ]
                    },
                    // T hallways
                    (Closed, Connection, Connection, Connection) => {
                        vec![
                            (wall.clone(), pos),
                            (round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())))
                        ]
                    },
                    (Connection, Closed, Connection, Connection) => {
                        vec![
                            (round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())))
                        ]
                    },
                    (Connection, Connection, Closed, Connection) => {
                        vec![
                            (round_hole.clone(), pos),
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())))
                        ]
                    },
                    (Connection, Connection, Connection, Closed) => {
                        vec![
                            (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())))
                        ]
                    },
                    // fourway
                    (Connection, Connection, Connection, Connection) => {
                        vec![
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))),
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))),
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())))
                        ]
                    },
                    // corners
                    (Connection, Connection, Closed, Closed) => {
                        vec![
                            (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))),
                        ]
                    },
                    (Closed, Connection, Connection, Closed) => {
                        vec![
                            (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))),
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))),
                        ]
                    },
                    (Closed, Closed, Connection, Connection) => {
                        vec![
                            (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))),
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))),
                        ]
                    },
                    (Connection, Closed, Closed, Connection) => {
                        vec![
                            (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))),
                            (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))),
                        ]
                    }
                    _ => vec![]
                }
            };

            for ((scene_root, colliders), pos) in values {
                commands.spawn((
                    RigidBody::Fixed,
                    scene_root,
                    pos,
                )).with_children(|parent| {
                    for (collider, transform) in colliders {
                        parent.spawn((collider, transform));
                    }
                });
            }
        }
        let (x,z) = map.end_node;
        commands.spawn((
            Mesh3d(meshes.add(Cuboid::new(1.0, 20.0, 1.0))),
            MeshMaterial3d(materials.add(Color::srgb_u8(255, 0, 0))),
            Transform::from_xyz(2.0*x as f32, 0.5, -2.0*z as f32),
        ));
    }
}


fn create_maps(n: i32) -> Vec<GameMap> {
    let mut maps = Vec::new();
    let mut initial_node = MapNode::new();
    initial_node.east = Side::Closed;
    initial_node.south = Side::Closed;
    initial_node.west = Side::Closed;
    initial_node.north = Side::Connection;

    let pos = (0, 0);

    let map = GameMap::new(pos, initial_node.clone(), 5);

    maps.push(map);

    for _ in 0..n-1 {
        let map = maps.last().unwrap();
        let mut pos = map.end_node.clone();

        let mut next_node = MapNode::new();
        next_node.east = Side::Connection;
        next_node.south = Side::Connection;
        next_node.west = Side::Connection;
        next_node.north = Side::Connection;

        if map.end_node.0 >= map.end_node.1 {
            next_node.south = Side::Closed;
            next_node.north = Side::Closed;
            pos.0 += 1;
        } else {
            next_node.east = Side::Closed;
            next_node.west = Side::Closed;
            pos.1 += 1;
        }

        initial_node = next_node;
        let map = GameMap::new(pos, initial_node.clone(), 5);
        maps.push(map);
    }

    maps
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Side {
    Empty,
    Connection,
    Closed,
}

#[derive(Clone, Debug)]
struct MapNode {
    north: Side,
    south: Side,
    east: Side,
    west: Side,
}

impl MapNode {
    fn new() -> Self {
        MapNode {
            north: Side::Empty,
            south: Side::Empty,
            east: Side::Empty,
            west: Side::Empty,
        }
    }
}

struct GameMap {
    nodes: HashMap<(i32, i32), MapNode>,
    end_node: (i32, i32),
    grid_size: i32,
    initial_point: (i32, i32)
}

impl GameMap {
    fn new(initial_point: (i32, i32), node: MapNode, grid_size: i32) -> Self {
        let mut nodes = HashMap::new();
        nodes.insert(initial_point, node);
        let mut m = GameMap { initial_point, nodes, grid_size, end_node: initial_point};
        m.generate_map();
        m
    }
    fn generate_map(&mut self) {
        let mut first_point = self.initial_point.clone();
        first_point.1 += 1;
        self.create_node(first_point);  // North
    }

    fn pos_within_boundaries(&self, pos: (i32, i32)) -> bool {
        pos.0 > self.grid_size + self.initial_point.0
        || pos.1 > self.grid_size + self.initial_point.1
        || pos.0 < self.initial_point.0
        || pos.1 < self.initial_point.1
    }

    fn choose_side(&self, pos: (i32, i32)) -> Side {
        if self.pos_within_boundaries(pos) {
            Side::Closed
        } else {
            if rand::rng().random_bool(0.5) {
                Side::Connection
            } else {
                Side::Closed
            }
        }
    }

    fn ensure_connection(&self, node: &mut MapNode, (x, y): (i32, i32)) {
        let am_connections = (node.north == Side::Connection) as u8 +
            (node.south == Side::Connection) as u8 +
            (node.east == Side::Connection) as u8 +
            (node.west == Side::Connection) as u8;

        if am_connections <= 2 {
            if node.north != Side::Connection && !self.nodes.contains_key(&(x, y + 1)) {
                node.north = Side::Connection
            }
            else if node.south != Side::Connection && !self.nodes.contains_key(&(x, y - 1)) {
                node.south = Side::Connection
            }
            else if node.east != Side::Connection && !self.nodes.contains_key(&(x + 1, y)) {
                node.east = Side::Connection
            }
            else if node.west != Side::Connection && !self.nodes.contains_key(&(x - 1, y)) {
                node.west = Side::Connection
            }
        }
    }

    fn create_node(&mut self, idx: (i32, i32)) {
        let mut nodes_to_process = vec![idx];

        while let Some(current_idx) = nodes_to_process.pop() {
            if self.nodes.contains_key(&current_idx) {
                continue;
            }

            let (x, y) = current_idx;
            let mut new_node = MapNode::new();

            if x >= self.end_node.0 && y >= self.end_node.1 {
                self.end_node = (x, y);
            }

            let north = self.nodes.get(&(x, y + 1));
            let south = self.nodes.get(&(x, y - 1));
            let east = self.nodes.get(&(x + 1, y));
            let west = self.nodes.get(&(x - 1, y));

            new_node.north = north.map_or_else(|| self.choose_side(current_idx), |n| n.south);
            new_node.south = south.map_or_else(|| self.choose_side(current_idx), |s| s.north);
            new_node.east = east.map_or_else(|| self.choose_side(current_idx), |e| e.west);
            new_node.west = west.map_or_else(|| self.choose_side(current_idx), |w| w.east);

            if !self.pos_within_boundaries(current_idx) {
                self.ensure_connection(&mut new_node, current_idx);
            }
            if new_node.north == Side::Connection && !self.nodes.contains_key(&(x, y + 1)) {
                nodes_to_process.push((x, y + 1));
            }
            if new_node.south == Side::Connection && !self.nodes.contains_key(&(x, y - 1)) {
                nodes_to_process.push((x, y - 1));
            }
            if new_node.east == Side::Connection && !self.nodes.contains_key(&(x + 1, y)) {
                nodes_to_process.push((x + 1, y));
            }
            if new_node.west == Side::Connection && !self.nodes.contains_key(&(x - 1, y)) {
                nodes_to_process.push((x - 1, y));
            }

            self.nodes.insert(current_idx, new_node);
        }

        let end_node = self.nodes.get_mut(&self.end_node).unwrap();
        if self.end_node.1 >= self.end_node.0 {
            end_node.north = Side::Connection;
        }
        else if self.end_node.0 >= self.end_node.1 {
            end_node.east = Side::Connection;
        }
    }

    fn print_map(&self) {
        let mut min_x = 0;
        let mut max_x = 0;
        let mut min_y = 0;
        let mut max_y = 0;

        for &(x, y) in self.nodes.keys() {
            min_x = min_x.min(x);
            max_x = max_x.max(x);
            min_y = min_y.min(y);
            max_y = max_y.max(y);
        }

        for y in (min_y..=max_y).rev() {
            for x in min_x..=max_x {
                match self.nodes.get(&(x, y)) {
                    Some(node) => print!("[+]"),
                    None => print!("   "),
                }
            }
            println!();
        }
    }
}

fn spawn_objects(
    mut commands: Commands,
    models: Res<Assets<Gltf>>,
    gltf_assets: Res<GltfAssets>,
    image_assets: Res<ImageAssets>,
) {

    // id card
    let card = models.get(&gltf_assets.card).unwrap();
    let asset = card.default_scene.as_ref().unwrap();

    commands
        .spawn((
            Transform::from_xyz(0.0, 2.0, 2.0),
            Interact,
            RigidBody::Dynamic,
            Name::new("Id Card 1"),
            Visibility::Visible,
            ItemIcon::new(image_assets.id_card.clone()),
            SceneRoot(asset.clone()),
        ))
        .with_children(|parent| {
            parent.spawn((
                ColliderMassProperties::Mass(10.0),
                Collider::cuboid(0.05, 0.05, 0.01),
                Transform::from_rotation(Quat::from_euler(
                    EulerRot::XYZ,
                    -10.0f32.to_radians(), // X-axis rotation (tilt)
                    -5.0f32.to_radians(),  // Y-axis rotation
                    10.0f32.to_radians(),  // Z-axis rotation
                )),
            ));
            parent.spawn((
                ActiveEvents::COLLISION_EVENTS,
                Transform::default(),
                Collider::ball(0.5), // Interaction radius
                Sensor,
            ));
        });
}
