use std::path::PathBuf;

use docx_review_core::{
    extract_from_path, extract_from_path_with_opts, BlockKind, ExtractOptions, SpanChangeKind,
    TrackChangesMode, TrackedChangeKind,
};

fn fixture_path(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fixtures")
        .join(name)
}

#[test]
fn extracts_expected_content_from_real_fixture() {
    let document = extract_from_path(fixture_path("review_mixed_example.docx")).unwrap();

    assert_eq!(document.blocks.len(), 6);
    assert_eq!(document.tracked_changes.len(), 4);
    assert_eq!(document.comments.len(), 4);

    assert_eq!(document.blocks[0].text_spans.as_ref().unwrap().len(), 1);
    assert_eq!(
        document.blocks[2]
            .text_spans
            .as_ref()
            .unwrap()
            .iter()
            .map(|span| span.text.as_str())
            .collect::<String>(),
        document.blocks[2].text
    );

    assert!(matches!(
        document.tracked_changes[0].kind,
        TrackedChangeKind::Replacement { .. }
    ));
    assert!(matches!(
        document.blocks[2].text_spans.as_ref().unwrap()[1].tracked_changes[0].kind,
        SpanChangeKind::Replacement
    ));

    assert_eq!(document.comments[0].anchored_text.as_deref(), Some("Lorem Ipsum"));
    assert_eq!(document.comments[0].anchors.len(), 1);
    assert_eq!(document.comments[0].anchors[0].char_start, 0);
    assert_eq!(document.comments[0].anchors[0].char_end, 11);
}

#[test]
fn both_mode_exposes_raw_changes_for_real_fixture() {
    let mut opts = ExtractOptions::default();
    opts.track_changes_mode = TrackChangesMode::Both;
    opts.include_raw_ids = true;

    let document = extract_from_path_with_opts(
        fixture_path("review_mixed_example.docx"),
        opts,
    )
    .unwrap();

    assert_eq!(document.tracked_changes.len(), 4);
    assert_eq!(document.raw_changes.len(), 7);
    assert!(document
        .raw_changes
        .iter()
        .any(|change| matches!(change.kind, TrackedChangeKind::Delete { .. })));
    assert!(document
        .raw_changes
        .iter()
        .any(|change| matches!(change.kind, TrackedChangeKind::Insert { .. })));
    assert!(document
        .raw_changes
        .iter()
        .any(|change| change.raw_revision_ids.insert.is_some() || change.raw_revision_ids.delete.is_some()));
}

#[test]
fn resolved_comment_fixture_marks_comment_as_resolved() {
    let document = extract_from_path(fixture_path("resolved_comment.docx")).unwrap();

    assert_eq!(document.comments.len(), 1);
    assert_eq!(document.comments[0].resolved, Some(true));
    assert_eq!(document.comments[0].anchored_text.as_deref(), Some("simply dummy text"));
    assert_eq!(document.comments[0].anchors[0].block_id, "body:block:000001");
}

#[test]
fn comments_without_extended_part_still_extract_cleanly() {
    let document = extract_from_path(fixture_path("comments_no_extended.docx")).unwrap();

    assert_eq!(document.comments.len(), 1);
    assert_eq!(document.comments[0].resolved, None);
    assert!(document.comments[0].parent_id.is_none());
    assert!(document.comments[0].replies.is_empty());
    assert_eq!(document.comments[0].anchored_text.as_deref(), Some("dummy text"));
}

#[test]
fn overlapping_comment_and_change_fixture_keeps_both_views() {
    let document = extract_from_path(fixture_path("comment_overlapping_change.docx")).unwrap();

    assert_eq!(document.comments.len(), 1);
    assert_eq!(document.tracked_changes.len(), 1);
    assert!(matches!(
        document.tracked_changes[0].kind,
        TrackedChangeKind::Insert { .. }
    ));
    assert_eq!(document.comments[0].anchored_text.as_deref(), Some("track change"));
}

#[test]
fn table_comment_fixture_anchors_to_table_cell_block() {
    let document = extract_from_path(fixture_path("table_comment.docx")).unwrap();

    assert_eq!(document.comments.len(), 1);
    assert_eq!(
        document.comments[0].anchors[0].block_id,
        "body:table:1:row:0:col:1"
    );
    assert_eq!(document.comments[0].anchored_text.as_deref(), Some("Cell 2"));
}

#[test]
fn table_change_fixture_binds_changes_to_table_cells() {
    let document = extract_from_path(fixture_path("table_change.docx")).unwrap();

    assert_eq!(document.tracked_changes.len(), 2);
    assert!(document
        .tracked_changes
        .iter()
        .any(|change| change.block_ids == vec!["body:table:1:row:0:col:1".to_string()]));
    assert!(document
        .tracked_changes
        .iter()
        .any(|change| change.block_ids == vec!["body:table:1:row:1:col:0".to_string()]));
    assert!(matches!(document.blocks[1].kind, BlockKind::TableCell { .. }));
}

#[test]
fn insertion_only_fixture_marks_insert_span_and_change() {
    let document = extract_from_path(fixture_path("insert_only.docx")).unwrap();

    assert_eq!(document.tracked_changes.len(), 1);
    assert!(matches!(
        document.tracked_changes[0].kind,
        TrackedChangeKind::Insert { .. }
    ));
    assert_eq!(
        document.blocks[0].text_spans.as_ref().unwrap()[1].tracked_changes[0].kind,
        SpanChangeKind::Insert
    );
}

#[test]
fn deletion_only_fixture_preserves_current_view_and_delete_change() {
    let document = extract_from_path(fixture_path("delete_only.docx")).unwrap();

    assert_eq!(document.tracked_changes.len(), 1);
    assert!(matches!(
        document.tracked_changes[0].kind,
        TrackedChangeKind::Delete { .. }
    ));
    assert_eq!(
        document.blocks[0]
            .text_spans
            .as_ref()
            .unwrap()
            .iter()
            .map(|span| span.text.as_str())
            .collect::<String>(),
        document.blocks[0].text
    );
}

#[test]
fn format_change_fixture_exposes_format_changes_and_span_markers() {
    let document = extract_from_path(fixture_path("format_change.docx")).unwrap();

    assert_eq!(document.tracked_changes.len(), 2);
    assert!(document
        .tracked_changes
        .iter()
        .all(|change| matches!(change.kind, TrackedChangeKind::Format { .. })));
    assert!(document.blocks[0]
        .text_spans
        .as_ref()
        .unwrap()
        .iter()
        .any(|span| span
            .tracked_changes
            .iter()
            .any(|change| change.kind == SpanChangeKind::Format)));
}

#[test]
fn move_change_fixture_exposes_paired_move_revisions() {
    let document = extract_from_path(fixture_path("move_change.docx")).unwrap();

    assert_eq!(document.tracked_changes.len(), 2);
    assert!(matches!(
        document.tracked_changes[0].kind,
        TrackedChangeKind::MoveFrom { .. }
    ));
    assert!(matches!(
        document.tracked_changes[1].kind,
        TrackedChangeKind::MoveTo { .. }
    ));
    assert_eq!(
        document.tracked_changes[0].move_pair_id.as_deref(),
        Some("mv-1")
    );
    assert_eq!(
        document.tracked_changes[1].move_pair_id.as_deref(),
        Some("mv-1")
    );
    assert_eq!(document.blocks.len(), 2);
    assert_eq!(document.blocks[0].text, "Alpha ");
    assert_eq!(document.blocks[1].text, "Gamma Beta");
}

#[test]
fn replacement_fixture_exposes_single_replacement_change() {
    let document = extract_from_path(fixture_path("replacement.docx")).unwrap();

    assert_eq!(document.tracked_changes.len(), 1);
    assert!(matches!(
        &document.tracked_changes[0].kind,
        TrackedChangeKind::Replacement { old_text, new_text }
            if old_text == "popularised " && new_text == "replaced "
    ));
    assert_eq!(
        document.blocks[0].text_spans.as_ref().unwrap()[1].tracked_changes[0].kind,
        SpanChangeKind::Replacement
    );
}

#[test]
fn footnote_basic_fixture_exposes_story_and_inline_reference() {
    let document = extract_from_path(fixture_path("footnote_basic.docx")).unwrap();

    assert!(document
        .blocks
        .iter()
        .any(|block| block.footnote_refs == vec![1]));
    assert!(document
        .blocks
        .iter()
        .any(|block| block.story == docx_review_core::Story::Footnote { index: 1 }
            && block.text.trim() == "Footnote text"));
}

#[test]
fn endnote_basic_fixture_exposes_story_and_inline_reference() {
    let document = extract_from_path(fixture_path("endnote_basic.docx")).unwrap();

    assert!(document
        .blocks
        .iter()
        .any(|block| block.endnote_refs == vec![1]));
    assert!(document
        .blocks
        .iter()
        .any(|block| block.story == docx_review_core::Story::Endnote { index: 1 }
            && block.text.trim() == "Endnote text"));
}

#[test]
fn header_footer_basic_fixture_exposes_both_stories() {
    let document = extract_from_path(fixture_path("header_footer_basic.docx")).unwrap();

    assert!(document
        .blocks
        .iter()
        .any(|block| block.story == docx_review_core::Story::Header { index: 1 }
            && block.text == "Header text"));
    assert!(document
        .blocks
        .iter()
        .any(|block| block.story == docx_review_core::Story::Footer { index: 1 }
            && block.text == "Footer text"));
}
