use indoc::indoc;
use liwe::graph::Graph;
use liwe::model::config::MarkdownOptions;
use liwe::query::execute;
use liwe::query::prelude::{eq, included_by, inclusion, update, update_op};
use liwe::query::{Filter, Outcome, Update, UpdateOp, UpdateOperator};
use liwe::state::{from_indoc, to_indoc};
use pretty_assertions::assert_str_eq;

fn assert_update(docs: &str, op: UpdateOp, expected: &str) {
    let state = from_indoc(docs);
    let graph = Graph::import(&state, MarkdownOptions::default(), None);
    let outcome = execute(&update(op), &graph);
    match outcome {
        Outcome::Update { changes } => {
            let mut new_state = graph.export();
            for (key, markdown) in changes {
                new_state.insert(key.to_string(), markdown);
            }
            assert_str_eq!(expected, to_indoc(&new_state));
        }
        other => panic!("expected Update, got {:?}", other),
    }
}

#[test]
fn set_writes_new_field() {
    assert_update(
        indoc! {"
            ---
            status: draft
            ---
            # A
        "},
        update_op(
            eq("status", "draft"),
            Update::new(vec![UpdateOperator::set("reviewed", true)]),
        ),
        indoc! {"
            ---
            status: draft
            reviewed: true
            ---

            # A
        "},
    );
}

#[test]
fn unset_removes_existing_field() {
    assert_update(
        indoc! {"
            ---
            status: draft
            reviewed: false
            ---
            # A
        "},
        update_op(
            eq("status", "draft"),
            Update::new(vec![UpdateOperator::unset("reviewed")]),
        ),
        indoc! {"
            ---
            status: draft
            ---

            # A
        "},
    );
}

#[test]
fn set_dotted_path_persists_nested_structure() {
    assert_update(
        indoc! {"
            ---
            status: draft
            ---
            # A
        "},
        update_op(
            eq("status", "draft"),
            Update::new(vec![
                UpdateOperator::set("author.name", "dmytro"),
                UpdateOperator::set("author.email", "d@example.com"),
            ]),
        ),
        indoc! {"
            ---
            status: draft
            author:
              name: dmytro
              email: d@example.com
            ---

            # A
        "},
    );
}

#[test]
fn update_creates_frontmatter_when_absent() {
    assert_update(
        indoc! {"
            # A

            body
        "},
        update_op(
            Filter::all(),
            Update::new(vec![UpdateOperator::set("status", "published")]),
        ),
        indoc! {"
            ---
            status: published
            ---

            # A

            body
        "},
    );
}

#[test]
fn update_only_matched_docs_change() {
    assert_update(
        indoc! {"
            ---
            status: draft
            ---
            # A
            _
            ---
            status: published
            ---
            # B
        "},
        update_op(
            eq("status", "draft"),
            Update::new(vec![UpdateOperator::set("reviewed", true)]),
        ),
        indoc! {"
            ---
            status: draft
            reviewed: true
            ---

            # A
            _
            ---
            status: published
            ---

            # B
        "},
    );
}

#[test]
fn update_with_graph_filter_targets_descendants() {
    assert_update(
        indoc! {"
            ---
            status: draft
            ---
            [b](2)
            _
            ---
            status: draft
            ---
            # B
            _
            ---
            status: draft
            ---
            # C
        "},
        update_op(
            included_by(inclusion("1", 5)),
            Update::new(vec![UpdateOperator::set("reviewed", true)]),
        ),
        indoc! {"
            ---
            status: draft
            ---

            [B](2)
            _
            ---
            status: draft
            reviewed: true
            ---

            # B
            _
            ---
            status: draft
            ---

            # C
        "},
    );
}
