use super::super::*;
use super::helpers::dummy_input_state;
use crate::draw::frame::{ShapeSnapshot, UndoAction};
use crate::draw::{Color, Shape};
use crate::input::BOARD_ID_TRANSPARENT;
use std::fs;

#[test]
fn snapshot_preserves_history_only_frames() {
    let temp = tempfile::tempdir().unwrap();
    let mut options = SessionOptions::new(temp.path().to_path_buf(), "display-history");
    options.persist_transparent = true;
    options.persist_history = true;

    let mut input = dummy_input_state();
    let frame = input.boards.active_frame_mut();
    let id = frame.add_shape(Shape::Line {
        x1: 0,
        y1: 0,
        x2: 50,
        y2: 50,
        color: Color {
            r: 1.0,
            g: 0.0,
            b: 0.0,
            a: 1.0,
        },
        thick: 3.0,
    });
    let index = frame.find_index(id).unwrap();
    frame.push_undo_action(
        UndoAction::Create {
            shapes: vec![(index, frame.shape(id).unwrap().clone())],
        },
        input.undo_stack_limit,
    );
    // Undo to place the action in the redo stack and clear the visible canvas.
    frame.undo_last();

    assert!(frame.shapes.is_empty());
    assert_eq!(frame.redo_stack_len(), 1);

    let snapshot = snapshot_from_input(&input, &options).expect("snapshot present");
    save_snapshot(&snapshot, &options).expect("save snapshot");

    let loaded = load_snapshot(&options)
        .expect("load snapshot result")
        .expect("snapshot data");

    let mut restored = dummy_input_state();
    apply_snapshot(&mut restored, loaded, &options);
    restored.switch_board(BOARD_ID_TRANSPARENT);
    let frame = restored.boards.active_frame_mut();
    assert_eq!(frame.shapes.len(), 0);
    assert_eq!(frame.redo_stack_len(), 1);
    frame.redo_last();
    assert_eq!(frame.shapes.len(), 1);
}

#[test]
fn modify_delete_cycle_survives_restore() {
    let temp = tempfile::tempdir().unwrap();
    let mut options = SessionOptions::new(temp.path().to_path_buf(), "display-modify-delete");
    options.persist_transparent = true;
    options.persist_history = true;

    let mut input = dummy_input_state();
    let frame = input.boards.active_frame_mut();
    let id = frame.add_shape(Shape::Line {
        x1: 0,
        y1: 0,
        x2: 10,
        y2: 10,
        color: Color {
            r: 1.0,
            g: 0.0,
            b: 0.0,
            a: 1.0,
        },
        thick: 2.0,
    });
    let index = frame.find_index(id).unwrap();
    frame.push_undo_action(
        UndoAction::Create {
            shapes: vec![(index, frame.shape(id).unwrap().clone())],
        },
        input.undo_stack_limit,
    );

    // Modify the shape (simulate an edit).
    frame.push_undo_action(
        UndoAction::Modify {
            shape_id: id,
            before: ShapeSnapshot {
                shape: frame.shape(id).unwrap().shape.clone(),
                locked: false,
            },
            after: ShapeSnapshot {
                shape: Shape::Line {
                    x1: 0,
                    y1: 0,
                    x2: 20,
                    y2: 20,
                    color: Color {
                        r: 0.0,
                        g: 0.0,
                        b: 0.0,
                        a: 1.0,
                    },
                    thick: 1.0,
                },
                locked: false,
            },
        },
        input.undo_stack_limit,
    );

    // Delete it (pushes a Delete with embedded shape data).
    let delete_action = frame
        .remove_shape_by_id(id)
        .map(|(idx, shape)| UndoAction::Delete {
            shapes: vec![(idx, shape)],
        });
    frame.push_undo_action(delete_action.unwrap(), input.undo_stack_limit);

    assert!(frame.shapes.is_empty());
    assert!(frame.undo_stack_len() >= 3);

    let snapshot = snapshot_from_input(&input, &options).expect("snapshot present");
    save_snapshot(&snapshot, &options).expect("save snapshot");

    let loaded = load_snapshot(&options)
        .expect("load snapshot result")
        .expect("snapshot data");

    let mut restored = dummy_input_state();
    apply_snapshot(&mut restored, loaded, &options);
    restored.switch_board(BOARD_ID_TRANSPARENT);
    let frame = restored.boards.active_frame_mut();

    // Undo should re-create the deleted shape, then undo the modify, restoring original coords.
    frame.undo_last(); // undo delete -> shape back (modified version)
    frame.undo_last(); // undo modify -> original version

    let restored_shape = frame.shape(id).expect("shape restored");
    if let Shape::Line { x2, y2, .. } = restored_shape.shape {
        assert_eq!((x2, y2), (10, 10));
    } else {
        panic!("Expected line shape");
    }
}

#[test]
fn clear_all_can_be_undone_after_restore() {
    let temp = tempfile::tempdir().unwrap();
    let mut options = SessionOptions::new(temp.path().to_path_buf(), "display-clear-all");
    options.persist_transparent = true;
    options.persist_history = true;

    let mut input = dummy_input_state();
    let frame = input.boards.active_frame_mut();
    for i in 0..3 {
        frame.add_shape(Shape::Rect {
            x: i * 10,
            y: i * 10,
            w: 5,
            h: 5,
            fill: false,
            color: Color {
                r: 1.0,
                g: 1.0,
                b: 0.0,
                a: 1.0,
            },
            thick: 1.0,
        });
    }

    assert_eq!(frame.shapes.len(), 3);
    assert!(input.clear_all());
    assert_eq!(input.boards.active_frame().shapes.len(), 0);
    assert!(input.boards.active_frame().undo_stack_len() > 0);

    let snapshot = snapshot_from_input(&input, &options).expect("snapshot present");
    save_snapshot(&snapshot, &options).expect("save snapshot");
    let loaded = load_snapshot(&options)
        .expect("load snapshot result")
        .expect("snapshot data");

    let mut restored = dummy_input_state();
    apply_snapshot(&mut restored, loaded, &options);
    restored.switch_board(BOARD_ID_TRANSPARENT);
    let frame = restored.boards.active_frame_mut();
    assert_eq!(frame.shapes.len(), 0);
    assert!(frame.undo_stack_len() > 0);
    frame.undo_last();
    assert_eq!(frame.shapes.len(), 3);
}

#[test]
fn corrupted_history_is_dropped_but_shapes_load() {
    let temp = tempfile::tempdir().unwrap();
    let mut options = SessionOptions::new(temp.path().to_path_buf(), "display-corrupt");
    options.persist_transparent = true;
    options.persist_history = true;

    let mut input = dummy_input_state();
    let frame = input.boards.active_frame_mut();
    let id = frame.add_shape(Shape::Line {
        x1: 1,
        y1: 1,
        x2: 2,
        y2: 2,
        color: Color {
            r: 0.0,
            g: 0.0,
            b: 0.0,
            a: 1.0,
        },
        thick: 1.5,
    });
    let index = frame.find_index(id).unwrap();
    frame.push_undo_action(
        UndoAction::Create {
            shapes: vec![(index, frame.shape(id).unwrap().clone())],
        },
        input.undo_stack_limit,
    );

    let snapshot = snapshot_from_input(&input, &options).expect("snapshot present");
    save_snapshot(&snapshot, &options).expect("save snapshot");

    // Corrupt the undo stack to force deserialization failure.
    let path = options.session_file_path();
    let data = fs::read_to_string(&path).expect("read session file");
    let corrupted = data.replace(
        "\"undo_stack\": [",
        "\"undo_stack\": [{\"kind\": \"compound\", \"garbage\": true},",
    );
    fs::write(&path, corrupted).expect("write corrupt session file");

    let loaded = load_snapshot(&options)
        .expect("load snapshot result")
        .expect("snapshot present after dropping history");

    let mut restored = dummy_input_state();
    apply_snapshot(&mut restored, loaded, &options);
    restored.switch_board(BOARD_ID_TRANSPARENT);
    let frame = restored.boards.active_frame();
    assert_eq!(frame.shapes.len(), 1);
    assert_eq!(frame.undo_stack_len(), 0);
    assert_eq!(frame.redo_stack_len(), 0);
}
