use crate::ecs::components::{AnimationBlend, Animator, BoneJoint, Transform};
use crate::ecs::resources::{AnimationClipAssets, SkeletonAssets};
use archetype_ecs::{Entity, World};
use glam::{Quat, Vec3};

/// System for blending between two animation clips
pub fn animation_blending_system(world: &mut World, dt: f32) {
    let skeleton_assets = match world.resource::<SkeletonAssets>() {
        Some(assets) => assets.clone(),
        None => return,
    };

    let clip_assets = match world.resource::<AnimationClipAssets>() {
        Some(assets) => assets.clone(),
        None => return,
    };

    let mut bone_updates = Vec::new();
    let mut completions = Vec::new();

    // Query entities with both Animator and AnimationBlend
    // We update Animator.player.time HERE to avoid duplicate mutable borrows
    for (entity, animator, blend) in
        world.query_mut::<(Entity, &mut Animator, &mut AnimationBlend)>()
    {
        // Advance blend time
        blend.blend_time += dt;
        let weight = blend.weight();

        // Advance playback time via player
        if let Some(clip_to) = clip_assets.get(blend.to_clip) {
            animator.player.update_constrained(dt, clip_to.duration);
        } else {
            animator.player.update(dt);
        }

        if !animator.player.is_playing {
            continue;
        }

        let skeleton = match skeleton_assets.get(animator.skeleton) {
            Some(s) => s,
            None => continue,
        };

        let clip_from = match clip_assets.get(blend.from_clip) {
            Some(c) => c,
            None => continue,
        };

        let clip_to = match clip_assets.get(blend.to_clip) {
            Some(c) => c,
            None => continue,
        };

        // Sample poses from both clips at current player time
        let mut blended_transforms = Vec::with_capacity(skeleton.bones.len());

        let from_transforms = animator.player.sample(clip_from);
        let to_transforms = animator.player.sample(clip_to);

        for bone in &skeleton.bones {
            let pose_from = from_transforms.get(&bone.name).cloned().unwrap_or((
                Vec3::ZERO,
                Quat::IDENTITY,
                Vec3::ONE,
            ));

            let pose_to = to_transforms.get(&bone.name).cloned().unwrap_or((
                Vec3::ZERO,
                Quat::IDENTITY,
                Vec3::ONE,
            ));

            // Blend poses
            let pos = pose_from.0.lerp(pose_to.0, weight);
            let rot = pose_from.1.slerp(pose_to.1, weight);
            let scale = pose_from.2.lerp(pose_to.2, weight);

            blended_transforms.push((pos, rot, scale));
        }

        bone_updates.push((animator.skeleton, blended_transforms));

        // Check if blend is finished
        if blend.blend_time >= blend.blend_duration {
            completions.push((entity, blend.to_clip));
        }
    }

    // Apply updates to bones
    for (bone_joint, transform) in world.query_mut::<(&BoneJoint, &mut Transform)>() {
        for (skeleton_handle, bone_transforms) in &bone_updates {
            if bone_joint.skeleton == *skeleton_handle {
                if let Some((pos, rot, scale)) = bone_transforms.get(bone_joint.bone_index) {
                    transform.position = *pos;
                    transform.rotation = *rot;
                    transform.scale = *scale;
                }
            }
        }
    }

    // Process completions: update current_clip and remove blend component
    for (entity, to_clip) in completions {
        if let Some(animator) = world.get_component_mut::<Animator>(entity) {
            animator.current_clip = to_clip;
        }
        // Properly remove the AnimationBlend component since the bug is fixed in archetype_ecs 1.1.7
        let _ = world.remove_component::<AnimationBlend>(entity);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::{AnimationClip, InterpolationMode, Keyframe, Skeleton, TransformTrack};
    use crate::ecs::resources::{AnimationClipAssets, SkeletonAssets};
    use std::collections::HashMap;

    fn create_test_skeleton() -> Skeleton {
        let mut skeleton = Skeleton::new();
        skeleton.add_bone("Root".to_string(), None, glam::Mat4::IDENTITY);
        skeleton
    }

    fn create_clip(name: &str, val: f32) -> AnimationClip {
        let mut bone_tracks = HashMap::new();
        let mut track = TransformTrack::new();
        track.positions = vec![
            Keyframe {
                time: 0.0,
                value: glam::Vec3::new(val, 0.0, 0.0),
                interpolation: InterpolationMode::Linear,
            },
            Keyframe {
                time: 1.0,
                value: glam::Vec3::new(val, 0.0, 0.0),
                interpolation: InterpolationMode::Linear,
            },
        ];
        bone_tracks.insert("Root".to_string(), track);
        AnimationClip::new(name.to_string(), 1.0, bone_tracks).expect("Clip creation failed")
    }

    #[test]
    fn test_blending_interpolates_poses() {
        let mut world = World::new();

        let mut skeleton_assets = SkeletonAssets::new();
        let mut clip_assets = AnimationClipAssets::new();

        let skeleton_handle = skeleton_assets.add(create_test_skeleton());
        let handle_a = clip_assets.add(create_clip("A", 0.0));
        let handle_b = clip_assets.add(create_clip("B", 10.0));

        world.insert_resource(skeleton_assets);
        world.insert_resource(clip_assets);

        let animator = Animator::new(skeleton_handle, handle_a);
        let mut blend = AnimationBlend::new(handle_a, handle_b, 1.0);
        blend.blend_time = 0.5;

        let _entity = world.spawn((animator, blend));
        world.spawn((
            BoneJoint {
                bone_index: 0,
                skeleton: skeleton_handle,
            },
            Transform::default(),
        ));

        animation_blending_system(&mut world, 0.0);

        let mut found = false;
        for (_, transform) in world.query_mut::<(&BoneJoint, &Transform)>() {
            assert!((transform.position.x - 5.0).abs() < 0.001);
            found = true;
        }
        assert!(found, "No bone updated");
    }

    #[test]
    fn test_blending_completes_properly() {
        let mut world = World::new();

        let mut skeleton_assets = SkeletonAssets::new();
        let mut clip_assets = AnimationClipAssets::new();

        let skeleton_handle = skeleton_assets.add(create_test_skeleton());
        let handle_a = clip_assets.add(create_clip("A", 0.0));
        let handle_b = clip_assets.add(create_clip("B", 10.0));

        world.insert_resource(skeleton_assets);
        world.insert_resource(clip_assets);

        let animator = Animator::new(skeleton_handle, handle_a);
        let mut blend = AnimationBlend::new(handle_a, handle_b, 1.0);
        blend.blend_time = 0.9;

        let entity = world.spawn((animator, blend));
        world.spawn((
            BoneJoint {
                bone_index: 0,
                skeleton: skeleton_handle,
            },
            Transform::default(),
        ));

        animation_blending_system(&mut world, 0.2);

        // Verify: Check animator first, then blend removal
        let animator_comp = world.get_component::<Animator>(entity);
        assert!(animator_comp.is_some(), "Animator should still exist");
        assert_eq!(
            animator_comp.unwrap().current_clip,
            handle_b,
            "Clip should be updated to target"
        );

        let blend_comp = world.get_component::<AnimationBlend>(entity);
        assert!(
            blend_comp.is_none(),
            "AnimationBlend should be removed (bug fixed in v1.1.7)"
        );
    }
}
