use proptest::prelude::*;
use std::time::Duration;

use crate::editor::buffer::Buffer;
use crate::editor::history::ChangeKind;
use crate::editor::position::Position;
use crate::editor::selection::Selection;

#[derive(Debug, Clone)]
enum Op {
    Insert(String),
    DeleteBackward,
    DeleteForward,
    InsertNewline,
}

impl Op {
    fn apply(&self, buf: &mut Buffer) {
        match self {
            Op::Insert(text) => buf.insert(text),
            Op::DeleteBackward => buf.delete_backward(),
            Op::DeleteForward => buf.delete_forward(),
            Op::InsertNewline => buf.insert_newline(),
        }
    }
}

fn gen_insert_text() -> impl Strategy<Value = String> {
    prop_oneof![
        Just("a".to_string()),
        Just("hello".to_string()),
        Just(" ".to_string()),
        Just("AB".to_string()),
        any::<char>().prop_map(|c| c.to_string()),
    ]
}

fn gen_ops(max_len: usize) -> impl Strategy<Value = Vec<Op>> {
    prop::collection::vec(
        prop_oneof![
            gen_insert_text().prop_map(Op::Insert),
            Just(Op::DeleteBackward),
            Just(Op::DeleteForward),
            Just(Op::InsertNewline),
        ],
        0..max_len,
    )
}

proptest! {
    #[test]
    fn test_undo_restores_state(ref ops in gen_ops(20)) {
        let mut buf = Buffer::from_text("");
        let mut snapshots: Vec<String> = Vec::new();

        for op in ops.iter() {
            snapshots.push(buf.text());
            op.apply(&mut buf);
        }

        while buf.can_undo() {
            prop_assert!(buf.undo());
        }

        prop_assert_eq!(buf.text(), String::new());
    }

    #[test]
    fn test_undo_redo_roundtrip(ref ops in gen_ops(15)) {
        let mut buf = Buffer::from_text("initial");

        for op in ops.iter() {
            op.apply(&mut buf);
        }

        let text_before_undo = buf.text();

        if buf.can_undo() {
            buf.undo();
            buf.redo();

            prop_assert_eq!(buf.text(), text_before_undo);
        }
    }

    #[test]
    fn test_random_operations_deterministic(ref ops in gen_ops(10)) {
        let mut buf = Buffer::from_text("");
        let initial = buf.text();

        for op in ops {
            op.apply(&mut buf);
        }

        let ops_count = ops.len();

        for _ in 0..ops_count {
            if buf.can_undo() {
                buf.undo();
            }
        }

        prop_assert_eq!(buf.text(), initial);
    }
}

#[test]
fn test_cursor_at_end() {
    let mut buf = Buffer::from_text("hello");
    buf.set_cursor(Position::new(0, 5));

    buf.insert("!");
    assert_eq!(buf.text(), "hello!");

    buf.undo();
    assert_eq!(buf.text(), "hello");
}

#[test]
fn test_merge_across_lines() {
    let mut buf = Buffer::from_text("");

    buf.insert("a");
    buf.insert_newline();
    buf.insert("b");
    buf.insert_newline();
    buf.insert("c");

    assert_eq!(buf.text(), "a\nb\nc");

    buf.undo();
    assert_eq!(buf.text(), "a\nb\n");

    buf.undo();
    assert_eq!(buf.text(), "a\nb");

    buf.undo();
    assert_eq!(buf.text(), "a\n");

    buf.undo();
    assert_eq!(buf.text(), "a");

    buf.undo();
    assert_eq!(buf.text(), "");
}

#[test]
fn test_merge_across_lines_debug() {
    let mut buf = Buffer::from_text("");

    buf.insert("a");
    eprintln!(
        "After 'a': text='{}', cursor={:?}",
        buf.text(),
        buf.cursor()
    );
    buf.insert_newline();
    eprintln!(
        "After newline: text='{}', cursor={:?}",
        buf.text(),
        buf.cursor()
    );
    buf.insert("b");
    eprintln!(
        "After 'b': text='{}', cursor={:?}",
        buf.text(),
        buf.cursor()
    );
    buf.insert_newline();
    eprintln!(
        "After newline: text='{}', cursor={:?}",
        buf.text(),
        buf.cursor()
    );
    buf.insert("c");
    eprintln!(
        "After 'c': text='{}', cursor={:?}",
        buf.text(),
        buf.cursor()
    );

    assert_eq!(buf.text(), "a\nb\nc");

    buf.undo();
    eprintln!(
        "After undo 1: text='{}', cursor={:?} (expected 'a\\nb\\n')",
        buf.text(),
        buf.cursor()
    );
    assert_eq!(buf.text(), "a\nb\n");

    buf.undo();
    eprintln!(
        "After undo 2: text='{}', cursor={:?} (expected 'a\\nb')",
        buf.text(),
        buf.cursor()
    );
    assert_eq!(buf.text(), "a\nb");

    buf.undo();
    eprintln!(
        "After undo 3: text='{}', cursor={:?} (expected 'a\\n')",
        buf.text(),
        buf.cursor()
    );
    assert_eq!(buf.text(), "a\n");

    buf.undo();
    eprintln!(
        "After undo 4: text='{}', cursor={:?} (expected 'a')",
        buf.text(),
        buf.cursor()
    );
    assert_eq!(buf.text(), "a");

    buf.undo();
    eprintln!(
        "After undo 5: text='{}', cursor={:?} (expected '')",
        buf.text(),
        buf.cursor()
    );
    assert_eq!(buf.text(), "");
}

#[test]
fn test_utf8_delete_multibyte() {
    let mut buf = Buffer::from_text("日本");

    buf.set_cursor(Position::new(0, 2));

    buf.delete_backward();
    assert_eq!(buf.text(), "日");

    buf.undo();
    assert_eq!(buf.text(), "日本");
}

#[test]
fn test_utf8_emoji_in_middle() {
    let mut buf = Buffer::from_text("a🚀b");

    buf.set_cursor(Position::new(0, 2));

    buf.insert("X");
    assert_eq!(buf.text(), "a🚀Xb");

    buf.undo();
    assert_eq!(buf.text(), "a🚀b");
}

#[test]
fn test_mixed_operations_with_newline() {
    let mut buf = Buffer::from_text("");

    buf.insert("line1");
    buf.insert_newline();
    buf.insert("line2");

    assert_eq!(buf.text(), "line1\nline2");

    buf.undo();
    assert_eq!(buf.text(), "line1\n");

    buf.undo();
    assert_eq!(buf.text(), "line1");

    buf.undo();
    assert_eq!(buf.text(), "");

    buf.redo();
    assert_eq!(buf.text(), "line1");

    buf.redo();
    assert_eq!(buf.text(), "line1\n");

    buf.redo();
    assert_eq!(buf.text(), "line1\nline2");
}

#[test]
fn test_selection_undo_restores() {
    let mut buf = Buffer::from_text("hello world");

    buf.set_selection(Some(Selection::new(
        Position::new(0, 0),
        Position::new(0, 6),
    )));

    buf.insert("hi ");
    assert_eq!(buf.text(), "hi world");

    buf.undo();
    assert_eq!(buf.text(), "hello world");
}

#[test]
fn test_replace_and_undo() {
    let mut buf = Buffer::from_text("hello world");

    buf.replace_range(Position::new(0, 0), Position::new(0, 5), "goodbye");
    assert_eq!(buf.text(), "goodbye world");

    buf.undo();
    assert_eq!(buf.text(), "hello world");
}

#[test]
fn test_multiple_undo_redo_cycles() {
    // Use a zero-threshold buffer so every single-char insert gets its own
    // undo step, letting us verify the full undo/redo cycle granularity.
    let mut buf = Buffer::from_text_no_merge("");

    buf.insert("A");
    buf.insert("B");
    buf.insert("C");

    assert_eq!(buf.text(), "ABC");

    buf.undo();
    assert_eq!(buf.text(), "AB");

    buf.redo();
    assert_eq!(buf.text(), "ABC");

    buf.undo();
    buf.undo();
    buf.undo();

    assert_eq!(buf.text(), "");

    buf.redo();
    buf.redo();
    buf.redo();

    assert_eq!(buf.text(), "ABC");
}

#[test]
fn test_undo_after_new_edit_truncates_redo() {
    // Use a zero-threshold buffer for per-char undo granularity.
    let mut buf = Buffer::from_text_no_merge("");

    buf.insert("a");
    buf.insert("b");
    buf.insert("c");

    buf.undo();
    buf.undo();

    assert_eq!(buf.text(), "a");

    buf.insert("X");

    assert_eq!(buf.text(), "aX");

    buf.undo();
    assert_eq!(buf.text(), "a");

    buf.undo();
    assert_eq!(buf.text(), "");
}

#[test]
fn test_large_text_undo() {
    let mut buf = Buffer::from_text("");

    let large_text = "x".repeat(1000);
    buf.insert(&large_text);

    assert_eq!(buf.text(), large_text);

    buf.undo();
    assert_eq!(buf.text(), "");

    buf.redo();
    assert_eq!(buf.text(), large_text);
}

#[test]
fn test_consecutive_newlines() {
    let mut buf = Buffer::from_text("");

    buf.insert_newline();
    buf.insert_newline();
    buf.insert_newline();

    assert_eq!(buf.text(), "\n\n\n");

    buf.undo();
    assert_eq!(buf.text(), "\n\n");

    buf.undo();
    assert_eq!(buf.text(), "\n");

    buf.undo();
    assert_eq!(buf.text(), "");
}

#[test]
fn test_backspace_at_line_start() {
    let mut buf = Buffer::from_text("a\nb");

    buf.set_cursor(Position::new(1, 0));

    buf.delete_backward();
    assert_eq!(buf.text(), "ab");

    buf.undo();
    assert_eq!(buf.text(), "a\nb");
}

#[test]
fn test_forward_delete_at_line_end() {
    let mut buf = Buffer::from_text("ab\ncd");

    buf.set_cursor(Position::new(0, 2));

    buf.delete_forward();
    assert_eq!(buf.text(), "abcd");

    buf.undo();
    assert_eq!(buf.text(), "ab\ncd");
}

// ---------------------------------------------------------------------------
// Transaction tests
// ---------------------------------------------------------------------------

#[test]
fn test_transaction_single_undo_step() {
    let mut buf = Buffer::from_text("");

    buf.begin_transaction(ChangeKind::InsertText);
    buf.insert("a");
    buf.insert("b");
    buf.insert("c");
    buf.end_transaction();

    assert_eq!(buf.text(), "abc");
    // All three inserts committed as one entry.
    assert_eq!(buf.history_position(), 1);

    buf.undo();
    assert_eq!(buf.text(), "");
    assert!(!buf.can_undo());
}

#[test]
fn test_transaction_redo_roundtrip() {
    let mut buf = Buffer::from_text("");

    buf.begin_transaction(ChangeKind::Paste);
    buf.insert("hello");
    buf.insert(" ");
    buf.insert("world");
    buf.end_transaction();

    assert_eq!(buf.text(), "hello world");

    buf.undo();
    assert_eq!(buf.text(), "");

    buf.redo();
    assert_eq!(buf.text(), "hello world");
    assert!(!buf.can_redo());
}

#[test]
fn test_transaction_cursor_restored_on_undo() {
    let mut buf = Buffer::from_text("hello");
    buf.set_cursor(Position::new(0, 2));

    let cursor_before = buf.cursor();

    buf.begin_transaction(ChangeKind::InsertText);
    buf.insert("X");
    buf.insert("Y");
    buf.end_transaction();

    assert_eq!(buf.text(), "heXYllo");

    buf.undo();
    assert_eq!(buf.text(), "hello");
    assert_eq!(buf.cursor(), cursor_before);
}

#[test]
fn test_transaction_empty_is_noop() {
    let mut buf = Buffer::from_text("hello");
    let pos_before = buf.history_position();

    buf.begin_transaction(ChangeKind::InsertText);
    // No edits.
    buf.end_transaction();

    assert_eq!(buf.text(), "hello");
    assert_eq!(buf.history_position(), pos_before);
}

/// In debug builds `begin_transaction` panics if called while a transaction is
/// already open.  This test documents that contract.
#[test]
#[should_panic(expected = "nested transactions are not supported")]
fn test_nested_begin_transaction_panics_in_debug() {
    let mut buf = Buffer::from_text("");
    buf.begin_transaction(ChangeKind::InsertText);
    // Second begin → debug_assert! fires.
    buf.begin_transaction(ChangeKind::DeleteText);
}

#[test]
fn test_transaction_is_not_merged_with_adjacent_edits() {
    let mut buf = Buffer::from_text("");

    // Typing before the transaction.
    buf.insert("x");

    // Paste via transaction.
    buf.begin_transaction(ChangeKind::Paste);
    buf.insert("a");
    buf.insert("b");
    buf.end_transaction();

    // Typing after the transaction.
    buf.insert("y");

    assert_eq!(buf.text(), "xaby");
    // "x" → 1 history entry; paste(a+b) → 1 entry (different kind, no merge);
    // "y" → 1 entry (InsertText after Paste kind, no merge) = 3 total.
    assert_eq!(buf.history_position(), 3);

    // Undo the trailing "y".
    buf.undo();
    assert_eq!(buf.text(), "xab");

    // Undo the paste transaction.
    buf.undo();
    assert_eq!(buf.text(), "x");
}

#[test]
fn test_abort_transaction_does_not_record_history() {
    let mut buf = Buffer::from_text("hello");
    let history_pos_before = buf.history_position();

    buf.begin_transaction(ChangeKind::InsertText);
    // These edits mutate the text but the history entry is discarded.
    buf.insert(" world");
    buf.abort_transaction();

    assert_eq!(buf.text(), "hello world"); // text IS changed
    assert_eq!(buf.history_position(), history_pos_before); // history NOT changed
    assert!(!buf.can_undo()); // nothing to undo
}

#[test]
fn test_transaction_with_heterogeneous_ops() {
    // A transaction can mix different operation kinds — the whole thing
    // becomes one undo step under the transaction's ChangeKind.
    let mut buf = Buffer::from_text("hello world");
    buf.set_cursor(Position::new(0, 5));

    buf.begin_transaction(ChangeKind::Structural);
    buf.delete_backward(); // remove 'o'
    buf.insert("O");       // insert 'O'
    buf.insert("!");
    buf.end_transaction();

    assert_eq!(buf.text(), "hellO! world");

    buf.undo();
    assert_eq!(buf.text(), "hello world");
}

#[test]
fn test_in_transaction_flag() {
    let mut buf = Buffer::from_text("");

    assert!(!buf.in_transaction());

    buf.begin_transaction(ChangeKind::Paste);
    assert!(buf.in_transaction());

    buf.end_transaction();
    assert!(!buf.in_transaction());
}

#[test]
fn test_merge_respects_time_gap() {
    // Edits separated by more than the 500 ms merge window must NOT coalesce.
    // This protects the merge threshold from silent regressions.
    let mut buf = Buffer::from_text("");

    buf.insert("a");
    std::thread::sleep(Duration::from_millis(600));
    buf.insert("b");

    assert_eq!(buf.text(), "ab");
    assert_eq!(buf.history_position(), 2);

    buf.undo();
    assert_eq!(buf.text(), "a");

    buf.undo();
    assert_eq!(buf.text(), "");
}
