use std::time::Instant;

/// Result of interpolation calculation for one frame
#[derive(Debug, Clone)]
pub struct Interpolated {
    /// Position within current beat (0.0 = start of beat, 1.0 = end of beat)
    pub position_in_beat: f32,
    /// True if a beat boundary occurred since the last frame
    pub beat_boundary: bool,
}

/// Handles interpolation of time-based values between OSC updates
#[derive(Debug)]
pub struct Interpolation {
    /// The calculated, wrapped position from the previous frame.
    /// Used to detect beat boundary crossings.
    last_calculated_position: f32,
}

impl Interpolation {
    pub fn new(_current_time: Instant) -> Self {
        Self {
            // Initialize to 0.0, it will be correctly updated on the first call to `update`.
            last_calculated_position: 0.0,
        }
    }

    /// Update interpolation with current state  and return interpolated values
    pub fn update<const COLS: usize, const ROWS: usize>(
        &mut self,
        state: &osc::State<COLS, ROWS>,
        current_time: Instant,
    ) -> Interpolated {
        // The OSC state is the source of truth for the base position and tempo.
        let base_position = state.metronome_position;
        let base_timestamp = state.metronome_timestamp;
        let tempo = state.tempo;

        // If tempo is not positive, we cannot calculate a beat duration.
        // Return the last known position without trying to interpolate.
        if tempo <= 0.0 {
            return Interpolated {
                position_in_beat: base_position,
                beat_boundary: false,
            };
        }

        let beat_duration_secs = 60.0 / tempo;

        // Calculate how much time has passed since the last OSC update's timestamp.
        // This is the duration we need to interpolate over. `saturating_duration_since`
        // prevents panics if current_time is somehow earlier than the base_timestamp.
        let interpolation_duration = current_time.saturating_duration_since(base_timestamp);
        let progress = interpolation_duration.as_secs_f32() / beat_duration_secs;

        // Calculate the new theoretical position by adding the progress.
        let new_raw_position = base_position + progress;

        // Wrap the position to the [0.0, 1.0) range.
        let final_position = new_raw_position.fract();

        // Detect a beat boundary.
        // This occurs when the new position appears to have "jumped back" by a large
        // amount (more than 0.5), which indicates it has wrapped around past 1.0.
        // This handles both interpolation and late OSC corrections robustly.
        let beat_boundary = self.last_calculated_position - final_position > 0.5;

        // Store the new position for the next frame's calculation.
        self.last_calculated_position = final_position;

        Interpolated {
            position_in_beat: final_position,
            beat_boundary,
        }
    }
}

#[cfg(test)]
mod interpolation_without_state_update_tests {
    use super::*;
    use std::time::{Duration, Instant};

    #[test]
    fn test_simple_forward_interpolation() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.2;
        state.metronome_timestamp = start_time;

        // First update at start time - should return the received position
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.2).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update 100ms later with same OSC state (no new OSC update)
        let elapsed_duration = Duration::from_millis(100);
        let later_time = start_time + elapsed_duration;
        let result2 = interpolation.update(&state, later_time);

        // Calculate expected position
        // At 120 BPM: beat_duration = 60.0 / 120.0 = 0.5 seconds
        // elapsed = 0.1 seconds = 0.1 / 0.5 = 0.2 beat progress
        // expected_position = 0.2 + 0.2 = 0.4
        let expected_position = 0.4;
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);
        assert!(!result2.beat_boundary);
    }

    #[test]
    fn test_interpolation_with_incremented_time_no_beat() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.2;
        state.metronome_timestamp = start_time;

        // First update at start time
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.2).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update 100ms later with same OSC state (no new OSC update)
        let elapsed_duration = Duration::from_millis(100);
        let later_time = start_time + elapsed_duration;
        let result2 = interpolation.update(&state, later_time);

        // Expected position: 0.2 + (0.1 / 0.5) = 0.4
        let expected_position = 0.4;
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);
        assert!(!result2.beat_boundary);

        // Third update with small time increment (1ms later) - should be nearly identical
        let slightly_later_time = later_time + Duration::from_millis(1);
        let result3 = interpolation.update(&state, slightly_later_time);

        // Position should advance only slightly: 1ms / 500ms = 0.002
        let expected_position_3 = 0.4 + 0.002;
        assert!((result3.position_in_beat - expected_position_3).abs() < 0.001);
        assert!(!result3.beat_boundary);
    }

    #[test]
    fn test_interpolation_with_identical_timestamps_no_beat() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.2;
        state.metronome_timestamp = start_time;

        // First update at start time
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.2).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update 100ms later with same OSC state (no new OSC update)
        let elapsed_duration = Duration::from_millis(100);
        let later_time = start_time + elapsed_duration;
        let result2 = interpolation.update(&state, later_time);

        // Expected position: 0.2 + (0.1 / 0.5) = 0.4
        let expected_position = 0.4;
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);
        assert!(!result2.beat_boundary);

        // Third update with identical parameters as result2 - should be identical
        let result3 = interpolation.update(&state, later_time);
        assert!((result3.position_in_beat - result2.position_in_beat).abs() < 0.001);
        assert_eq!(result3.beat_boundary, result2.beat_boundary);
    }

    #[test]
    fn test_beat_boundary_detected_on_position_wraparound() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.8;
        state.metronome_timestamp = start_time;

        // First update at start time
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.8).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update 150ms later - should cross beat boundary
        // At 120 BPM: beat_duration = 0.5 seconds
        // Time to reach boundary from 0.8: (1.0 - 0.8) * 0.5 = 0.1 seconds = 100ms
        // Extra time after boundary: 150ms - 100ms = 50ms
        // Position after boundary: 50ms / 500ms = 0.1
        let elapsed_duration = Duration::from_millis(150);
        let later_time = start_time + elapsed_duration;
        let result2 = interpolation.update(&state, later_time);

        assert!(result2.beat_boundary); // Should detect boundary crossing
        assert!((result2.position_in_beat - 0.1).abs() < 0.001); // Wrapped around
    }

    #[test]
    fn test_interpolation_with_incremented_time_with_beat() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.8;
        state.metronome_timestamp = start_time;

        // First update at start time
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.8).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update 150ms later - should cross beat boundary
        let elapsed_duration = Duration::from_millis(150);
        let later_time = start_time + elapsed_duration;
        let result2 = interpolation.update(&state, later_time);

        assert!(result2.beat_boundary); // Should detect boundary crossing
        assert!((result2.position_in_beat - 0.1).abs() < 0.001); // Wrapped around

        // Third update with small time increment (1ms later) - should NOT detect boundary again
        let slightly_later_time = later_time + Duration::from_millis(1);
        let result3 = interpolation.update(&state, slightly_later_time);

        assert!(!result3.beat_boundary); // Boundary already detected, don't fire again
                                         // Position should advance only slightly: 1ms / 500ms = 0.002
        let expected_position_3 = 0.1 + 0.002;
        assert!((result3.position_in_beat - expected_position_3).abs() < 0.001);
    }

    #[test]
    fn test_interpolation_with_identical_timestamps_with_beat() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.8;
        state.metronome_timestamp = start_time;

        // First update at start time
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.8).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update 150ms later - should cross beat boundary
        let elapsed_duration = Duration::from_millis(150);
        let later_time = start_time + elapsed_duration;
        let result2 = interpolation.update(&state, later_time);

        assert!(result2.beat_boundary); // Should detect boundary crossing
        assert!((result2.position_in_beat - 0.1).abs() < 0.001); // Wrapped around

        // Third update with identical parameters - should NOT detect boundary again
        let result3 = interpolation.update(&state, later_time);

        assert!(!result3.beat_boundary); // Boundary already detected, don't fire again
        assert!((result3.position_in_beat - result2.position_in_beat).abs() < 0.001);
        // Same position
    }
}

#[cfg(test)]
mod state_update_tests {
    use super::*;
    use std::time::{Duration, Instant};

    #[test]
    fn test_osc_update_matches_interpolated_position_exactly() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.2;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.2).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update 100ms later with same OSC state (interpolation only)
        let elapsed_duration = Duration::from_millis(100);
        let later_time = start_time + elapsed_duration;
        let result2 = interpolation.update(&state, later_time);

        // At 120 BPM: beat_duration = 0.5 seconds
        // elapsed = 0.1 seconds = 0.1 / 0.5 = 0.2 beat progress
        // expected_position = 0.2 + 0.2 = 0.4
        let expected_position = 0.4;
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);
        assert!(!result2.beat_boundary);

        // Third update: OSC update arrives at same time with exact matching position
        state.metronome_position = 0.4; // Matches our interpolated position
        state.metronome_timestamp = later_time; // Update timestamp to current time
        let result3 = interpolation.update(&state, later_time);

        // Should maintain the same position smoothly, no beat boundary
        assert!((result3.position_in_beat - 0.4).abs() < 0.001);
        assert!(!result3.beat_boundary);

        // Fourth update: Small time increment after OSC sync
        let slightly_later_time = later_time + Duration::from_millis(10);
        let result4 = interpolation.update(&state, slightly_later_time);

        // Should continue interpolating from the OSC-synchronized position
        // 10ms = 0.01 seconds = 0.01 / 0.5 = 0.02 beat progress
        let expected_position_4 = 0.4 + 0.02;
        assert!((result4.position_in_beat - expected_position_4).abs() < 0.001);
        assert!(!result4.beat_boundary);
    }

    #[test]
    fn test_osc_update_arrives_slightly_late_no_beat() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.3;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.3).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // We expect to interpolate for 100ms to reach position 0.5
        // But OSC update arrives 20ms late (timestamp from 80ms ago) with position 0.46
        let late_osc_time = start_time + Duration::from_millis(80);
        let current_time = start_time + Duration::from_millis(100);

        // At 120 BPM: beat_duration = 0.5 seconds
        // At 80ms: expected position = 0.3 + (0.08 / 0.5) = 0.3 + 0.16 = 0.46
        // OSC reports 0.46 (matches expectation but arrives late)
        state.metronome_position = 0.46;
        state.metronome_timestamp = late_osc_time; // OSC timestamp is 80ms (20ms ago)

        // Update called at 100ms with OSC data from 80ms
        let result2 = interpolation.update(&state, current_time);

        // Should adjust to OSC truth plus interpolation for the 20ms difference
        // From OSC position 0.46 at 80ms, advance 20ms: 0.46 + (0.02 / 0.5) = 0.46 + 0.04 = 0.50
        let expected_position = 0.50;
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);
        assert!(!result2.beat_boundary); // No beat boundary in this scenario

        // Third update: Continue normally from corrected baseline
        let later_time = current_time + Duration::from_millis(60);
        let result3 = interpolation.update(&state, later_time);

        // Should continue interpolating from the corrected position
        // 60ms = 0.06 seconds = 0.06 / 0.5 = 0.12 beat progress
        let expected_position_3 = 0.50 + 0.12; // = 0.62
        assert!((result3.position_in_beat - expected_position_3).abs() < 0.001);
        assert!(!result3.beat_boundary);
    }

    #[test]
    fn test_osc_update_arrives_late_confirms_detected_boundary() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.9;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.9).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update: Interpolate past beat boundary (should detect it)
        let boundary_cross_time = start_time + Duration::from_millis(80);
        let result2 = interpolation.update(&state, boundary_cross_time);

        // At 120 BPM: beat_duration = 0.5 seconds
        // At 80ms: position = 0.9 + (0.08 / 0.5) = 0.9 + 0.16 = 1.06 → wraps to 0.06
        assert!(result2.beat_boundary); // Should detect boundary crossing
        assert!((result2.position_in_beat - 0.06).abs() < 0.001);

        // Third update: Late OSC arrives confirming we're in next beat
        let late_osc_time = start_time + Duration::from_millis(70); // 10ms ago
        let current_time = start_time + Duration::from_millis(90);

        // OSC confirms we're in next beat with position 0.04 at 70ms
        state.metronome_position = 0.04;
        state.metronome_timestamp = late_osc_time;

        let result3 = interpolation.update(&state, current_time);

        // Should NOT detect beat boundary again (already detected)
        assert!(!result3.beat_boundary);

        // Should show current position: OSC 0.04 at 70ms + 20ms interpolation
        // 20ms = 0.02 seconds = 0.02 / 0.5 = 0.04 beat progress
        let expected_position = 0.04 + 0.04; // = 0.08
        assert!((result3.position_in_beat - expected_position).abs() < 0.001);
    }

    #[test]
    fn test_osc_update_arrives_late_shows_missed_boundary() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.85;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.85).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update: Interpolate but don't cross boundary yet
        let no_boundary_time = start_time + Duration::from_millis(60);
        let result2 = interpolation.update(&state, no_boundary_time);

        // At 120 BPM: beat_duration = 0.5 seconds
        // At 60ms: position = 0.85 + (0.06 / 0.5) = 0.85 + 0.12 = 0.97
        assert!(!result2.beat_boundary); // Should NOT detect boundary yet
        assert!((result2.position_in_beat - 0.97).abs() < 0.001);

        // Third update: Late OSC arrives showing we're actually in next beat
        let late_osc_time = start_time + Duration::from_millis(70); // 20ms ago
        let current_time = start_time + Duration::from_millis(90);

        // OSC shows we're at position 0.1 in next beat, meaning boundary was crossed
        state.metronome_position = 0.1;
        state.metronome_timestamp = late_osc_time;

        let result3 = interpolation.update(&state, current_time);

        // Should retroactively detect the missed beat boundary
        assert!(result3.beat_boundary);

        // Should show current position: OSC 0.1 at 70ms + 20ms interpolation
        // 20ms = 0.02 seconds = 0.02 / 0.5 = 0.04 beat progress
        let expected_position = 0.1 + 0.04; // = 0.14
        assert!((result3.position_in_beat - expected_position).abs() < 0.001);
    }

    #[test]
    fn test_osc_update_arrives_late_contradicts_detected_boundary() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.92;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.92).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Second update: Interpolate past beat boundary (should detect it)
        let boundary_cross_time = start_time + Duration::from_millis(90);
        let result2 = interpolation.update(&state, boundary_cross_time);

        // At 120 BPM: beat_duration = 0.5 seconds
        // At 90ms: position = 0.92 + (0.09 / 0.5) = 0.92 + 0.18 = 1.10 → wraps to 0.10
        assert!(result2.beat_boundary); // Should detect boundary crossing
        assert!((result2.position_in_beat - 0.10).abs() < 0.001);

        // Third update: Late OSC arrives contradicting our boundary detection
        let late_osc_time = start_time + Duration::from_millis(80); // 20ms ago
        let current_time = start_time + Duration::from_millis(100);

        // OSC shows we're still at position 0.98 in same beat (boundary not yet crossed)
        state.metronome_position = 0.98;
        state.metronome_timestamp = late_osc_time;

        let result3 = interpolation.update(&state, current_time);

        // Should trust OSC data and NOT report beat boundary for this update
        // (The previous boundary detection was premature/incorrect)
        assert!(!result3.beat_boundary);

        // Should show current position: OSC 0.98 at 80ms + 20ms interpolation
        // 20ms = 0.02 seconds = 0.02 / 0.5 = 0.04 beat progress
        let expected_position = 0.98 + 0.04; // = 1.02 → wraps to 0.02
        let wrapped_position = if expected_position >= 1.0 {
            expected_position - 1.0
        } else {
            expected_position
        };
        assert!((result3.position_in_beat - wrapped_position).abs() < 0.001);
    }

    #[test]
    fn test_osc_update_arrives_very_late() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.2;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.2).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // OSC update arrives extremely late (600ms = more than one beat duration of 500ms)
        let very_late_osc_time = start_time + Duration::from_millis(200); // OSC timestamp
        let current_time = start_time + Duration::from_millis(800); // Current time (600ms later)

        // At 120 BPM: beat_duration = 0.5 seconds = 500ms
        // Gap of 600ms is more than one full beat, should trigger resync
        state.metronome_position = 0.6;
        state.metronome_timestamp = very_late_osc_time;

        let result2 = interpolation.update(&state, current_time);

        // For very late updates, should do complete resync rather than huge interpolation
        // Should base position on OSC data and the time difference, but handle gracefully
        // 600ms gap = 600ms / 500ms = 1.2 beats = 1 full beat + 0.2 beat
        // From OSC position 0.6, advance 0.2 beats: 0.6 + 0.2 = 0.8
        let expected_position = 0.8;
        assert!((result2.position_in_beat - expected_position).abs() < 0.1); // Looser tolerance for edge case

        // May or may not detect beat boundary depending on implementation strategy
        // The key is that it shouldn't crash or produce wildly incorrect results

        // Third update: Should continue normally after resync
        let later_time = current_time + Duration::from_millis(50);
        let result3 = interpolation.update(&state, later_time);

        // Should continue interpolating smoothly from the resynced position
        // 50ms = 0.05 seconds = 0.05 / 0.5 = 0.1 beat progress
        let expected_position_3 = result2.position_in_beat + 0.1;
        if expected_position_3 >= 1.0 {
            // Handle wrap-around
            assert!((result3.position_in_beat - (expected_position_3 - 1.0)).abs() < 0.1);
        } else {
            assert!((result3.position_in_beat - expected_position_3).abs() < 0.1);
        }
    }

    #[test]
    fn test_osc_update_arrives_slightly_early_no_beat() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.2;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.2).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // We expect to interpolate for 100ms to reach position 0.4
        // But OSC update arrives 10ms early (at 90ms) with position 0.36
        let early_time = start_time + Duration::from_millis(90);
        let current_time = start_time + Duration::from_millis(100);

        // At 120 BPM: beat_duration = 0.5 seconds
        // At 90ms: expected position = 0.2 + (0.09 / 0.5) = 0.2 + 0.18 = 0.38
        // But let's say OSC reports 0.36 (slightly behind due to processing delays)
        state.metronome_position = 0.36;
        state.metronome_timestamp = early_time; // OSC timestamp is 90ms

        // Update called at 100ms with OSC data from 90ms
        let result2 = interpolation.update(&state, current_time);

        // Should adjust to OSC truth plus interpolation for the 10ms difference
        // From OSC position 0.36 at 90ms, advance 10ms: 0.36 + (0.01 / 0.5) = 0.36 + 0.02 = 0.38
        let expected_position = 0.38;
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);
        assert!(!result2.beat_boundary); // No beat boundary in this scenario

        // Third update: Continue normally from corrected baseline
        let later_time = current_time + Duration::from_millis(50);
        let result3 = interpolation.update(&state, later_time);

        // Should continue interpolating from the corrected position
        // 50ms = 0.05 seconds = 0.05 / 0.5 = 0.1 beat progress
        let expected_position_3 = 0.38 + 0.1; // = 0.48
        assert!((result3.position_in_beat - expected_position_3).abs() < 0.001);
        assert!(!result3.beat_boundary);
    }

    #[test]
    fn test_osc_update_arrives_slightly_early_with_past_beat() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.8;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.8).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // We're going to interpolate for 80ms, which should put us at position 0.96
        // But OSC update arrives showing we're actually at position 0.05 in the next beat
        // This means the beat boundary was crossed somewhere in between
        let update_time = start_time + Duration::from_millis(80);
        let current_time = start_time + Duration::from_millis(90);

        // At 120 BPM: beat_duration = 0.5 seconds
        // Expected interpolated position at 80ms: 0.8 + (0.08 / 0.5) = 0.8 + 0.16 = 0.96
        // But OSC shows position 0.05, meaning we crossed beat boundary and are 0.05 into next beat
        state.metronome_position = 0.05;
        state.metronome_timestamp = update_time; // OSC timestamp is 80ms

        // Update called at 90ms with OSC data from 80ms
        let result2 = interpolation.update(&state, current_time);

        // Should detect that a beat boundary was crossed (based on OSC position being < previous)
        assert!(result2.beat_boundary);

        // Should show current position: OSC position 0.05 at 80ms + 10ms interpolation
        // 10ms = 0.01 seconds = 0.01 / 0.5 = 0.02 beat progress
        let expected_position = 0.05 + 0.02; // = 0.07
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);

        // Third update: Continue normally, should NOT detect beat boundary again
        let later_time = current_time + Duration::from_millis(50);
        let result3 = interpolation.update(&state, later_time);

        // Should continue interpolating from the corrected position
        // 50ms = 0.05 seconds = 0.05 / 0.5 = 0.1 beat progress
        let expected_position_3 = 0.07 + 0.1; // = 0.17
        assert!((result3.position_in_beat - expected_position_3).abs() < 0.001);
        assert!(!result3.beat_boundary); // No boundary on this update
    }

    #[test]
    fn test_osc_update_arrives_slightly_early_with_beat_now() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.85;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.85).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // We're going to interpolate for 60ms, which should put us at position 0.97
        // But OSC update arrives 10ms early showing position 0.0 (beat boundary at OSC time)
        let osc_time = start_time + Duration::from_millis(60);
        let current_time = start_time + Duration::from_millis(70);

        // At 120 BPM: beat_duration = 0.5 seconds
        // Expected interpolated position at 60ms: 0.85 + (0.06 / 0.5) = 0.85 + 0.12 = 0.97
        // But OSC shows position 0.0, meaning beat boundary occurred at the OSC timestamp
        state.metronome_position = 0.0;
        state.metronome_timestamp = osc_time; // OSC timestamp is 60ms (10ms ago)

        // Update called at 70ms with OSC data from 60ms
        let result2 = interpolation.update(&state, current_time);

        // Should detect beat boundary (OSC shows we hit 0.0 at its timestamp)
        assert!(result2.beat_boundary);

        // Should show current position: OSC position 0.0 at 60ms + 10ms interpolation
        // 10ms = 0.01 seconds = 0.01 / 0.5 = 0.02 beat progress
        let expected_position = 0.0 + 0.02; // = 0.02
        assert!((result2.position_in_beat - expected_position).abs() < 0.001);

        // Third update: Continue normally, should NOT detect beat boundary again
        let later_time = current_time + Duration::from_millis(40);
        let result3 = interpolation.update(&state, later_time);

        // Should continue interpolating from the corrected position
        // 40ms = 0.04 seconds = 0.04 / 0.5 = 0.08 beat progress
        let expected_position_3 = 0.02 + 0.08; // = 0.10
        assert!((result3.position_in_beat - expected_position_3).abs() < 0.001);
        assert!(!result3.beat_boundary); // No boundary on this update

        // Fourth update: Verify smooth continuation
        let even_later_time = later_time + Duration::from_millis(25);
        let result4 = interpolation.update(&state, even_later_time);

        // 25ms = 0.025 seconds = 0.025 / 0.5 = 0.05 beat progress
        let expected_position_4 = 0.10 + 0.05; // = 0.15
        assert!((result4.position_in_beat - expected_position_4).abs() < 0.001);
        assert!(!result4.beat_boundary);
    }

    #[test]
    fn test_osc_update_at_beat_boundary_exact_match() {
        let start_time = Instant::now();
        let mut interpolation = Interpolation::new(start_time);

        // Create initial state with 120 BPM, starting near end of beat
        let mut state = osc::State::<5, 5>::new(120.0);
        state.metronome_position = 0.9;
        state.metronome_timestamp = start_time;

        // First update at start time - establish baseline
        let result1 = interpolation.update(&state, start_time);
        assert!((result1.position_in_beat - 0.9).abs() < 0.001);
        assert!(!result1.beat_boundary);

        // Calculate when boundary should occur
        // At 120 BPM: beat_duration = 0.5 seconds
        // Time to reach boundary from 0.9: (1.0 - 0.9) * 0.5 = 0.05 seconds = 50ms
        let boundary_time = start_time + Duration::from_millis(50);

        // Second update: OSC update arrives exactly at calculated boundary time
        state.metronome_position = 0.0; // Confirms we've crossed to new beat
        state.metronome_timestamp = boundary_time;
        let result2 = interpolation.update(&state, boundary_time);

        // Should detect beat boundary and show position 0.0
        assert!(result2.beat_boundary);
        assert!((result2.position_in_beat - 0.0).abs() < 0.001);

        // Third update: Continue 50ms later (should be at position 0.1 in new beat)
        let later_time = boundary_time + Duration::from_millis(50);
        let result3 = interpolation.update(&state, later_time);

        // Should NOT detect beat boundary again, and should be at position 0.1
        assert!(!result3.beat_boundary);
        let expected_position = 0.1; // 50ms / 500ms = 0.1
        assert!((result3.position_in_beat - expected_position).abs() < 0.001);

        // Fourth update: Small increment to verify smooth continuation
        let slightly_later_time = later_time + Duration::from_millis(10);
        let result4 = interpolation.update(&state, slightly_later_time);

        // Should continue smoothly without boundary detection
        assert!(!result4.beat_boundary);
        let expected_position_4 = 0.1 + 0.02; // 10ms = 0.02 beat progress
        assert!((result4.position_in_beat - expected_position_4).abs() < 0.001);
    }
}
