use std::{
    collections::HashMap, error::Error, f32::consts::PI, fmt::Debug, hash::Hash,
    marker::PhantomData,
};

use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use bevy_tts::Tts;
use leafwing_input_manager::prelude::*;

use crate::{
    commands::RunIfExistsExt,
    core::{Angle, Area, CardinalDirection, GlobalTransformExt, Player},
    error::error_handler,
    exploration::{ExplorationFocused, Exploring},
    log::Log,
    pathfinding::Destination,
    utils::target_and_other,
};

#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub enum NavigationAction {
    Move,
    Rotate,
    SnapLeft,
    SnapRight,
    SnapCardinal,
    SnapReverse,
    Sprint,
}

#[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct MaxSpeed(pub f32);

impl Default for MaxSpeed {
    fn default() -> Self {
        MaxSpeed(2.)
    }
}

#[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct RotationSpeed(pub Angle);

impl Default for RotationSpeed {
    fn default() -> Self {
        Self(Angle::Radians(0.))
    }
}

#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct Speed(pub f32);

#[derive(Component, Deref, DerefMut)]
struct SnapTimer(Timer);

impl Default for SnapTimer {
    fn default() -> Self {
        Self(Timer::from_seconds(0.2, false))
    }
}

fn movement_controls<S>(
    mut commands: Commands,
    config: Res<NavigationConfig<S>>,
    time: Res<Time>,
    snap_timers: Res<HashMap<Entity, SnapTimer>>,
    mut query: Query<(
        Entity,
        &ActionState<NavigationAction>,
        &mut Velocity,
        &mut Speed,
        &MaxSpeed,
        Option<&RotationSpeed>,
        &mut Transform,
        Option<&Destination>,
    )>,
    exploration_focused: Query<Entity, With<ExplorationFocused>>,
) where
    S: 'static + Clone + Debug + Eq + Hash + Send + Sync,
{
    for (
        entity,
        actions,
        mut velocity,
        mut speed,
        max_speed,
        rotation_speed,
        mut transform,
        destination,
    ) in &mut query
    {
        let sprinting = actions.pressed(NavigationAction::Sprint);
        let mut cleanup = false;
        if actions.pressed(NavigationAction::Move) {
            if let Some(pair) = actions.clamped_axis_pair(NavigationAction::Move) {
                cleanup = true;
                let direction = pair.xy();
                let forward_backward_movement_factor = if direction.x > 0. {
                    config.forward_movement_factor
                } else if direction.x < 0. {
                    config.backward_movement_factor
                } else {
                    0.
                };
                let movement_factor = if direction.x != 0. && direction.y != 0. {
                    config
                        .strafe_movement_factor
                        .min(forward_backward_movement_factor)
                } else if direction.y != 0. {
                    config.strafe_movement_factor
                } else {
                    forward_backward_movement_factor
                };
                let mut s = if sprinting {
                    **max_speed
                } else {
                    **max_speed / config.sprint_movement_factor
                };
                s *= movement_factor;
                **speed = s;
                let mut v = direction * **speed;
                v = transform
                    .compute_matrix()
                    .transform_vector3(v.extend(0.))
                    .truncate();
                velocity.linvel = v;
            }
        } else if destination.is_none() {
            velocity.linvel = Vec2::ZERO;
        }
        if !snap_timers.contains_key(&entity) {
            if let Some(rotation_speed) = rotation_speed {
                if actions.pressed(NavigationAction::Rotate) {
                    cleanup = true;
                    let delta = rotation_speed.radians()
                        * time.delta_seconds()
                        * actions.clamped_value(NavigationAction::Rotate);
                    transform.rotate(Quat::from_rotation_z(delta));
                }
            } else {
                velocity.angvel = 0.;
            }
        } else {
            velocity.angvel = 0.;
        }
        if cleanup {
            commands.entity(entity).remove::<Destination>();
            commands.entity(entity).remove::<Exploring>();
            for entity in exploration_focused.iter() {
                commands.entity(entity).remove::<ExplorationFocused>();
            }
        }
    }
}

fn snap(
    mut tts: ResMut<Tts>,
    mut snap_timers: ResMut<HashMap<Entity, SnapTimer>>,
    mut query: Query<
        (
            Entity,
            &ActionState<NavigationAction>,
            &mut Transform,
            &mut Velocity,
            &CardinalDirection,
        ),
        With<Player>,
    >,
) -> Result<(), Box<dyn Error>> {
    for (entity, actions, mut transform, mut velocity, direction) in query.iter_mut() {
        if snap_timers.contains_key(&entity) {
            continue;
        } else if actions.pressed(NavigationAction::SnapLeft) {
            snap_timers.insert(entity, SnapTimer::default());
            transform.rotation = Quat::from_rotation_z(match direction {
                CardinalDirection::North => PI,
                CardinalDirection::East => PI / 2.,
                CardinalDirection::South => 0.,
                CardinalDirection::West => -PI / 2.,
            });
            velocity.angvel = 0.;
        } else if actions.pressed(NavigationAction::SnapRight) {
            snap_timers.insert(entity, SnapTimer::default());
            transform.rotation = Quat::from_rotation_z(match direction {
                CardinalDirection::North => 0.,
                CardinalDirection::East => -PI / 2.,
                CardinalDirection::South => PI,
                CardinalDirection::West => PI / 2.,
            });
            velocity.angvel = 0.;
        } else if actions.pressed(NavigationAction::SnapReverse) {
            snap_timers.insert(entity, SnapTimer::default());
            transform.rotate(Quat::from_rotation_z(PI));
            velocity.angvel = 0.;
        } else if actions.pressed(NavigationAction::SnapCardinal) {
            let yaw: Angle = direction.into();
            let yaw = yaw.radians();
            transform.rotation = Quat::from_rotation_z(yaw);
            tts.speak(direction.to_string(), true)?;
            velocity.angvel = 0.;
        }
    }
    Ok(())
}

fn tick_snap_timers(time: Res<Time>, mut snap_timers: ResMut<HashMap<Entity, SnapTimer>>) {
    for timer in snap_timers.values_mut() {
        timer.tick(time.delta());
    }
    snap_timers.retain(|_, v| !v.finished());
}

fn update_direction(
    mut commands: Commands,
    mut query: Query<
        (Entity, &GlobalTransform, Option<&mut CardinalDirection>),
        (With<Player>, Changed<Transform>),
    >,
) {
    for (entity, transform, direction) in query.iter_mut() {
        let yaw = transform.yaw();
        let new_direction: CardinalDirection = yaw.into();
        if let Some(mut direction) = direction {
            if *direction != new_direction {
                *direction = new_direction;
            }
        } else {
            commands.entity(entity).insert(new_direction);
        }
    }
}

fn remove_direction(
    mut commands: Commands,
    removed: RemovedComponents<Transform>,
    directions: Query<&CardinalDirection>,
) {
    for entity in removed.iter() {
        if directions.contains(entity) {
            commands.run_if_exists(entity, |mut entity| {
                entity.remove::<CardinalDirection>();
            });
        }
    }
}

fn speak_direction(
    mut tts: ResMut<Tts>,
    player: Query<
        (&CardinalDirection, ChangeTrackers<CardinalDirection>),
        (With<Player>, Changed<CardinalDirection>),
    >,
) -> Result<(), Box<dyn Error>> {
    if let Ok((direction, change)) = player.get_single() {
        if !change.is_added() {
            let direction: String = (*direction).into();
            tts.speak(direction, true)?;
        }
    }
    Ok(())
}

fn add_speed(mut commands: Commands, query: Query<Entity, (Added<Speed>, Without<Velocity>)>) {
    for entity in query.iter() {
        commands.entity(entity).insert(Velocity {
            linvel: Vec2::ZERO,
            ..default()
        });
    }
}

pub(crate) fn limit_speed(mut query: Query<(&mut Speed, &MaxSpeed)>) {
    for (mut speed, max_speed) in &mut query {
        if **speed > **max_speed {
            **speed = **max_speed;
        }
    }
}

fn remove_speed(removed: RemovedComponents<Speed>, mut query: Query<&mut Velocity>) {
    for entity in removed.iter() {
        if let Ok(mut velocity) = query.get_mut(entity) {
            velocity.linvel = Vec2::ZERO;
        }
    }
}

fn log_area_descriptions<S, A>(
    mut events: EventReader<CollisionEvent>,
    areas: Query<(&Area, Option<&Name>)>,
    players: Query<&Player>,
    config: Res<NavigationConfig<S>>,
    mut log: Query<&mut Log>,
) where
    S: 'static + Send + Sync,
    A: 'static + Send + Sync,
{
    if !config.log_area_descriptions {
        return;
    }
    for event in events.iter() {
        let (entity1, entity2, started) = match event {
            CollisionEvent::Started(collider1, collider2, _) => (collider1, collider2, true),
            CollisionEvent::Stopped(collider1, collider2, _) => (collider1, collider2, false),
        };
        if let Some((area, other)) = target_and_other(*entity1, *entity2, &|v| areas.get(v).is_ok())
        {
            if players.get(other).is_ok() {
                if let Ok((aabb, area_name)) = areas.get(area) {
                    let name = if let Some(name) = area_name {
                        Some(name.to_string())
                    } else if config.describe_undescribed_areas {
                        Some(format!("{}-by-{} area", aabb.extents().x, aabb.extents().y))
                    } else {
                        None
                    };
                    if let Some(name) = name {
                        if let Ok(mut log) = log.get_single_mut() {
                            if started {
                                log.push(format!("Entering {name}."));
                            } else {
                                log.push(format!("Leaving {name}."));
                            }
                        }
                    }
                }
            }
        }
    }
}

#[derive(Clone, Debug)]
pub struct NavigationConfig<S> {
    pub forward_movement_factor: f32,
    pub backward_movement_factor: f32,
    pub strafe_movement_factor: f32,
    pub sprint_movement_factor: f32,
    pub movement_control_states: Vec<S>,
    pub describe_undescribed_areas: bool,
    pub log_area_descriptions: bool,
}

impl<S> Default for NavigationConfig<S> {
    fn default() -> Self {
        Self {
            forward_movement_factor: 1.,
            backward_movement_factor: 1.,
            strafe_movement_factor: 1.,
            sprint_movement_factor: 3.,
            movement_control_states: vec![],
            describe_undescribed_areas: false,
            log_area_descriptions: true,
        }
    }
}

pub struct NavigationPlugin<'a, S, A>(PhantomData<&'a S>, PhantomData<&'a A>);

impl<'a, S, A> Default for NavigationPlugin<'a, S, A> {
    fn default() -> Self {
        Self(PhantomData, PhantomData)
    }
}

impl<S, A> Plugin for NavigationPlugin<'static, S, A>
where
    S: 'static + Clone + Copy + Debug + Eq + Hash + Send + Sync,
    A: Hash + Eq + Copy + Send + Sync,
{
    fn build(&self, app: &mut App) {
        if !app.world.contains_resource::<NavigationConfig<S>>() {
            app.insert_resource(NavigationConfig::<S>::default());
        }
        let config = app
            .world
            .get_resource::<NavigationConfig<S>>()
            .unwrap()
            .clone();
        app.init_resource::<HashMap<Entity, SnapTimer>>()
            .register_type::<MaxSpeed>()
            .register_type::<RotationSpeed>()
            .add_plugin(InputManagerPlugin::<NavigationAction>::default())
            .add_system_to_stage(CoreStage::PreUpdate, update_direction)
            .add_system_to_stage(CoreStage::PostUpdate, remove_direction)
            .add_system(tick_snap_timers)
            .add_system(speak_direction.chain(error_handler))
            .add_system(add_speed)
            .add_system(limit_speed)
            .add_system_to_stage(CoreStage::PostUpdate, remove_speed)
            .add_system_to_stage(CoreStage::PostUpdate, log_area_descriptions::<S, A>);
        const MOVEMENT_CONTROLS: &str = "MOVEMENT_CONTROLS";
        if config.movement_control_states.is_empty() {
            app.add_system(
                movement_controls::<S>
                    .label(MOVEMENT_CONTROLS)
                    .after(limit_speed),
            )
            .add_system(snap.chain(error_handler).before(MOVEMENT_CONTROLS));
        } else {
            let states = config.movement_control_states;
            for state in states {
                app.add_system_set(
                    SystemSet::on_update(state)
                        .with_system(movement_controls::<S>.label(MOVEMENT_CONTROLS))
                        .with_system(snap.chain(error_handler).before(MOVEMENT_CONTROLS)),
                );
            }
        }
    }
}