use crate::{
    GameState, character::HedzCharacter, global_observer, loading_assets::GameAssets,
    utils::billboards::Billboard,
};
#[cfg(feature = "server")]
use crate::{
    ai::Ai,
    character::AnimatedCharacter,
    head::ActiveHead,
    head_drop::HeadDrops,
    heads::{ActiveHeads, HEAD_COUNT, HeadState},
    heads_database::HeadsDatabase,
    hitpoints::{Hitpoints, Kill},
    keys::KeySpawn,
    protocol::PlaySound,
    tb_entities::EnemySpawn,
};
use bevy::{pbr::NotShadowCaster, prelude::*};
#[cfg(feature = "server")]
use lightyear::prelude::{NetworkTarget, Replicate};
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use std::collections::HashMap;

#[derive(Component, Reflect, PartialEq, Serialize, Deserialize)]
#[reflect(Component)]
#[require(HedzCharacter)]
pub struct Npc;

#[derive(Resource, Reflect, Default)]
#[reflect(Resource)]
struct NpcSpawning {
    spawn_index: u32,
}

#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct SpawningBeam(pub f32);

#[cfg(feature = "server")]
#[derive(Event)]
struct OnCheckSpawns;

#[derive(Event)]
pub struct SpawnCharacter(pub Vec3);

pub fn plugin(app: &mut App) {
    app.init_resource::<NpcSpawning>();
    #[cfg(feature = "server")]
    app.add_systems(FixedUpdate, setup.run_if(in_state(GameState::Playing)));
    app.add_systems(Update, update_beams.run_if(in_state(GameState::Playing)));

    #[cfg(feature = "server")]
    global_observer!(app, on_spawn_check);
    global_observer!(app, on_spawn);
}

#[cfg(feature = "server")]
fn setup(mut commands: Commands, mut spawned: Local<bool>) {
    if *spawned {
        return;
    }

    commands.init_resource::<NpcSpawning>();
    commands.trigger(OnCheckSpawns);

    *spawned = true;
}

#[cfg(feature = "server")]
fn on_spawn_check(
    _trigger: Trigger<OnCheckSpawns>,
    mut commands: Commands,
    query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>,
    heads_db: Res<HeadsDatabase>,
    spawning: Res<NpcSpawning>,
) {
    //TODO: move into HeadsDatabase
    let mut names: HashMap<String, usize> = HashMap::default();
    for i in 0..HEAD_COUNT {
        names.insert(heads_db.head_key(i).to_string(), i);
    }

    for (e, spawn, transform) in query.iter() {
        if let Some(order) = spawn.spawn_order
            && order > spawning.spawn_index
        {
            continue;
        }

        let id = names[&spawn.head];
        let mut ecommands = commands.entity(e);
        ecommands
            .insert((
                Hitpoints::new(100),
                Npc,
                ActiveHead(id),
                ActiveHeads::new([
                    Some(HeadState::new(id, heads_db.as_ref())),
                    None,
                    None,
                    None,
                    None,
                ]),
                Replicate::to_clients(NetworkTarget::All),
            ))
            .insert_if(Ai, || !spawn.disable_ai)
            .with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
            .observe(on_kill);
        #[cfg(feature = "server")]
        ecommands.insert(Replicate::to_clients(NetworkTarget::All));

        commands.trigger(SpawnCharacter(transform.translation));
        commands.trigger(PlaySound::Beaming);
    }
}

#[cfg(feature = "server")]
fn on_kill(
    trigger: Trigger<Kill>,
    mut commands: Commands,
    query: Query<(&Transform, &EnemySpawn, &ActiveHead)>,
) {
    let Ok((transform, enemy, head)) = query.get(trigger.target()) else {
        return;
    };

    if let Some(order) = enemy.spawn_order {
        commands.insert_resource(NpcSpawning {
            spawn_index: order + 1,
        });
    }

    commands.trigger(HeadDrops::new(transform.translation, head.0));
    commands.trigger(OnCheckSpawns);

    commands.entity(trigger.target()).despawn();

    if !enemy.key.is_empty() {
        commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
    }
}

fn on_spawn(
    trigger: Trigger<SpawnCharacter>,
    mut commands: Commands,
    assets: Res<GameAssets>,
    time: Res<Time>,
) {
    commands.spawn((
        Transform::from_translation(trigger.event().0 + Vec3::new(0., -2., 0.))
            .with_scale(Vec3::new(1., 40., 1.)),
        Billboard::XZ,
        NotShadowCaster,
        SpawningBeam(time.elapsed_secs()),
        SceneRoot(assets.beaming.clone()),
    ));
}

fn update_beams(
    mut commands: Commands,
    mut query: Query<(Entity, &SpawningBeam, &mut Transform)>,
    time: Res<Time>,
) {
    for (entity, beam, mut transform) in query.iter_mut() {
        let age = time.elapsed_secs() - beam.0;

        transform.scale.x = age.sin() * 2.;

        if age > 3. {
            commands.entity(entity).despawn();
        }
    }
}
