use std::ops::Range;
use std::time::{Duration, Instant};

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

type SnapshotEntry = (Vec<String>, Position, Option<Selection>, bool, Version);

const DEFAULT_MERGE_THRESHOLD_MS: u64 = 500;
const MAX_HISTORY_SIZE: usize = 1000;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChangeKind {
    InsertText,
    DeleteText,
    Paste,
    Replace,
    CursorMove,
    Structural,
}

#[derive(Debug, Clone)]
pub enum BufferOp {
    Replace {
        range: Range<Position>,
        text: String,
        old_text: String,
        end_position: Position,
    },
    MoveCursor {
        position: Position,
    },
    SetSelection {
        selection: Option<Selection>,
    },
}

impl BufferOp {
    pub fn invert(&self) -> BufferOp {
        match self {
            BufferOp::Replace {
                range,
                text,
                old_text,
                end_position,
            } => {
                // Inverse: replace range.start..*end_position with old_text.
                BufferOp::Replace {
                    range: range.start..*end_position,
                    text: old_text.clone(),
                    old_text: text.clone(),
                    end_position: range.start,
                }
            }
            BufferOp::MoveCursor { position } => BufferOp::MoveCursor {
                position: *position,
            },
            BufferOp::SetSelection { selection } => BufferOp::SetSelection {
                selection: *selection,
            },
        }
    }
}

#[derive(Debug, Clone)]
pub struct ChangeSet {
    pub ops: Vec<BufferOp>,
    pub before_cursor: Position,
    pub after_cursor: Position,
    pub before_selection: Option<Selection>,
    pub after_selection: Option<Selection>,
}

impl ChangeSet {
    pub fn new(
        ops: Vec<BufferOp>,
        before_cursor: Position,
        after_cursor: Position,
        before_selection: Option<Selection>,
        after_selection: Option<Selection>,
    ) -> Self {
        Self {
            ops,
            before_cursor,
            after_cursor,
            before_selection,
            after_selection,
        }
    }

    pub fn invert(&self) -> ChangeSet {
        let inverse_ops: Vec<BufferOp> = self.ops.iter().rev().map(|op| op.invert()).collect();

        ChangeSet {
            ops: inverse_ops,
            before_cursor: self.after_cursor,
            after_cursor: self.before_cursor,
            before_selection: self.after_selection,
            after_selection: self.before_selection,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.ops.is_empty()
    }

    pub fn extend(&mut self, other: &mut ChangeSet) {
        self.ops.append(&mut other.ops);
        self.after_cursor = other.after_cursor;
        self.after_selection = other.after_selection;
    }
}

#[derive(Debug, Clone)]
pub struct HistoryNode {
    pub id: u64,
    pub change: ChangeSet,
    pub inverse: ChangeSet,
    pub kind: ChangeKind,
    pub timestamp: Instant,
    pub version: Version,
}

#[derive(Debug)]
pub struct History {
    timeline: Vec<HistoryNode>,
    cursor: usize,
    merge_threshold: Duration,
    next_id: u64,
}

impl History {
    pub fn new() -> Self {
        Self {
            timeline: Vec::new(),
            cursor: 0,
            merge_threshold: Duration::from_millis(DEFAULT_MERGE_THRESHOLD_MS),
            next_id: 1,
        }
    }

    pub fn with_threshold(merge_threshold_ms: u64) -> Self {
        Self {
            timeline: Vec::new(),
            cursor: 0,
            merge_threshold: Duration::from_millis(merge_threshold_ms),
            next_id: 1,
        }
    }

    pub fn can_undo(&self) -> bool {
        self.cursor > 0
    }

    pub fn can_redo(&self) -> bool {
        self.cursor < self.timeline.len()
    }

    pub fn push(&mut self, change: ChangeSet, kind: ChangeKind, version: Version) {
        let id = self.next_id;
        self.next_id += 1;

        let inverse = change.invert();
        let timestamp = Instant::now();

        let node = HistoryNode {
            id,
            change,
            inverse,
            kind,
            timestamp,
            version,
        };

        if self.cursor < self.timeline.len() {
            self.timeline.truncate(self.cursor);
        }

        self.timeline.push(node);
        self.cursor += 1;

        if self.timeline.len() > MAX_HISTORY_SIZE {
            self.timeline.remove(0);
            self.cursor = self.cursor.saturating_sub(1);
        }
    }

    pub fn try_merge(&mut self, new_change: ChangeSet, kind: ChangeKind, version: Version) -> bool {
        if self.timeline.is_empty() || self.cursor == 0 {
            return false;
        }

        let last_idx = self.cursor - 1;
        let last_node = &self.timeline[last_idx];

        if last_node.kind != kind {
            return false;
        }

        if last_node.timestamp.elapsed() > self.merge_threshold {
            return false;
        }

        if !can_merge_changes(&last_node.change, &new_change, kind) {
            return false;
        }

        let last_node = &mut self.timeline[last_idx];
        last_node.change.extend(&mut new_change.clone());
        last_node.inverse = last_node.change.invert();
        last_node.version = version;

        true
    }

    pub fn undo(&mut self) -> Option<&HistoryNode> {
        if !self.can_undo() {
            return None;
        }

        self.cursor -= 1;
        Some(&self.timeline[self.cursor])
    }

    pub fn redo(&mut self) -> Option<&HistoryNode> {
        if !self.can_redo() {
            return None;
        }

        let node = &self.timeline[self.cursor];
        self.cursor += 1;
        Some(node)
    }

    pub fn clear(&mut self) {
        self.timeline.clear();
        self.cursor = 0;
    }

    pub fn len(&self) -> usize {
        self.timeline.len()
    }

    pub fn is_empty(&self) -> bool {
        self.timeline.is_empty()
    }

    pub fn current_position(&self) -> usize {
        self.cursor
    }

    pub fn timeline_entries(&self) -> Vec<(ChangeSet, ChangeSet, Version)> {
        self.timeline
            .iter()
            .map(|n| (n.change.clone(), n.inverse.clone(), n.version))
            .collect()
    }

    pub fn restore_from_snapshots(
        &mut self,
        undo_stack: Vec<super::buffer::SerializableSnapshot>,
        redo_stack: Vec<super::buffer::SerializableSnapshot>,
        _current_version: super::buffer::Version,
        current_lines: Vec<String>,
    ) {
        self.timeline.clear();
        self.next_id = 1;

        // If there are no snapshots at all, nothing to restore.
        if undo_stack.is_empty() && redo_stack.is_empty() {
            self.cursor = 0;
            return;
        }

        // New on-disk format: undo_stack is expected to include a base snapshot as
        // the first element (state before any changes). To support older formats
        // where the base snapshot was not stored, detect that case and synthesize
        // a base snapshot from the provided current_lines.
        // If undo_stack already contains a plausible base (first entry with
        // content differing from later entries), use it as-is.


        // Build combined sequential snapshots (as lightweight tuples):
        // (lines, cursor, selection, dirty, version)
        let orig_undo_len = undo_stack.len();
        let has_base = !undo_stack.is_empty() && undo_stack[0].lines().is_empty();
        let mut all: Vec<SnapshotEntry> = Vec::new();

        if has_base {
            // Synthesize a base snapshot from current_lines and include existing undo entries.
            all.push((current_lines.clone(), Position::new(0, 0), None, false, undo_stack[0].version()));
            for s in undo_stack.into_iter() {
                all.push((s.lines().to_vec(), s.cursor(), s.selection(), s.dirty(), s.version()));
            }
        } else {
            for s in undo_stack.into_iter() {
                all.push((s.lines().to_vec(), s.cursor(), s.selection(), s.dirty(), s.version()));
            }
        }
        let undo_included = if has_base { 1 + orig_undo_len } else { orig_undo_len };

        for s in redo_stack.into_iter() {
            all.push((s.lines().to_vec(), s.cursor(), s.selection(), s.dirty(), s.version()));
        }

        // Number of undo transitions equals number of undo snapshots included minus one
        // (because we include a base snapshot as the first entry).
        let undo_transitions = undo_included.saturating_sub(1);


        // Construct timeline of transitions between consecutive snapshots. Each
        // transition is represented as a single Replace op spanning the whole
        // previous snapshot's content to the next one. This allows undo/redo to
        // restore full text state.
        for i in 0..(all.len().saturating_sub(1)) {
            let prev = &all[i];
            let curr = &all[i + 1];

            let prev_text = prev.0.join("\n");
            let curr_text = curr.0.join("\n");

            // Compute end position for the previous snapshot to define the replace range.
            let prev_line_count = prev.0.len();
            let end_pos = if prev_line_count == 0 {
                Position::new(0, 0)
            } else {
                let last_idx = prev_line_count - 1;
                let last_len = prev.0[last_idx].chars().count();
                Position::new(last_idx, last_len)
            };

            let change_op = crate::editor::history::BufferOp::Replace {
                range: Position::new(0, 0)..end_pos,
                text: curr_text,
                old_text: prev_text,
                end_position: curr.1,
            };

            let change = ChangeSet::new(
                vec![change_op],
                prev.1,
                curr.1,
                prev.2,
                curr.2,
            );

            let inverse = change.invert();

            self.timeline.push(HistoryNode {
                id: self.next_id,
                change,
                inverse,
                kind: ChangeKind::Structural,
                timestamp: Instant::now(),
                version: curr.4,
            });
            self.next_id += 1;
        }

        // Set the history cursor to point at the last undo transition: number of
        // undo transitions equals undo_stack.len() - 1 when a base snapshot is
        // included.
        self.cursor = if all.is_empty() { 0 } else { undo_transitions };
    }
}

impl Default for History {
    fn default() -> Self {
        Self::new()
    }
}

fn can_merge_changes(a: &ChangeSet, b: &ChangeSet, kind: ChangeKind) -> bool {
    if a.after_cursor != b.before_cursor || a.after_selection != b.before_selection {
        return false;
    }

    match kind {
        ChangeKind::InsertText => can_merge_op(a, b, true),
        ChangeKind::DeleteText => can_merge_op(a, b, false),
        _ => false,
    }
}

fn can_merge_op(a: &ChangeSet, b: &ChangeSet, is_insert: bool) -> bool {
    // The new change must be a single-op edit.  The existing entry may already hold
    // multiple ops from prior merges, so we only require it to be non-empty and
    // check its *last* op for adjacency.
    if a.ops.is_empty() || b.ops.len() != 1 {
        return false;
    }

    let last_op_a = &a.ops[a.ops.len() - 1];
    let op_b = &b.ops[0];

    match (last_op_a, op_b) {
        (
            BufferOp::Replace {
                range: ra,
                text: ta,
                old_text: oa,
                end_position: ea,
            },
            BufferOp::Replace {
                range: rb,
                text: tb,
                old_text: ob,
                ..
            },
        ) => {
            if is_insert {
                // For insert: old_text must be empty, and new text can't span lines.
                if !oa.is_empty() || !ob.is_empty() {
                    return false;
                }
                if ta.contains('\n') || tb.contains('\n') {
                    return false;
                }
                *ea == rb.start
            } else {
                // For delete: new text must be empty, and old text can't span lines.
                if !ta.is_empty() || !tb.is_empty() {
                    return false;
                }
                if oa.contains('\n') || ob.contains('\n') {
                    return false;
                }
                rb.end == ra.start
            }
        }
        _ => false,
    }
}

pub fn apply_changeset(buffer: &mut crate::editor::buffer::Buffer, cs: &ChangeSet) {
    for op in &cs.ops {
        apply_op(buffer, op);
    }

    buffer.set_cursor(cs.after_cursor);
    buffer.normalize_cursor();
    buffer.set_selection(cs.after_selection);
}

fn apply_op(buffer: &mut crate::editor::buffer::Buffer, op: &BufferOp) {
    buffer.apply_op_without_history(op);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_changeset_invert_swaps_cursor() {
        let cs = ChangeSet::new(
            vec![],
            Position::new(1, 2),
            Position::new(3, 4),
            Some(Selection::new(Position::new(1, 2), Position::new(1, 5))),
            Some(Selection::new(Position::new(3, 4), Position::new(3, 7))),
        );

        let inv = cs.invert();

        assert_eq!(inv.before_cursor, Position::new(3, 4));
        assert_eq!(inv.after_cursor, Position::new(1, 2));
        assert_eq!(inv.before_selection.unwrap().anchor, Position::new(3, 4));
        assert_eq!(inv.after_selection.unwrap().anchor, Position::new(1, 2));
    }

    #[test]
    fn test_history_push_truncates_future() {
        let mut history = History::new();
        let cs = create_empty_changeset(Position::new(0, 0));

        history.push(cs.clone(), ChangeKind::InsertText, Version::new());
        history.push(cs.clone(), ChangeKind::InsertText, Version::new());
        assert_eq!(history.len(), 2);
        assert!(history.can_undo());
        assert!(!history.can_redo());

        history.undo();
        history.push(cs, ChangeKind::DeleteText, Version::new());
        assert_eq!(history.len(), 2);
        assert!(!history.can_redo());
    }

    #[test]
    fn test_history_undo_redo() {
        let mut history = History::new();
        let _buffer = create_test_buffer();

        let cs = create_changeset(
            Position::new(0, 0),
            Position::new(0, 5),
            vec![BufferOp::Replace {
                range: Position::new(0, 0)..Position::new(0, 0),
                text: "hello".to_string(),
                old_text: String::new(),
                end_position: Position::new(0, 5),
            }],
        );

        history.push(cs, ChangeKind::InsertText, Version::new());
        assert!(history.undo().is_some());
        assert!(history.redo().is_some());
    }

    #[test]
    fn test_try_merge_same_kind_within_threshold() {
        let mut history = History::new();

        let cs1 = create_changeset(
            Position::new(0, 0),
            Position::new(0, 1),
            vec![BufferOp::Replace {
                range: Position::new(0, 0)..Position::new(0, 1),
                text: "h".to_string(),
                old_text: String::new(),
                end_position: Position::new(0, 1),
            }],
        );

        let cs2 = create_changeset(
            Position::new(0, 1),
            Position::new(0, 2),
            vec![BufferOp::Replace {
                range: Position::new(0, 1)..Position::new(0, 2),
                text: "e".to_string(),
                old_text: String::new(),
                end_position: Position::new(0, 2),
            }],
        );

        history.push(cs1, ChangeKind::InsertText, Version::new());
        let merged = history.try_merge(cs2, ChangeKind::InsertText, Version::new());

        assert!(merged);
        assert_eq!(history.len(), 1);
    }

    #[test]
    fn test_try_merge_different_kind_fails() {
        let mut history = History::new();

        let cs1 = create_empty_changeset(Position::new(0, 0));
        let cs2 = create_empty_changeset(Position::new(0, 1));

        history.push(cs1, ChangeKind::InsertText, Version::new());
        let merged = history.try_merge(cs2, ChangeKind::DeleteText, Version::new());

        assert!(!merged);
        assert_eq!(history.len(), 1);
    }

    #[test]
    fn test_try_merge_different_cursor_fails() {
        let mut history = History::new();

        let cs1 = create_changeset(Position::new(0, 0), Position::new(0, 1), vec![]);
        let cs2 = create_changeset(Position::new(1, 0), Position::new(1, 1), vec![]);

        history.push(cs1, ChangeKind::InsertText, Version::new());
        let merged = history.try_merge(cs2, ChangeKind::InsertText, Version::new());

        assert!(!merged);
    }

    fn create_test_buffer() -> crate::editor::buffer::Buffer {
        crate::editor::buffer::Buffer::new()
    }

    fn create_empty_changeset(cursor: Position) -> ChangeSet {
        ChangeSet::new(vec![], cursor, cursor, None, None)
    }

    fn create_changeset(
        before_cursor: Position,
        after_cursor: Position,
        ops: Vec<BufferOp>,
    ) -> ChangeSet {
        ChangeSet::new(ops, before_cursor, after_cursor, None, None)
    }
}
