use crate::harness::{default_ctx, expect_outputs, forth};
use cagire::forth::{CcAccess, StepContext};
use cagire::midi::CcMemory;


#[allow(unused_imports)]
use cagire::forth::Value;

#[test]
fn test_midi_channel_set() {
    let outputs = expect_outputs("60 note 100 velocity 3 chan m.", 1);
    assert!(outputs[0].starts_with("/midi/note/60/vel/100/chan/2/dur/"));
}

#[test]
fn test_midi_note_default_channel() {
    let outputs = expect_outputs("72 note 80 velocity m.", 1);
    assert!(outputs[0].starts_with("/midi/note/72/vel/80/chan/0/dur/"));
}

#[test]
fn test_midi_cc() {
    let outputs = expect_outputs("64 ccnum 127 ccout m.", 1);
    assert!(outputs[0].contains("/midi/cc/64/127/chan/0"));
}

#[test]
fn test_midi_cc_with_channel() {
    let outputs = expect_outputs("5 chan 1 ccnum 64 ccout m.", 1);
    assert!(outputs[0].contains("/midi/cc/1/64/chan/4"));
}

#[test]
fn test_ccval_returns_zero_without_cc_memory() {
    let f = forth();
    let ctx = default_ctx();
    let outputs = f.evaluate("1 1 ccval", &ctx).unwrap();
    assert!(outputs.is_empty());
    let stack = f.stack();
    assert_eq!(stack.len(), 1);
    match &stack[0] {
        cagire::forth::Value::Int(v, _) => assert_eq!(*v, 0),
        _ => panic!("expected Int"),
    }
}

#[test]
fn test_ccval_reads_from_cc_memory() {
    let cc_memory = CcMemory::new();
    cc_memory.set_cc(0, 0, 1, 64); // device 0, channel 1 (0-indexed), CC 1, value 64
    cc_memory.set_cc(0, 5, 74, 127); // device 0, channel 6 (0-indexed), CC 74, value 127

    let f = forth();
    let ctx = StepContext {
        cc_access: Some(&cc_memory as &dyn CcAccess),
        ..default_ctx()
    };

    // Test CC 1 on channel 1 (user provides 1, internally 0)
    f.evaluate("1 1 ccval", &ctx).unwrap();
    let stack = f.stack();
    assert_eq!(stack.len(), 1);
    match &stack[0] {
        cagire::forth::Value::Int(v, _) => assert_eq!(*v, 64),
        _ => panic!("expected Int"),
    }
    f.clear_stack();

    // Test CC 74 on channel 6 (user provides 6, internally 5)
    f.evaluate("74 6 ccval", &ctx).unwrap();
    let stack = f.stack();
    assert_eq!(stack.len(), 1);
    match &stack[0] {
        cagire::forth::Value::Int(v, _) => assert_eq!(*v, 127),
        _ => panic!("expected Int"),
    }
}

#[test]
fn test_midi_channel_clamping() {
    // Channel should be clamped 1-16, then converted to 0-15 internally
    let outputs = expect_outputs("60 note 100 velocity 0 chan m.", 1);
    assert!(outputs[0].contains("/chan/0")); // 0 clamped to 1, then -1 = 0

    let outputs = expect_outputs("60 note 100 velocity 17 chan m.", 1);
    assert!(outputs[0].contains("/chan/15")); // 17 clamped to 16, then -1 = 15
}

#[test]
fn test_midi_note_clamping() {
    let outputs = expect_outputs("-1 note 100 velocity m.", 1);
    assert!(outputs[0].contains("/note/0"));

    let outputs = expect_outputs("200 note 100 velocity m.", 1);
    assert!(outputs[0].contains("/note/127"));
}

#[test]
fn test_midi_velocity_clamping() {
    let outputs = expect_outputs("60 note -10 velocity m.", 1);
    assert!(outputs[0].contains("/vel/0"));

    let outputs = expect_outputs("60 note 200 velocity m.", 1);
    assert!(outputs[0].contains("/vel/127"));
}

#[test]
fn test_midi_defaults() {
    // With only note specified, velocity defaults to 100 and channel to 0
    let outputs = expect_outputs("60 note m.", 1);
    assert!(outputs[0].starts_with("/midi/note/60/vel/100/chan/0/dur/"));
}

#[test]
fn test_midi_full_defaults() {
    // With nothing specified, defaults to note=60, velocity=100, channel=0
    let outputs = expect_outputs("m.", 1);
    assert!(outputs[0].starts_with("/midi/note/60/vel/100/chan/0/dur/"));
}

// Pitch bend tests
#[test]
fn test_midi_bend_center() {
    let outputs = expect_outputs("0.0 bend m.", 1);
    // 0.0 -> 8192 (center)
    assert!(
        outputs[0].contains("/midi/bend/8191/chan/0")
            || outputs[0].contains("/midi/bend/8192/chan/0")
    );
}

#[test]
fn test_midi_bend_max() {
    let outputs = expect_outputs("1.0 bend m.", 1);
    // 1.0 -> 16383 (max)
    assert!(outputs[0].contains("/midi/bend/16383/chan/0"));
}

#[test]
fn test_midi_bend_min() {
    let outputs = expect_outputs("-1.0 bend m.", 1);
    // -1.0 -> 0 (min)
    assert!(outputs[0].contains("/midi/bend/0/chan/0"));
}

#[test]
fn test_midi_bend_with_channel() {
    let outputs = expect_outputs("0.5 bend 3 chan m.", 1);
    assert!(outputs[0].contains("/chan/2")); // channel 3 -> 2 (0-indexed)
    assert!(outputs[0].contains("/midi/bend/"));
}

#[test]
fn test_midi_bend_clamping() {
    let outputs = expect_outputs("2.0 bend m.", 1);
    // 2.0 clamped to 1.0 -> 16383
    assert!(outputs[0].contains("/midi/bend/16383/chan/0"));

    let outputs = expect_outputs("-5.0 bend m.", 1);
    // -5.0 clamped to -1.0 -> 0
    assert!(outputs[0].contains("/midi/bend/0/chan/0"));
}

// Channel pressure (aftertouch) tests
#[test]
fn test_midi_pressure() {
    let outputs = expect_outputs("64 pressure m.", 1);
    assert!(outputs[0].contains("/midi/pressure/64/chan/0"));
}

#[test]
fn test_midi_pressure_with_channel() {
    let outputs = expect_outputs("100 pressure 5 chan m.", 1);
    assert!(outputs[0].contains("/midi/pressure/100/chan/4"));
}

#[test]
fn test_midi_pressure_clamping() {
    let outputs = expect_outputs("-10 pressure m.", 1);
    assert!(outputs[0].contains("/midi/pressure/0/chan/0"));

    let outputs = expect_outputs("200 pressure m.", 1);
    assert!(outputs[0].contains("/midi/pressure/127/chan/0"));
}

// Program change tests
#[test]
fn test_midi_program() {
    let outputs = expect_outputs("0 program m.", 1);
    assert!(outputs[0].contains("/midi/program/0/chan/0"));
}

#[test]
fn test_midi_program_with_channel() {
    let outputs = expect_outputs("42 program 10 chan m.", 1);
    assert!(outputs[0].contains("/midi/program/42/chan/9"));
}

#[test]
fn test_midi_program_clamping() {
    let outputs = expect_outputs("-1 program m.", 1);
    assert!(outputs[0].contains("/midi/program/0/chan/0"));

    let outputs = expect_outputs("200 program m.", 1);
    assert!(outputs[0].contains("/midi/program/127/chan/0"));
}

// MIDI real-time messages
#[test]
fn test_midi_clock() {
    let outputs = expect_outputs("mclock", 1);
    assert_eq!(outputs[0], "/midi/clock/dev/0");
}

#[test]
fn test_midi_start() {
    let outputs = expect_outputs("mstart", 1);
    assert_eq!(outputs[0], "/midi/start/dev/0");
}

#[test]
fn test_midi_stop() {
    let outputs = expect_outputs("mstop", 1);
    assert_eq!(outputs[0], "/midi/stop/dev/0");
}

#[test]
fn test_midi_continue() {
    let outputs = expect_outputs("mcont", 1);
    assert_eq!(outputs[0], "/midi/continue/dev/0");
}

// at (delta) tests
#[test]
fn test_midi_at_single_delta() {
    let outputs = expect_outputs("0.5 at 60 note m.", 1);
    assert!(outputs[0].contains("/note/60/"));
    assert!(outputs[0].contains("/delta/"));
}

#[test]
fn test_midi_at_multiple_deltas() {
    let outputs = expect_outputs("0 0.5 at 60 note m.", 2);
    assert!(outputs[0].contains("/note/60/"));
    assert!(outputs[1].contains("/note/60/"));
    assert!(outputs[1].contains("/delta/"));
}

#[test]
fn test_midi_at_with_polyphony() {
    // 2 notes × 2 deltas = 4 events
    expect_outputs("0 0.5 at 60 64 note m.", 4);
}

#[test]
fn test_midi_arp_notes() {
    let outputs = expect_outputs("c4 e4 g4 arp note m.", 3);
    assert!(outputs[0].contains("/note/60/"));
    assert!(outputs[1].contains("/note/64/"));
    assert!(outputs[2].contains("/note/67/"));
}

#[test]
fn test_midi_arp_with_at() {
    let outputs = expect_outputs("0 0.25 0.5 at c4 e4 g4 arp note m.", 3);
    assert!(outputs[0].contains("/note/60/"));
    assert!(outputs[1].contains("/note/64/"));
    assert!(outputs[2].contains("/note/67/"));
}

#[test]
fn test_midi_at_cc() {
    let outputs = expect_outputs("0 0.5 at 1 ccnum 64 ccout m.", 2);
    assert!(outputs[0].contains("/midi/cc/1/64/"));
    assert!(outputs[1].contains("/midi/cc/1/64/"));
    assert!(outputs[1].contains("/delta/"));
}

// Test message type priority (first matching type wins)
#[test]
fn test_midi_message_priority_cc_over_note() {
    // CC params take priority over note
    let outputs = expect_outputs("60 note 1 ccnum 64 ccout m.", 1);
    assert!(outputs[0].contains("/midi/cc/1/64"));
}

#[test]
fn test_midi_message_priority_bend_over_note() {
    // bend takes priority over note (but not over CC)
    let outputs = expect_outputs("60 note 0.5 bend m.", 1);
    assert!(outputs[0].contains("/midi/bend/"));
}

// MIDI note duration tests
#[test]
fn test_midi_note_default_duration() {
    // Default dur=1.0, with tempo=120 and speed=1.0, step_duration = 60/120/4/1 = 0.125
    let outputs = expect_outputs("60 note m.", 1);
    assert!(outputs[0].contains("/dur/0.125"));
}

#[test]
fn test_midi_note_explicit_duration() {
    // dur=0.5 means half step duration = 0.0625 seconds
    let outputs = expect_outputs("60 note 0.5 dur m.", 1);
    assert!(outputs[0].contains("/dur/0.0625"));
}

#[test]
fn test_midi_note_long_duration() {
    // dur=2.0 means two step durations = 0.25 seconds
    let outputs = expect_outputs("60 note 2 dur m.", 1);
    assert!(outputs[0].contains("/dur/0.25"));
}

#[test]
fn test_midi_note_duration_with_tempo() {
    use crate::harness::{ctx_with, forth};
    let f = forth();
    // At tempo=60, step_duration = 60/60/4/1 = 0.25 seconds
    let ctx = ctx_with(|c| c.tempo = 60.0);
    let outputs = f.evaluate("60 note m.", &ctx).unwrap();
    assert!(outputs[0].contains("/dur/0.25"));
}

#[test]
fn test_midi_note_duration_with_speed() {
    use crate::harness::{ctx_with, forth};
    let f = forth();
    // At tempo=120 speed=2, step_duration = 60/120/4/2 = 0.0625 seconds
    let ctx = ctx_with(|c| c.speed = 2.0);
    let outputs = f.evaluate("60 note m.", &ctx).unwrap();
    assert!(outputs[0].contains("/dur/0.0625"));
}

// Polyphonic MIDI tests
#[test]
fn test_midi_polyphonic_notes() {
    let outputs = expect_outputs("60 64 67 note m.", 3);
    assert!(outputs[0].contains("/midi/note/60/"));
    assert!(outputs[1].contains("/midi/note/64/"));
    assert!(outputs[2].contains("/midi/note/67/"));
}

#[test]
fn test_midi_polyphonic_notes_with_velocity() {
    let outputs = expect_outputs("60 64 67 note 100 80 60 velocity m.", 3);
    assert!(outputs[0].contains("/note/60/vel/100/"));
    assert!(outputs[1].contains("/note/64/vel/80/"));
    assert!(outputs[2].contains("/note/67/vel/60/"));
}

#[test]
fn test_midi_polyphonic_channel() {
    let outputs = expect_outputs("60 note 1 2 chan m.", 2);
    assert!(outputs[0].contains("/note/60/") && outputs[0].contains("/chan/0"));
    assert!(outputs[1].contains("/note/60/") && outputs[1].contains("/chan/1"));
}

#[test]
fn test_midi_polyphonic_cc() {
    let outputs = expect_outputs("1 2 ccnum 64 127 ccout m.", 2);
    assert!(outputs[0].contains("/midi/cc/1/64/"));
    assert!(outputs[1].contains("/midi/cc/2/127/"));
}
