use crate::node::script::NodeScript::*;
use crate::node::script::NodeTest;
use lib_ot::core::{AttributeBuilder, Node};
use lib_ot::{
    core::{NodeBodyChangeset, NodeData, NodeDataBuilder, NodeOperation, Path},
    text_delta::TextDeltaBuilder,
};

#[test]
fn operation_insert_node_serde_test() {
    let insert = NodeOperation::Insert {
        path: Path(vec![0, 1]),
        nodes: vec![NodeData::new("text".to_owned())],
    };
    let result = serde_json::to_string(&insert).unwrap();
    assert_eq!(result, r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"#);
}

#[test]
fn operation_insert_node_with_children_serde_test() {
    let node = NodeDataBuilder::new("text")
        .add_node(NodeData::new("sub_text".to_owned()))
        .build();

    let insert = NodeOperation::Insert {
        path: Path(vec![0, 1]),
        nodes: vec![node],
    };
    assert_eq!(
        serde_json::to_string(&insert).unwrap(),
        r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"sub_text"}]}]}"#
    );
}
#[test]
fn operation_update_node_attributes_serde_test() {
    let operation = NodeOperation::UpdateAttributes {
        path: Path(vec![0, 1]),
        new: AttributeBuilder::new().insert("bold", true).build(),
        old: AttributeBuilder::new().insert("bold", false).build(),
    };

    let result = serde_json::to_string(&operation).unwrap();
    assert_eq!(
        result,
        r#"{"op":"update-attribute","path":[0,1],"new":{"bold":true},"old":{"bold":null}}"#
    );
}

#[test]
fn operation_update_node_body_serialize_test() {
    let delta = TextDeltaBuilder::new().insert("AppFlowy...").build();
    let inverted = delta.invert_str("");
    let changeset = NodeBodyChangeset::Delta { delta, inverted };
    let insert = NodeOperation::UpdateBody {
        path: Path(vec![0, 1]),
        changeset,
    };
    let result = serde_json::to_string(&insert).unwrap();
    assert_eq!(
        result,
        r#"{"op":"update-body","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"#
    );
    //
}

#[test]
fn operation_update_node_body_deserialize_test() {
    let json_1 = r#"{"op":"update-body","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"#;
    let operation: NodeOperation = serde_json::from_str(json_1).unwrap();
    let json_2 = serde_json::to_string(&operation).unwrap();
    assert_eq!(json_1, json_2);
}

#[test]
fn operation_insert_op_transform_test() {
    let node_1 = NodeDataBuilder::new("text_1").build();
    let node_2 = NodeDataBuilder::new("text_2").build();
    let op_1 = NodeOperation::Insert {
        path: Path(vec![0, 1]),
        nodes: vec![node_1],
    };

    let mut insert_2 = NodeOperation::Insert {
        path: Path(vec![0, 1]),
        nodes: vec![node_2],
    };

    // let mut node_tree = NodeTree::new("root");
    // node_tree.apply_op(insert_1.clone()).unwrap();

    op_1.transform(&mut insert_2);
    let json = serde_json::to_string(&insert_2).unwrap();
    assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#);
}

#[test]
fn operation_insert_transform_one_level_path_test() {
    let mut test = NodeTest::new();
    let node_data_1 = NodeDataBuilder::new("text_1").build();
    let node_data_2 = NodeDataBuilder::new("text_2").build();
    let node_data_3 = NodeDataBuilder::new("text_3").build();
    let node_3: Node = node_data_3.clone().into();
    //  0: text_1
    //  1: text_2
    //
    //  Insert a new operation with rev_id 1,but the rev_id:1 is already exist, so
    //  it needs to be transformed.
    //  1:text_3 => 2:text_3
    //
    //  0: text_1
    //  1: text_2
    //  2: text_3
    //
    //  If the rev_id of the insert operation is 3. then the tree will be:
    //  0: text_1
    //  1: text_3
    //  2: text_2
    let scripts = vec![
        InsertNode {
            path: 0.into(),
            node_data: node_data_1,
            rev_id: 1,
        },
        InsertNode {
            path: 1.into(),
            node_data: node_data_2,
            rev_id: 2,
        },
        InsertNode {
            path: 1.into(),
            node_data: node_data_3,
            rev_id: 1,
        },
        AssertNode {
            path: 2.into(),
            expected: Some(node_3),
        },
    ];
    test.run_scripts(scripts);
}

#[test]
fn operation_insert_transform_multiple_level_path_test() {
    let mut test = NodeTest::new();
    let node_data_1 = NodeDataBuilder::new("text_1")
        .add_node(NodeDataBuilder::new("text_1_1").build())
        .add_node(NodeDataBuilder::new("text_1_2").build())
        .build();

    let node_data_2 = NodeDataBuilder::new("text_2")
        .add_node(NodeDataBuilder::new("text_2_1").build())
        .add_node(NodeDataBuilder::new("text_2_2").build())
        .build();

    let node_data_3 = NodeDataBuilder::new("text_3").build();
    let scripts = vec![
        InsertNode {
            path: 0.into(),
            node_data: node_data_1,
            rev_id: 1,
        },
        InsertNode {
            path: 1.into(),
            node_data: node_data_2,
            rev_id: 2,
        },
        InsertNode {
            path: 1.into(),
            node_data: node_data_3.clone(),
            rev_id: 1,
        },
        AssertNode {
            path: 2.into(),
            expected: Some(node_data_3.into()),
        },
    ];
    test.run_scripts(scripts);
}

#[test]
fn operation_delete_transform_path_test() {
    let mut test = NodeTest::new();
    let node_data_1 = NodeDataBuilder::new("text_1").build();
    let node_data_2 = NodeDataBuilder::new("text_2").build();
    let node_data_3 = NodeDataBuilder::new("text_3").build();
    let node_3: Node = node_data_3.clone().into();

    let scripts = vec![
        InsertNode {
            path: 0.into(),
            node_data: node_data_1,
            rev_id: 1,
        },
        InsertNode {
            path: 1.into(),
            node_data: node_data_2,
            rev_id: 2,
        },
        // The node's in the tree will be:
        // 0: text_1
        // 2: text_2
        //
        // The insert action is happened concurrently with the delete action, because they
        // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1,
        // but it was moved to index 2.
        InsertNode {
            path: 1.into(),
            node_data: node_data_3,
            rev_id: 3,
        },
        // 0: text_1
        // 1: text_3
        // 2: text_2
        //
        // The path of the delete action will be transformed to a new path that point to the text_2.
        // 1 -> 2
        DeleteNode {
            path: 1.into(),
            rev_id: 3,
        },
        // After perform the delete action, the tree will be:
        // 0: text_1
        // 1: text_3
        AssertNumberOfNodesAtPath { path: None, len: 2 },
        AssertNode {
            path: 1.into(),
            expected: Some(node_3),
        },
        AssertNode {
            path: 2.into(),
            expected: None,
        },
    ];
    test.run_scripts(scripts);
}
