use std::f32::consts::PI;
use std::rc::Rc;

use glam::Vec3;
use rapier3d::control::{CharacterAutostep, KinematicCharacterController};
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};

use crate::bundles::Bundle;
use crate::components::lights::spot::SpotlightComponent;
use crate::components::{
    InputComponent, JumpComponent, MeshComponent, MovementComponent, PhysicsComponent,
};
use crate::entity::EntityHandle;
use crate::loaders::mesh::Mesh;
use crate::physics::PhysicsManager;
use crate::render::Pipeline;
use crate::state::StateMachine;
use crate::systems::player_states::{
    PlayerFallingState, PlayerIdleState, PlayerJumpingState, PlayerWalkingState,
};
use crate::world::{Transform, World};

pub struct PlayerBundle
{
    pub position: Vec3,
}

impl Bundle for PlayerBundle
{
    fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
    {
        let entity = world.spawn();

        let spawn_transform = Transform::from_position(self.position);

        let rigidbody = RigidBodyBuilder::kinematic_position_based()
            .translation(spawn_transform.position.into())
            .build();
        let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
        let _controller = KinematicCharacterController {
            slide: true,
            autostep: Some(CharacterAutostep::default()),
            max_slope_climb_angle: 45.0,
            ..Default::default()
        };

        let rigidbody_handle = PhysicsManager::add_rigidbody(rigidbody);
        let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));

        let mesh = Mesh::load_mesh("meshes/player_mesh.glb")
            .map_err(|e| format!("missing player mesh: {}", e))?;

        let falling_state = PlayerFallingState { entity };
        let idle_state = PlayerIdleState { entity };
        let walking_state = PlayerWalkingState {
            entity,
            enter_time_stamp: 0.0,
        };
        let jumping_state = PlayerJumpingState {
            entity,
            enter_time_stamp: 0.0,
        };

        let mut state_machine = StateMachine::new(Box::new(falling_state));
        state_machine.add_state(walking_state);
        state_machine.add_state(idle_state);
        state_machine.add_state(jumping_state);

        let entity_id = entity;

        state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && !has_input
        });

        state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && has_input
        });

        state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && has_input
        });

        state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && !has_input
        });

        state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            !is_grounded
        });

        state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            !is_grounded
        });

        state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let jump_pressed = world
                .inputs
                .with(entity_id, |i| i.jump_just_pressed)
                .unwrap_or(false);
            is_grounded && jump_pressed
        });

        state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let jump_pressed = world
                .inputs
                .with(entity_id, |i| i.jump_just_pressed)
                .unwrap_or(false);
            is_grounded && jump_pressed
        });

        state_machine.add_transition::<PlayerJumpingState, PlayerFallingState>(move |world| {
            world
                .jumps
                .with(entity_id, |jump| {
                    jump.jump_context.duration >= jump.jump_duration
                })
                .unwrap_or(true)
        });

        world.transforms.insert(entity, spawn_transform);
        world.movements.insert(entity, MovementComponent::new());
        world.jumps.insert(entity, JumpComponent::default());
        world.inputs.insert(entity, InputComponent::default());
        world.physics.insert(
            entity,
            PhysicsComponent {
                rigidbody: rigidbody_handle,
                collider: Some(collider_handle),
            },
        );
        world.meshes.insert(
            entity,
            MeshComponent {
                mesh: Rc::new(mesh),
                pipeline: Pipeline::Standard,
                instance_buffer: None,
                num_instances: 1,
                tile_scale: 4.0,
                enable_dissolve: false,
                enable_snow_light: false,
            },
        );
        world.player_tags.insert(entity, ());
        world.state_machines.insert(entity, state_machine);
        world.names.insert(entity, "Player".to_string());

        let outer_angle = PI / 2.0 * 0.9;
        world.spotlights.insert(
            entity,
            SpotlightComponent::new(
                Vec3::new(1.0, 2.5, 1.0),
                Vec3::new(0.0, -1.0, 0.0),
                100.0,
                outer_angle * 0.5,
                outer_angle,
            ),
        );

        Ok(entity)
    }
}
