//! Tests for MpvBackend to prevent regressions
//!
//! These tests are designed to catch common issues like:
//! - Tokio runtime panics when spawning async tasks from std::thread
//! - Position update thread failures
//! - Event emission issues
//!
//! TRACES: UR-003, UR-004 | IR-003 | IT-003, IT-004

#[cfg(test)]
mod tests {
    use crate::utils::lock::MutexSafe;
    use std::sync::{Arc, Mutex};
    use tokio::sync::Mutex as TokioMutex;

    /// Test that simulates the position update thread spawning async tasks
    /// without a Tokio runtime (the bug we just fixed)
    #[test]
    fn test_position_thread_handles_missing_tokio_runtime() {
        use std::sync::atomic::{AtomicBool, Ordering};

        let success = Arc::new(AtomicBool::new(false));
        let success_clone = success.clone();

        // Spawn a regular thread (no Tokio runtime)
        let handle = std::thread::spawn(move || {
            // This simulates what the position update thread does
            // It should handle the case where there's no Tokio runtime

            // Try to get the current Tokio runtime handle
            if let Ok(handle) = tokio::runtime::Handle::try_current() {
                // We have a runtime, use it
                handle.spawn(async move {
                    // Async work here
                });
            } else {
                // No runtime, spawn a new thread with its own runtime
                // This is the fix we applied
                std::thread::spawn(move || {
                    let rt = tokio::runtime::Runtime::new().unwrap();
                    rt.block_on(async move {
                        // Async work here
                        success_clone.store(true, Ordering::SeqCst);
                    });
                });
            }
        });

        handle.join().unwrap();

        // Give the spawned thread time to complete
        std::thread::sleep(std::time::Duration::from_millis(100));

        assert!(
            success.load(Ordering::SeqCst),
            "Should successfully execute async code from std::thread without panicking"
        );
    }

    /// Test that the Tokio runtime fallback pattern works correctly
    #[test]
    fn test_tokio_runtime_fallback_pattern() {
        let counter = Arc::new(Mutex::new(0));
        let counter_clone = counter.clone();

        // Spawn from a regular thread (no runtime)
        let handle = std::thread::spawn(move || {
            if let Ok(handle) = tokio::runtime::Handle::try_current() {
                // Has runtime (shouldn't happen in this test)
                handle.spawn(async move {
                    *counter_clone.lock_safe() += 1;
                });
            } else {
                // No runtime - use fallback (should happen in this test)
                std::thread::spawn(move || {
                    let rt = tokio::runtime::Runtime::new().unwrap();
                    rt.block_on(async move {
                        *counter_clone.lock_safe() += 1;
                    });
                });
            }
        });

        handle.join().unwrap();

        // Wait for async task to complete
        std::thread::sleep(std::time::Duration::from_millis(100));

        let count = *counter.lock_safe();
        assert_eq!(
            count, 1,
            "Fallback pattern should execute async code successfully"
        );
    }

    /// Test that position update logic works in a thread
    #[test]
    fn test_position_update_in_thread() {
        use std::time::Duration;

        let positions = Arc::new(Mutex::new(Vec::new()));
        let positions_clone = positions.clone();

        // Simulate the position update thread
        let handle = std::thread::spawn(move || {
            for i in 0..5 {
                std::thread::sleep(Duration::from_millis(10));

                // Simulate getting position from player
                let position = i as f64 * 0.25;

                // Store position (simulating event emission)
                positions_clone.lock_safe().push(position);
            }
        });

        handle.join().unwrap();

        let recorded_positions = positions.lock_safe();
        assert_eq!(
            recorded_positions.len(),
            5,
            "Should have recorded 5 position updates"
        );

        // Verify positions are increasing
        for (i, pos) in recorded_positions.iter().enumerate() {
            let expected = i as f64 * 0.25;
            assert!(
                (*pos - expected).abs() < 0.001,
                "Position {} should be close to {}",
                pos,
                expected
            );
        }
    }

    /// Test async progress reporting pattern
    #[tokio::test]
    async fn test_progress_reporting_with_tokio_mutex() {
        use crate::playback_reporting::{EventThrottler, PlaybackReporter};

        // Create mock reporter (None for this test)
        let reporter = Arc::new(TokioMutex::new(None::<PlaybackReporter>));
        let throttler = Arc::new(EventThrottler::new());

        // Simulate progress reporting
        let item_id = "test_item_123".to_string();

        // This should not panic even though reporter is None
        let reporter_guard = reporter.lock().await;
        if let Some(_reporter_instance) = reporter_guard.as_ref() {
            // Would report here if reporter was configured
        } else {
            // Reporter not configured - this is OK
        }
        drop(reporter_guard);

        // Verify throttler works
        assert!(
            throttler.should_report(&item_id),
            "First report should be allowed"
        );

        throttler.mark_reported(&item_id);

        // Immediate second report should be throttled
        // (EventThrottler has internal logic for this)
    }

    /// Test that position updates are emitted even when paused (for scrubbing)
    /// This is critical for UI responsiveness when seeking while paused
    #[test]
    fn test_position_updates_while_paused() {
        use std::sync::atomic::{AtomicUsize, Ordering};
        use std::time::Duration;

        let update_count = Arc::new(AtomicUsize::new(0));
        let update_count_clone = update_count.clone();

        // Simulate a position update thread that runs regardless of pause state
        let handle = std::thread::spawn(move || {
            // Simulate 5 position updates
            for _ in 0..5 {
                std::thread::sleep(Duration::from_millis(50));

                // In the real implementation, we check position from MPV
                // and emit PositionUpdate events even when paused
                // This simulates that behavior:
                let _is_paused = true; // Simulating paused state

                // Key: We DON'T skip the update when paused
                // This allows scrubbing to work
                update_count_clone.fetch_add(1, Ordering::SeqCst);
            }
        });

        handle.join().unwrap();

        let final_count = update_count.load(Ordering::SeqCst);
        assert_eq!(
            final_count, 5,
            "Position updates should be emitted even when paused (got {} updates)",
            final_count
        );
    }

    /// Test that progress reporting is skipped when paused
    /// Progress reporting to the server should only happen during active playback
    #[test]
    fn test_progress_reporting_skipped_when_paused() {
        use std::sync::atomic::{AtomicUsize, Ordering};
        use std::time::Duration;

        let report_count = Arc::new(AtomicUsize::new(0));
        let report_count_clone = report_count.clone();

        // Simulate the progress reporting logic
        let handle = std::thread::spawn(move || {
            // Simulate 5 update cycles
            for i in 0..5 {
                std::thread::sleep(Duration::from_millis(50));

                // Position updates are emitted (tested separately)
                // But progress reporting depends on pause state
                let is_paused = i % 2 == 0; // Alternate between paused and playing

                // Key: Only report when NOT paused
                if !is_paused {
                    report_count_clone.fetch_add(1, Ordering::SeqCst);
                }
            }
        });

        handle.join().unwrap();

        let final_count = report_count.load(Ordering::SeqCst);
        assert_eq!(
            final_count, 2,
            "Progress reporting should only happen when not paused (got {} reports)",
            final_count
        );
    }

    /// Test that position updates are suppressed briefly after a seek
    /// This prevents "jumping to zero" visual glitches during seek operations
    #[test]
    fn test_position_updates_suppressed_after_seek() {
        use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
        use std::time::{Duration, SystemTime, UNIX_EPOCH};

        let last_seek_time = Arc::new(AtomicU64::new(0));
        let last_seek_time_clone = last_seek_time.clone();
        let update_count = Arc::new(AtomicUsize::new(0));
        let update_count_clone = update_count.clone();

        // Simulate a seek happening
        let seek_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;
        last_seek_time.store(seek_time, Ordering::Relaxed);

        // Simulate position update thread
        let handle = std::thread::spawn(move || {
            // Try 2 position updates at 50ms intervals (well within the 150ms window)
            for _ in 0..2 {
                std::thread::sleep(Duration::from_millis(50));

                // Check if we should suppress updates (within 150ms of seek)
                let now = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_millis() as u64;
                let last_seek = last_seek_time_clone.load(Ordering::Relaxed);
                let time_since_seek = now.saturating_sub(last_seek);

                if time_since_seek < 150 {
                    // Suppress update (don't increment counter)
                    continue;
                }

                // Emit update
                update_count_clone.fetch_add(1, Ordering::SeqCst);
            }
        });

        handle.join().unwrap();

        let final_count = update_count.load(Ordering::SeqCst);
        // With 50ms intervals and 150ms suppression window, updates at 50ms and 100ms
        // should both be suppressed
        assert_eq!(
            final_count, 0,
            "Position updates should be suppressed within 150ms of seek (got {} updates)",
            final_count
        );
    }

    /// Test that position updates resume after seek suppression window
    #[test]
    fn test_position_updates_resume_after_seek_window() {
        use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
        use std::time::{Duration, SystemTime, UNIX_EPOCH};

        let last_seek_time = Arc::new(AtomicU64::new(0));
        let last_seek_time_clone = last_seek_time.clone();
        let update_count = Arc::new(AtomicUsize::new(0));
        let update_count_clone = update_count.clone();

        // Simulate a seek that happened 200ms ago (past the suppression window)
        let seek_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64
            - 200; // 200ms ago
        last_seek_time.store(seek_time, Ordering::Relaxed);

        // Simulate position update thread
        let handle = std::thread::spawn(move || {
            // Try 3 position updates
            for _ in 0..3 {
                std::thread::sleep(Duration::from_millis(10));

                // Check if we should suppress updates (within 150ms of seek)
                let now = SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_millis() as u64;
                let last_seek = last_seek_time_clone.load(Ordering::Relaxed);
                let time_since_seek = now.saturating_sub(last_seek);

                if time_since_seek < 150 {
                    continue; // Should not happen in this test
                }

                // Emit update
                update_count_clone.fetch_add(1, Ordering::SeqCst);
            }
        });

        handle.join().unwrap();

        let final_count = update_count.load(Ordering::SeqCst);
        assert_eq!(
            final_count, 3,
            "Position updates should resume after seek suppression window (got {} updates)",
            final_count
        );
    }
}
