use super::*;

fn unique_person_graph_type() -> GraphTypeDef {
    GraphTypeDef {
        name: db_string("closed.unique.graph"),
        node_types: vec![NodeTypeDef {
            name: db_string("closed.unique.person"),
            key_labels: LabelSet::single(db_string("Person")),
            properties: vec![PropertyTypeDef {
                name: db_string("serial"),
                value_type: PropertyValueType::String,
                list_element_type: None,
                required: false,
                default: None,
                immutable: false,
                unique: true,
                decimal_type: None,
                character_string_type: None,
                byte_string_type: None,
                record_field_types: None,
            }],
            validation_mode: ValidationMode::Strict,
        }],
        edge_types: Vec::new(),
    }
}

fn unique_person_graph(graph_id: u64) -> SharedGraph {
    SharedGraph::builder(GraphId::new(graph_id))
        .bound_to(unique_person_graph_type())
        .unwrap()
        .build()
        .unwrap()
}

fn serial(value: &str) -> PropertyMap {
    prop("serial", Value::String(db_string(value)))
}

#[test]
fn duplicate_unique_node_property_rejects_commit() {
    let shared = unique_person_graph(31);
    let mut txn = shared.begin_write();
    let (first, second) = {
        let mut mutator = txn.mutator();
        let first = mutator
            .create_node(LabelSet::single(db_string("Person")), serial("A"))
            .unwrap();
        let second = mutator
            .create_node(LabelSet::single(db_string("Person")), serial("A"))
            .unwrap();
        (first, second)
    };

    let err = txn
        .commit()
        .expect_err("duplicate unique value rejects commit");

    assert!(matches!(
        err,
        GraphError::TypeViolation(TypeViolation::UniquePropertyDuplicate {
            entity_id,
            conflicting_entity_id,
            property,
            ..
        }) if entity_id == EntityId::Node(second)
            && conflicting_entity_id == EntityId::Node(first)
            && property == db_string("serial")
    ));
    assert_eq!(shared.read().node_count(), 0);
}

#[test]
fn duplicate_unique_node_property_update_rolls_back() {
    let shared = unique_person_graph(32);
    let (first, second) = {
        let mut txn = shared.begin_write();
        let (first, second) = {
            let mut mutator = txn.mutator();
            let first = mutator
                .create_node(LabelSet::single(db_string("Person")), serial("A"))
                .unwrap();
            let second = mutator
                .create_node(LabelSet::single(db_string("Person")), serial("B"))
                .unwrap();
            (first, second)
        };
        txn.commit().unwrap();
        (first, second)
    };

    let mut txn = shared.begin_write();
    txn.mutator()
        .update_node(
            second,
            LabelDiff::new([], []).unwrap(),
            PropertyDiff::new([(db_string("serial"), Value::String(db_string("A")))], []).unwrap(),
        )
        .unwrap();
    let err = txn
        .commit()
        .expect_err("duplicate unique update rejects commit");

    assert!(matches!(
        err,
        GraphError::TypeViolation(TypeViolation::UniquePropertyDuplicate {
            entity_id,
            conflicting_entity_id,
            property,
            ..
        }) if entity_id == EntityId::Node(second)
            && conflicting_entity_id == EntityId::Node(first)
            && property == db_string("serial")
    ));
    assert_eq!(
        shared
            .read()
            .node_properties(second)
            .and_then(|properties| properties.get(&db_string("serial"))),
        Some(&Value::String(db_string("B")))
    );
}

#[test]
fn duplicate_unique_node_property_update_reports_changed_entity() {
    let shared = unique_person_graph(34);
    let (first, second) = {
        let mut txn = shared.begin_write();
        let (first, second) = {
            let mut mutator = txn.mutator();
            let first = mutator
                .create_node(LabelSet::single(db_string("Person")), serial("A"))
                .unwrap();
            let second = mutator
                .create_node(LabelSet::single(db_string("Person")), serial("B"))
                .unwrap();
            (first, second)
        };
        txn.commit().unwrap();
        (first, second)
    };

    let mut txn = shared.begin_write();
    txn.mutator()
        .update_node(
            first,
            LabelDiff::new([], []).unwrap(),
            PropertyDiff::new([(db_string("serial"), Value::String(db_string("B")))], []).unwrap(),
        )
        .unwrap();
    let err = txn
        .commit()
        .expect_err("duplicate unique update rejects commit");

    assert!(matches!(
        err,
        GraphError::TypeViolation(TypeViolation::UniquePropertyDuplicate {
            entity_id,
            conflicting_entity_id,
            property,
            ..
        }) if entity_id == EntityId::Node(first)
            && conflicting_entity_id == EntityId::Node(second)
            && property == db_string("serial")
    ));
    assert_eq!(
        shared
            .read()
            .node_properties(first)
            .and_then(|properties| properties.get(&db_string("serial"))),
        Some(&Value::String(db_string("A")))
    );
}

#[test]
fn repeated_unique_node_property_update_to_same_value_commits() {
    let shared = unique_person_graph(35);
    let id = {
        let mut txn = shared.begin_write();
        let id = txn
            .mutator()
            .create_node(LabelSet::single(db_string("Person")), serial("A"))
            .unwrap();
        txn.commit().unwrap();
        id
    };

    let mut txn = shared.begin_write();
    txn.mutator()
        .update_node(
            id,
            LabelDiff::new([], []).unwrap(),
            PropertyDiff::new([(db_string("serial"), Value::String(db_string("B")))], []).unwrap(),
        )
        .unwrap();
    txn.mutator()
        .update_node(
            id,
            LabelDiff::new([], []).unwrap(),
            PropertyDiff::new([(db_string("serial"), Value::String(db_string("B")))], []).unwrap(),
        )
        .unwrap();
    txn.commit().unwrap();

    assert_eq!(
        shared
            .read()
            .node_properties(id)
            .and_then(|properties| properties.get(&db_string("serial"))),
        Some(&Value::String(db_string("B")))
    );
}

#[test]
fn unique_node_property_reuses_value_after_delete() {
    let shared = unique_person_graph(33);
    let first = {
        let mut txn = shared.begin_write();
        let first = txn
            .mutator()
            .create_node(LabelSet::single(db_string("Person")), serial("A"))
            .unwrap();
        txn.commit().unwrap();
        first
    };

    let mut txn = shared.begin_write();
    txn.mutator().delete_node(first).unwrap();
    txn.commit().unwrap();

    let mut txn = shared.begin_write();
    let second = txn
        .mutator()
        .create_node(LabelSet::single(db_string("Person")), serial("A"))
        .unwrap();
    txn.commit().unwrap();

    assert!(shared.read().is_node_alive(second));
}
