use super::*;

#[test]
fn phase_estimation_and_indexing_stay_stable_when_pulses_are_missing() {
    let video_phase = estimate_phase(&[4.0, 5.0, 7.0, 8.0, 10.0], 1.0);
    let audio_phase = estimate_phase(&[4.018, 5.017, 6.019, 8.018, 9.018], 1.0);
    assert!((video_phase - 0.0).abs() < 0.02);
    assert!((audio_phase - 0.018).abs() < 0.02);

    let video_indexed = index_onsets_by_spacing(&[4.0, 5.0, 7.0, 8.0, 10.0], 1.0);
    let audio_indexed = index_onsets_by_spacing(&[4.018, 5.017, 6.019, 8.018, 9.018], 1.0);
    assert_eq!(
        video_indexed.keys().copied().collect::<Vec<_>>(),
        vec![0, 1, 3, 4, 6]
    );
    assert_eq!(
        audio_indexed.keys().copied().collect::<Vec<_>>(),
        vec![0, 1, 2, 4, 5]
    );
}

#[test]
fn correlation_helpers_cover_empty_index_sets_and_wrapped_phase_math() {
    assert!(index_onsets_by_spacing(&[], 1.0).is_empty());
    assert!(candidate_index_offsets(&BTreeMap::new(), &BTreeMap::new()).is_empty());

    let mut video_only = BTreeMap::new();
    video_only.insert(0, 1.0);
    assert!(candidate_index_offsets(&video_only, &BTreeMap::new()).is_empty());

    let mut audio_only = BTreeMap::new();
    audio_only.insert(0, 1.0);
    assert!(candidate_index_offsets(&BTreeMap::new(), &audio_only).is_empty());

    let mut video_indexed = BTreeMap::new();
    video_indexed.insert(2, 2.0);
    let mut audio_indexed = BTreeMap::new();
    audio_indexed.insert(5, 5.0);
    assert_eq!(
        candidate_index_offsets(&video_indexed, &audio_indexed),
        vec![3]
    );

    assert!((shortest_wrapped_difference(0.6, 1.0) + 0.4).abs() < 0.000_001);
    assert!((shortest_wrapped_difference(-0.6, 1.0) - 0.4).abs() < 0.000_001);
}

#[test]
fn marker_index_offsets_include_marker_alignment_and_general_fallback() {
    let video_indexed = index_onsets_by_spacing(&[4.0, 5.0, 7.0, 8.0, 10.0], 1.0);
    let audio_indexed = index_onsets_by_spacing(&[5.018, 6.017, 7.019, 9.018, 10.018], 1.0);
    let offsets = marker_index_offsets(&video_indexed, &audio_indexed, &[10.0], &[10.018]);
    assert!(offsets.contains(&1));
    assert!(offsets.contains(&0));
}

#[test]
fn correlate_onsets_ignores_missing_pulses_and_preserves_stable_skew() {
    let report = correlate_onsets(
        &[4.0, 5.0, 7.0, 8.0, 10.0],
        &[4.018, 5.017, 6.019, 8.018, 9.018],
        1.0,
        0.2,
    )
    .expect("correlated report");

    assert_eq!(report.paired_event_count, 3);
    assert!((report.mean_skew_ms - 17.666).abs() < 5.0);
    assert!(report.max_abs_skew_ms < 30.0);
}

#[test]
fn correlate_segments_uses_markers_to_break_period_aliasing() {
    let video = vec![
        PulseSegment {
            start_s: 3.3,
            end_s: 3.55,
            duration_s: 0.25,
        },
        PulseSegment {
            start_s: 4.266667,
            end_s: 4.4,
            duration_s: 0.133333,
        },
        PulseSegment {
            start_s: 5.3,
            end_s: 5.433333,
            duration_s: 0.133333,
        },
    ];
    let audio = vec![
        PulseSegment {
            start_s: 3.35,
            end_s: 3.59,
            duration_s: 0.24,
        },
        PulseSegment {
            start_s: 4.316667,
            end_s: 4.436667,
            duration_s: 0.12,
        },
        PulseSegment {
            start_s: 5.35,
            end_s: 5.47,
            duration_s: 0.12,
        },
    ];

    let report =
        correlate_segments(&video, &audio, 1.0, 0.12, 5, 0.2).expect("marker-correlated report");
    assert_eq!(report.paired_event_count, 3);
    assert!((report.mean_skew_ms - 50.0).abs() < 10.0);
}

#[test]
fn collapse_segments_by_phase_keeps_one_best_segment_per_pulse_slot() {
    let collapsed = collapse_segments_by_phase(
        &[
            PulseSegment {
                start_s: 4.00,
                end_s: 4.12,
                duration_s: 0.12,
            },
            PulseSegment {
                start_s: 4.08,
                end_s: 4.10,
                duration_s: 0.02,
            },
            PulseSegment {
                start_s: 5.01,
                end_s: 5.13,
                duration_s: 0.12,
            },
            PulseSegment {
                start_s: 5.09,
                end_s: 5.11,
                duration_s: 0.02,
            },
        ],
        1.0,
        0.32,
    );
    assert_eq!(collapsed.len(), 2);
    assert!((collapsed[0].start_s - 4.0).abs() < 0.001);
    assert!((collapsed[1].start_s - 5.01).abs() < 0.001);
}

#[test]
fn collapse_segments_by_phase_prefers_the_longest_regular_cadence() {
    let collapsed = collapse_segments_by_phase(
        &[
            PulseSegment {
                start_s: 4.00,
                end_s: 4.12,
                duration_s: 0.12,
            },
            PulseSegment {
                start_s: 5.02,
                end_s: 5.14,
                duration_s: 0.12,
            },
            PulseSegment {
                start_s: 6.00,
                end_s: 6.12,
                duration_s: 0.12,
            },
            PulseSegment {
                start_s: 7.01,
                end_s: 7.13,
                duration_s: 0.12,
            },
            PulseSegment {
                start_s: 4.42,
                end_s: 4.67,
                duration_s: 0.25,
            },
            PulseSegment {
                start_s: 6.42,
                end_s: 6.67,
                duration_s: 0.25,
            },
        ],
        1.0,
        0.32,
    );

    assert_eq!(collapsed.len(), 4);
    assert!((collapsed[0].start_s - 4.0).abs() < 0.001);
    assert!((collapsed[3].start_s - 7.01).abs() < 0.001);
}

#[test]
fn correlate_segments_collapses_repeated_noise_within_each_pulse_slot() {
    let video = vec![
        PulseSegment {
            start_s: 4.0,
            end_s: 4.12,
            duration_s: 0.12,
        },
        PulseSegment {
            start_s: 4.03,
            end_s: 4.05,
            duration_s: 0.02,
        },
        PulseSegment {
            start_s: 5.0,
            end_s: 5.12,
            duration_s: 0.12,
        },
        PulseSegment {
            start_s: 5.03,
            end_s: 5.05,
            duration_s: 0.02,
        },
    ];
    let audio = vec![
        PulseSegment {
            start_s: 4.02,
            end_s: 4.14,
            duration_s: 0.12,
        },
        PulseSegment {
            start_s: 4.05,
            end_s: 4.07,
            duration_s: 0.02,
        },
        PulseSegment {
            start_s: 5.02,
            end_s: 5.14,
            duration_s: 0.12,
        },
        PulseSegment {
            start_s: 5.05,
            end_s: 5.07,
            duration_s: 0.02,
        },
    ];

    let report = correlate_segments(&video, &audio, 1.0, 0.12, 5, 0.2).expect("collapsed");
    assert_eq!(report.video_event_count, 2);
    assert_eq!(report.audio_event_count, 2);
    assert_eq!(report.paired_event_count, 2);
    assert!(report.max_abs_skew_ms < 30.0);
}

#[test]
fn marker_detection_finds_wider_segments_only() {
    let markers = marker_onsets(
        &[
            PulseSegment {
                start_s: 1.0,
                end_s: 1.12,
                duration_s: 0.12,
            },
            PulseSegment {
                start_s: 5.0,
                end_s: 5.24,
                duration_s: 0.24,
            },
        ],
        0.12,
    );
    assert_eq!(markers, vec![5.0]);
}

#[test]
fn median_handles_empty_even_and_odd_inputs() {
    assert_eq!(median(Vec::new()), 0.0);
    assert_eq!(median(vec![1.0, 3.0, 2.0]), 2.0);
    assert_eq!(median(vec![4.0, 1.0, 3.0, 2.0]), 2.5);
}
