use proptest::prelude::*;
use std::ops::Range;

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

#[derive(Debug, Clone)]
enum Op {
    Insert(String),
    DeleteBackward,
    DeleteForward,
    InsertNewline,
    Undo,
    Redo,
    MoveCursorLeft,
    MoveCursorRight,
    SetSelection(Option<Range<Position>>),
}

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(),
            Op::Undo => {
                buf.undo();
            }
            Op::Redo => {
                buf.redo();
            }
            Op::MoveCursorLeft => {
                buf.cursor_left();
            }
            Op::MoveCursorRight => {
                buf.cursor_right();
            }
            Op::SetSelection(range) => {
                if let Some(r) = range {
                    buf.selection = Some(Selection::new(r.start, r.end));
                } else {
                    buf.selection = None;
                }
            }
        }
    }
}

fn strategy_for_text() -> impl Strategy<Value = String> {
    prop_oneof![
        Just("a".to_string()),
        Just("hello".to_string()),
        Just(" ".to_string()),
        Just("\n".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![
            strategy_for_text().prop_map(Op::Insert),
            Just(Op::DeleteBackward),
            Just(Op::DeleteForward),
            Just(Op::InsertNewline),
            Just(Op::Undo),
            Just(Op::Redo),
        ],
        0..max_len,
    )
}

proptest! {
    #[test]
    fn test_undo_restores_previous_state(ref ops in gen_ops(50)) {
        let mut buf = Buffer::from_text("");
        let mut snapshots = Vec::new();

        for op in ops.iter() {
            if !matches!(op, Op::Undo | Op::Redo) {
                snapshots.push((buf.text(), buf.cursor(), buf.selection.clone()));
            }
            op.apply(&mut buf);
        }

        prop_assert!(buf.can_undo() || ops.iter().all(|op| matches!(op, Op::Undo | Op::Redo)));

        let mut undo_count = 0;
        while buf.can_undo() && undo_count < ops.len() {
            prop_assert!(buf.undo());
            undo_count += 1;

            if undo_count <= snapshots.len() {
                let (expected_text, expected_cursor, expected_selection) = &snapshots[snapshots.len() - undo_count];
                prop_assert_eq!(buf.text(), *expected_text, "Text mismatch after {} undos", undo_count);
            }
        }
    }

    #[test]
    fn test_undo_redo_roundtrip(ref ops in gen_ops(30)) {
        let mut buf = Buffer::from_text("initial");
        let initial_text = buf.text();
        let initial_cursor = buf.cursor();

        for op in ops.iter() {
            if !matches!(op, Op::Undo | Op::Redo) {
                op.apply(&mut buf);
            }
        }

        let text_before_undo = buf.text();
        let cursor_before_undo = buf.cursor();

        buf.undo();

        buf.redo();

        prop_assert_eq!(buf.text(), text_before_undo, "Redo should restore text");
        prop_assert_eq!(buf.cursor(), cursor_before_undo, "Redo should restore cursor");
    }

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

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

        let ops_count = ops.iter().filter(|o| !matches!(o, Op::Undo | Op::Redo)).count();

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

        prop_assert_eq!(buf.text(), initial.0, "Full undo should restore initial text");
        prop_assert_eq!(buf.cursor(), initial.1, "Full undo should restore initial cursor");
    }
}

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

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

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

#[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_empty_buffer_operations() {
    let mut buf = Buffer::from_text("");

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

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

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

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

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

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

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

    buf.insert("héllo");
    assert_eq!(buf.text(), "héllo");
    assert_eq!(buf.cursor().column, 6);

    buf.insert("世界");
    assert_eq!(buf.text(), "héllo世界");
    assert_eq!(buf.cursor().column, 12);

    buf.undo();
    assert_eq!(buf.text(), "héllo");

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

    buf.redo();
    assert_eq!(buf.text(), "héllo");

    buf.redo();
    assert_eq!(buf.text(), "héllo世界");
}

#[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.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_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\n");

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

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

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

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

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

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

#[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() {
    let mut buf = Buffer::from_text("");

    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();
    assert_eq!(buf.text(), "AB");

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

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

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

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

#[test]
fn test_utf8_edit_at_line_end_lf() {
    // Test editing at end of line with UTF-8 character and LF line ending (\n)
    let mut buf = Buffer::from_text("# ∞\nline2");

    // Move to end of first line (after ∞)
    buf.set_cursor(Position::new(0, 3)); // 3 characters: #, space, ∞

    // Insert text at end
    buf.insert("test");
    assert_eq!(buf.text(), "# ∞test\nline2");
    assert_eq!(buf.cursor(), Position::new(0, 7)); // # space ∞ t e s t = 7 chars
}

#[test]
fn test_utf8_edit_at_line_end_crlf() {
    // Test editing at end of line with UTF-8 character and CRLF line ending (\r\n)
    let mut buf = Buffer::from_text("# ∞\r\nline2");

    // Move to end of first line (after ∞, before \r\n)
    buf.set_cursor(Position::new(0, 3)); // 3 characters: #, space, ∞

    // Insert text at end
    buf.insert("test");
    assert_eq!(buf.text(), "# ∞test\r\nline2");
    assert_eq!(buf.cursor(), Position::new(0, 7)); // # space ∞ t e s t = 7 chars
}

#[test]
fn test_utf8_backspace_at_line_end_lf() {
    // Test backspace at end of line with UTF-8 character and LF
    let mut buf = Buffer::from_text("# ∞x\nline2");

    // Move to end of first line
    buf.set_cursor(Position::new(0, 4)); // after the 'x'

    // Delete backwards
    buf.delete_backward();
    assert_eq!(buf.text(), "# ∞\nline2");
    assert_eq!(buf.cursor(), Position::new(0, 3));
}

#[test]
fn test_utf8_backspace_at_line_end_crlf() {
    // Test backspace at end of line with UTF-8 character and CRLF
    let mut buf = Buffer::from_text("# ∞x\r\nline2");

    // Move to end of first line
    buf.set_cursor(Position::new(0, 4)); // after the 'x'

    // Delete backwards
    buf.delete_backward();
    assert_eq!(buf.text(), "# ∞\r\nline2");
    assert_eq!(buf.cursor(), Position::new(0, 3));
}

#[test]
fn test_multiple_utf8_chars_at_line_end() {
    // Test with multiple multi-byte UTF-8 characters
    let mut buf = Buffer::from_text("emoji: 🚀✨\nmore");

    // Move to end of first line
    let line_len = "emoji: 🚀✨".chars().count();
    buf.set_cursor(Position::new(0, line_len));

    // Insert text
    buf.insert("!");
    assert_eq!(buf.text(), "emoji: 🚀✨!\nmore");
    assert_eq!(buf.cursor().column, line_len + 1);
}
