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

/// Core animation sampling system
///
/// This system:
/// 1. Advances playback time for all Animator components
/// 2. Samples animation clips at the current time
/// 3. Updates BoneJoint Transform components with sampled values
///
/// # Performance
/// - O(n) where n = number of animated bones
/// - Uses binary search for keyframe sampling (O(log k) where k = keyframes per bone)
/// - Allocates minimal memory per frame
///
/// # System Ordering
/// Must run BEFORE gameplay systems that might read bone transforms,
/// but AFTER any systems that modify Animator playback state.
pub fn animation_sampling_system(world: &mut World, dt: f32) {
    // Get asset storage - fail gracefully if not available
    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 updates = Vec::new();

    // Identify entities that are currently blending to skip them in this system
    let mut blending_entities = HashSet::new();
    for (entity, _) in world.query_mut::<(Entity, &AnimationBlend)>() {
        blending_entities.insert(entity);
    }

    // Query all Animator components
    for (entity, animator) in world.query_mut::<(Entity, &mut Animator)>() {
        // Skip if this entity is currently blending (blending system handles it)
        if blending_entities.contains(&entity) {
            continue;
        }

        // Advance time
        // Get clip duration for constraint
        if let Some(clip) = clip_assets.get(animator.current_clip) {
            animator.player.update_constrained(dt, clip.duration);
        } else {
            // If clip is not found, we can't constrain, so just advance time
            animator.player.update(dt);
        }

        if !animator.player.is_playing {
            continue;
        }

        // Get the animation clip
        let clip = match clip_assets.get(animator.current_clip) {
            Some(clip) => clip,
            None => continue, // Invalid clip handle
        };

        // Get the skeleton
        let skeleton = match skeleton_assets.get(animator.skeleton) {
            Some(skeleton) => skeleton,
            None => continue, // Invalid skeleton handle
        };

        // Sample transformation via player
        let bone_transforms_map = animator.player.sample(clip);

        // FIXED: Only collect bones that have animation data (sparse update)
        // This preserves manual LocalTransform values on non-animated bones
        let mut bone_transforms: Vec<(usize, (Vec3, Quat, Vec3))> = Vec::new();
        for (bone_name, transform) in bone_transforms_map.iter() {
            // Find bone index in skeleton
            if let Some(bone_idx) = skeleton.bones.iter().position(|b| &b.name == bone_name) {
                bone_transforms.push((bone_idx, *transform));
            }
        }

        updates.push((entity, animator.skeleton, bone_transforms));
    }

    // Apply updates to bone entities (only animated bones)
    for (_animator_entity, skeleton_handle, bone_transforms) in updates {
        // Find all bone entities for this skeleton and update them
        for (bone_joint, transform) in world.query_mut::<(&BoneJoint, &mut Transform)>() {
            if bone_joint.skeleton == skeleton_handle {
                // Find transform for this specific bone (if animated)
                if let Some((_, (pos, rot, scale))) = bone_transforms
                    .iter()
                    .find(|(idx, _)| *idx == bone_joint.bone_index)
                {
                    transform.position = *pos;
                    transform.rotation = *rot;
                    transform.scale = *scale;
                }
                // If not found: bone is not animated, LocalTransform unchanged ✅
            }
        }
    }
}

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

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

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

        let skeleton = create_test_skeleton();
        let skeleton_handle = skeleton_assets.add(skeleton);

        let clip = create_test_clip().unwrap();
        let clip_handle = clip_assets.add(clip);

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

        // Spawn animator
        world.spawn((
            Animator::new(skeleton_handle, clip_handle),
            Transform::default(),
        ));

        // Run system
        animation_sampling_system(&mut world, 0.5);

        // Verify time advanced
        for animator in world.query_mut::<&Animator>() {
            assert_eq!(animator.player.time, 0.5);
        }
    }

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

    fn create_test_clip() -> crate::error::Result<AnimationClip> {
        let mut bone_tracks = HashMap::new();
        let mut hips_track = TransformTrack::new();
        hips_track.positions = vec![
            Keyframe {
                time: 0.0,
                value: glam::Vec3::ZERO,
                interpolation: InterpolationMode::Linear,
            },
            Keyframe {
                time: 1.0,
                value: glam::Vec3::new(0.0, 1.0, 0.0),
                interpolation: InterpolationMode::Linear,
            },
        ];
        bone_tracks.insert("Hips".to_string(), hips_track);
        AnimationClip::new("Test".to_string(), 1.0, bone_tracks)
    }

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

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

        let skeleton = create_test_skeleton();
        let skeleton_handle = skeleton_assets.add(skeleton);

        let clip = create_test_clip().unwrap();
        let clip_handle = clip_assets.add(clip);

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

        // Spawn animator with 2x speed
        let mut animator = Animator::new(skeleton_handle, clip_handle);
        animator.player.speed = 2.0;
        animator.player.is_looping = false; // Disable looping to avoid wrapping
        world.spawn((animator, Transform::default()));

        // Run system with 0.5s delta
        animation_sampling_system(&mut world, 0.5);

        // At 2x speed, 0.5s delta = 1.0s playback time
        for animator in world.query_mut::<&Animator>() {
            assert_eq!(animator.player.time, 1.0);
        }
    }

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

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

        let skeleton = create_test_skeleton();
        let skeleton_handle = skeleton_assets.add(skeleton);

        let clip = create_test_clip().unwrap(); // Duration = 1.0
        let clip_handle = clip_assets.add(clip);

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

        // Spawn animator with looping enabled
        let mut animator = Animator::new(skeleton_handle, clip_handle);
        animator.player.is_looping = true;
        animator.player.time = 0.8; // Start near end
        world.spawn((animator, Transform::default()));

        // Run system with 0.5s delta (should wrap: 0.8 + 0.5 = 1.3 -> 0.3)
        animation_sampling_system(&mut world, 0.5);

        for animator in world.query_mut::<&Animator>() {
            assert!((animator.player.time - 0.3).abs() < 0.001);
        }
    }

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

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

        let skeleton = create_test_skeleton();
        let skeleton_handle = skeleton_assets.add(skeleton);

        let clip = create_test_clip().unwrap(); // Duration = 1.0
        let clip_handle = clip_assets.add(clip);

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

        // Spawn animator with looping disabled
        let mut animator = Animator::new(skeleton_handle, clip_handle);
        animator.player.is_looping = false;
        animator.player.time = 0.8;
        world.spawn((animator, Transform::default()));

        // Run system with 0.5s delta (should clamp: 0.8 + 0.5 = 1.3 -> 1.0)
        animation_sampling_system(&mut world, 0.5);

        for animator in world.query_mut::<&Animator>() {
            assert_eq!(animator.player.time, 1.0);
        }
    }

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

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

        let skeleton = create_test_skeleton();
        let skeleton_handle = skeleton_assets.add(skeleton);

        let clip = create_test_clip().unwrap();
        let clip_handle = clip_assets.add(clip);

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

        // Spawn paused animator
        let mut animator = Animator::new(skeleton_handle, clip_handle);
        animator.player.pause();
        animator.player.time = 0.5;
        world.spawn((animator, Transform::default()));

        // Run system
        animation_sampling_system(&mut world, 0.5);

        // Time should not advance
        for animator in world.query_mut::<&Animator>() {
            assert_eq!(animator.player.time, 0.5);
        }
    }

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

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

        let skeleton = create_test_skeleton();
        let skeleton_handle = skeleton_assets.add(skeleton);

        let clip = create_test_clip().unwrap();
        let clip_handle = clip_assets.add(clip);

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

        // Spawn animator with 0.5x speed (slow motion)
        let mut animator = Animator::new(skeleton_handle, clip_handle);
        animator.player.speed = 0.5;
        world.spawn((animator, Transform::default()));

        // Run system with 1.0s delta
        animation_sampling_system(&mut world, 1.0);

        // At 0.5x speed, 1.0s delta = 0.5s playback time
        for animator in world.query_mut::<&Animator>() {
            assert_eq!(animator.player.time, 0.5);
        }
    }
}
