use super::TriggerGun;
use crate::{
    GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase,
    hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, sounds::PlaySound,
    tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer,
};
use avian3d::prelude::*;
use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use std::f32::consts::PI;

#[derive(Component)]
struct GunProjectile {
    time: f32,
    owner_head: usize,
}

const MAX_SHOT_AGES: f32 = 1.;

#[derive(Resource)]
struct ShotAssets {
    image: Handle<Image>,
    layout: Handle<TextureAtlasLayout>,
}

pub fn plugin(app: &mut App) {
    app.add_systems(OnEnter(GameState::Playing), setup);
    app.add_systems(
        Update,
        (shot_collision, enemy_hit).run_if(in_state(GameState::Playing)),
    );
    app.add_systems(
        FixedUpdate,
        (update, timeout).run_if(in_state(GameState::Playing)),
    );

    global_observer!(app, on_trigger_gun);
}

fn setup(mut commands: Commands, assets: Res<GameAssets>, mut sprite_params: Sprite3dParams) {
    let layout = TextureAtlasLayout::from_grid(UVec2::splat(256), 7, 6, None, None);
    let texture_atlas_layout = sprite_params.atlas_layouts.add(layout);

    commands.insert_resource(ShotAssets {
        image: assets.impact_atlas.clone(),
        layout: texture_atlas_layout,
    });
}

fn enemy_hit(
    mut commands: Commands,
    mut collision_event_reader: EventReader<CollisionStarted>,
    query_shot: Query<&GunProjectile>,
    query_npc: Query<&EnemySpawn>,
    heads_db: Res<HeadsDatabase>,
) {
    for CollisionStarted(e1, e2) in collision_event_reader.read() {
        if !query_shot.contains(*e1) && !query_shot.contains(*e2) {
            continue;
        }
        if !query_npc.contains(*e1) && !query_npc.contains(*e2) {
            continue;
        }

        let (enemy_entity, projectile) = if query_npc.contains(*e1) {
            (*e1, query_shot.get(*e2))
        } else {
            (*e2, query_shot.get(*e1))
        };

        if let Ok(head) = projectile.map(|p| p.owner_head) {
            let damage = heads_db.head_stats(head).damage;
            commands.entity(enemy_entity).trigger(Hit { damage });
        }
    }
}

fn on_trigger_gun(
    trigger: Trigger<TriggerGun>,
    mut commands: Commands,
    query_transform: Query<&Transform>,
    time: Res<Time>,
    mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) {
    let state = trigger.0;

    commands.trigger(PlaySound::Gun);

    let rotation = if let Some(t) = state
        .target
        .and_then(|target| query_transform.get(target).ok())
    {
        Transform::from_translation(state.pos)
            .looking_at(t.translation, Vec3::Y)
            .rotation
    } else {
        state.rot.mul_quat(Quat::from_rotation_y(PI))
    };

    let mut t = Transform::from_translation(state.pos).with_rotation(rotation);
    t.translation += t.forward().as_vec3() * 2.0;

    commands.spawn((
        Name::new("projectile-gun"),
        GunProjectile {
            time: time.elapsed_secs(),
            owner_head: state.head,
        },
        Collider::capsule_endpoints(0.5, Vec3::new(0., 0., 0.), Vec3::new(0., 0., -3.)),
        CollisionLayers::new(
            LayerMask(GameLayer::Projectile.to_bits()),
            LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
        ),
        Sensor,
        CollisionEventsEnabled,
        Visibility::default(),
        t,
        Children::spawn(Spawn(Gizmo {
            handle: gizmo_assets.add({
                let mut g = GizmoAsset::default();
                g.line(Vec3::Z * -2., Vec3::Z * 2., LinearRgba::rgb(0.9, 0.9, 0.));
                g
            }),
            line_config: GizmoLineConfig {
                width: 8.,
                ..default()
            },
            ..default()
        })),
    ));
}

fn update(mut query: Query<&mut Transform, With<GunProjectile>>) {
    for mut transform in query.iter_mut() {
        let forward = transform.forward();
        transform.translation += forward * 2.;
    }
}

fn timeout(mut commands: Commands, query: Query<(Entity, &GunProjectile)>, time: Res<Time>) {
    let current_time = time.elapsed_secs();

    for (e, GunProjectile { time, .. }) in query.iter() {
        if current_time > time + MAX_SHOT_AGES {
            commands.entity(e).despawn();
        }
    }
}

fn shot_collision(
    mut commands: Commands,
    mut collision_event_reader: EventReader<CollisionStarted>,
    query_shot: Query<(&GunProjectile, &Transform)>,
    assets: Res<ShotAssets>,
    mut sprite_params: Sprite3dParams,
) {
    for CollisionStarted(e1, e2) in collision_event_reader.read() {
        if !query_shot.contains(*e1) && !query_shot.contains(*e2) {
            continue;
        }

        let shot_entity = if query_shot.contains(*e1) { *e1 } else { *e2 };

        if let Ok(mut entity) = commands.get_entity(shot_entity) {
            entity.try_despawn();
        } else {
            continue;
        }

        let shot_pos = query_shot.get(shot_entity).unwrap().1.translation;

        let texture_atlas = TextureAtlas {
            layout: assets.layout.clone(),
            index: 0,
        };

        commands
            .spawn(
                Sprite3dBuilder {
                    image: assets.image.clone(),
                    pixels_per_metre: 128.,
                    alpha_mode: AlphaMode::Blend,
                    unlit: true,
                    ..default()
                }
                .bundle_with_atlas(&mut sprite_params, texture_atlas),
            )
            .insert((
                Billboard,
                Transform::from_translation(shot_pos),
                NotShadowCaster,
                AnimationTimer::new(Timer::from_seconds(0.01, TimerMode::Repeating)),
            ));
    }
}
