use crate::{catalog::Catalog, queries::ALL_QUERIES, test_helpers::*, *};
use horned_owl::{
    io::{OWXParserConfiguration, ParserConfiguration, RDFParserConfiguration},
    model::{AnnotatedComponent, Build},
};
use indoc::indoc;
use pos::Position;
use pretty_assertions::assert_eq;
use ropey::Rope;
use sophia::api::term::SimpleTerm;
use tempdir::TempDir;
use test_log::test;
use tower_lsp::LspService;

/// This module contains tests.
/// Each test function name is in the form of `<function>_<thing>_<condition>_<expectation>`.

/////////////////////////
// Tree sitter tests
/////////////////////////

#[test]
fn parse_ontology_should_work() {
    setup();
    // Arrange
    let mut parser = arrange_parser();

    let source_code = "Ontology: Foobar";

    // Act
    let tree = parser.parse(source_code, None).unwrap();

    // Assert
    assert_eq!(tree.root_node().has_error(), false);
}

#[test]
/// This test should initilize all queries and therefore check if they are valid
fn deref_all_queries_should_be_valid() {
    setup();
    let _ = *ALL_QUERIES;
}

///////////////////////////////////////////
// Backend Tests
//////////////////////////////////////////

#[test]
#[ignore = "This was just a spice for messing around"]
fn horned_owl_should_parse_rdf_xml() {
    let x = r##"    
<rdf:RDF xmlns="http://www.example.com/iri#"
     xml:base="http://www.example.com/iri"
     xmlns:o="http://www.example.com/iri#"
     xmlns:owl="http://www.w3.org/2002/07/owl#"
     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:xml="http://www.w3.org/XML/1998/namespace"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
    <owl:Ontology rdf:about="http://www.example.com/iri">
        <owl:versionIRI rdf:resource="http://www.example.com/viri"/>
    </owl:Ontology>

    <owl:Class rdf:about="http://www.example.com/iri#C"/>

</rdf:RDF>"##;
    let mut buffer = x.as_bytes();

    let b = Build::new_string();

    let a =
        horned_owl::io::rdf::reader::read_with_build::<String, AnnotatedComponent<String>, &[u8]>(
            &mut buffer,
            &b,
            ParserConfiguration {
                rdf: RDFParserConfiguration { lax: true },
                owx: OWXParserConfiguration::default(),
            },
        )
        .unwrap();
    info!("{a:?}");
}

#[test]
#[ignore = "This was just a spice for messing around"]
fn sophia_should_parse_rdf_xml() {
    use sophia::api::prelude::*;
    use sophia::inmem::graph::LightGraph;

    use sophia::turtle::serializer::nt::NtSerializer;
    let x = r##"    
<rdf:RDF xmlns="http://www.example.com/iri#"
     xml:base="http://www.example.com/iri"
     xmlns:o="http://www.example.com/iri#"
     xmlns:owl="http://www.w3.org/2002/07/owl#"
     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:xml="http://www.w3.org/XML/1998/namespace"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
    <owl:Ontology rdf:about="http://www.example.com/iri">
        <owl:versionIRI rdf:resource="http://www.example.com/viri"/>
    </owl:Ontology>

    <owl:Class rdf:about="http://www.example.com/iri#C"/>

    <rdf:Property rdf:about="http://www.w3.org/2000/01/rdf-schema#seeAlso">
      <rdfs:isDefinedBy rdf:resource="http://www.w3.org/2000/01/rdf-schema#"/>
      <rdfs:label>seeAlso</rdfs:label>
      <rdfs:comment>Further information about the subject resource.</rdfs:comment>
      <rdfs:range rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
      <rdfs:domain   rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
    </rdf:Property>
</rdf:RDF>"##;

    let graph: LightGraph = sophia::xml::parser::parse_str(x).collect_triples().unwrap();

    let triples = graph
        .triples_matching(
            |s: SimpleTerm| {
                s.iri()
                    .map(|iri| iri.as_str() == "http://www.w3.org/2000/01/rdf-schema#seeAlso")
                    .unwrap_or(false)
            },
            Any,
            Any,
        )
        .flatten()
        .flat_map(
            |[subject, predicate, object]| match (subject, predicate, object) {
                (SimpleTerm::Iri(r), _, c) => {
                    info!("Found one {c:#?}");
                    Some(r)
                }
                _ => None,
            },
        );
    for a in triples {
        info!("{a}");
    }

    // for [a, b, c] in triples {
    //     let a = a.iri().unwrap();
    //     let a = a.as_str();
    //     let b = b.iri().unwrap();
    //     let b = b.as_str();
    //     let c = c.iri().unwrap();
    //     let c = c.as_str();
    //     info!("{a},{b},{c}");
    // }

    let mut nt_stringifier = NtSerializer::new_stringifier();
    let example2 = nt_stringifier.serialize_graph(&graph).unwrap().as_str();
    info!("nt={example2:#?}");
}

#[test(tokio::test)]
async fn backend_did_open_should_create_document() {
    setup();
    // Arrange
    let service = arrange_backend(None, vec![]).await;

    let dir = TempDir::new("owl-ms-test").unwrap();
    let url = Url::from_file_path(dir.path().join("foo.omn")).unwrap();

    // Act

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: "abc".to_string(),
            },
        })
        .await;

    // Assert
    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    let workspace = workspaces.iter().exactly_one().unwrap();

    let docs = workspace.internal_documents().collect_vec();

    let doc = docs
        .iter()
        .exactly_one()
        .unwrap_or_else(|_| panic!("Should be exactly one"));

    assert_eq!(doc.uri, url);
    assert_eq!(doc.version, 0);
    assert_eq!(doc.rope().to_string(), "abc");
    assert!(doc.tree().root_node().is_error());
}

/// This tests if the "did_change" feature works on the lsp. It takes the document DEF and adds two changes resolving in ABCDEFGHI.
#[test(tokio::test)]
async fn backend_did_change_should_update_internal_rope() -> error::Result<()> {
    setup();
    // Arrange
    let service = arrange_backend(None, vec![]).await;

    let dir = TempDir::new("owl-ms-test").unwrap();
    let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: ontology_url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: "DE😊F".to_string(),
            },
        })
        .await;

    {
        let sync = service.inner().read_sync().await;
        info!("{sync:#?}");
    }

    // Act
    service
        .inner()
        .did_change(DidChangeTextDocumentParams {
            text_document: VersionedTextDocumentIdentifier {
                uri: ontology_url.clone(),
                version: 2,
            },
            content_changes: vec![
                TextDocumentContentChangeEvent {
                    range: Some(lsp_types::Range {
                        start: lsp_types::Position::new(0, 0),
                        end: lsp_types::Position::new(0, 0),
                    }),
                    range_length: None,
                    text: "A😊BC".to_string(),
                },
                TextDocumentContentChangeEvent {
                    range: Some(lsp_types::Range {
                        start: lsp_types::Position::new(
                            0,
                            // Changes depend on each other in order. By the time that the first
                            // change is applied the line ends at charater 6 not 3.
                            14,
                        ),
                        end: lsp_types::Position::new(0, 14),
                    }),
                    range_length: None,
                    text: "GH😊I".to_string(),
                },
            ],
        })
        .await;

    // Assert
    let sync = service.inner().read_sync().await;
    info!("{sync:#?}");
    let (doc, _) = sync.get_internal_document(&ontology_url).unwrap();

    let doc_content = doc.rope().to_string();

    assert_eq!(doc_content, "A😊BCDE😊FGH😊I");
    Ok(())
}

#[test(tokio::test)]
async fn backend_hover_on_class_should_show_class_info() {
    setup();
    // Arrange
    let service = arrange_backend(
        None,
        vec![("http://www.w3.org/2000/01/rdf-schema#", "dummy")],
    )
    .await;

    let dir = TempDir::new("owl-ms-test").unwrap();
    let url = Url::from_file_path(dir.path().join("file.omn")).unwrap();

    let ontology = r#"
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: HoverOnto
            Class: Janek
                Annotations:
                    rdfs:label "Janek der Coder"@de
        AnnotationProperty: rdfs:label
    "#;
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act
    let hover_result = service
        .inner()
        .hover(HoverParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(3, 21),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    assert_empty_diagnostics(&service).await;
    let hover_result = hover_result.unwrap();
    let range = hover_result.range.unwrap();
    assert_eq!(
        range,
        // Range of the "Janek" in the ontology
        lsp_types::Range {
            start: lsp_types::Position::new(3, 19),
            end: lsp_types::Position::new(3, 24)
        }
    );

    let contents = match hover_result.contents {
        HoverContents::Scalar(MarkedString::String(str)) => str,
        _ => panic!("Did not think of that"),
    };

    assert!(contents.contains("Class"));
    assert!(contents.contains("**Janek der Coder**"));
    assert!(contents.contains("`label`: Janek der Coder"));
    assert!(contents.contains("IRI: Janek"));
}

async fn arrange_multi_file_ontology() -> (LspService<Backend>, TempDir) {
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![
            WorkspaceMember::Folder {
                name: "ontology-a".into(),
                children: vec![
                    WorkspaceMember::CatalogFile(
                        Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                            .with_uri("http://ontology-a.org/a1.omn", "a1.omn")
                            .with_uri("http://ontology-a.org/a2.omn", "a2.omn"),
                    ),
                    WorkspaceMember::OmnFile {
                        name: "a2.omn".into(),
                        content: r#"
                        Prefix: #comment
                            : #comment
                            <http://ontology-a.org/ontology#> #comment
                        Prefix: #comment
                            rdfs: #comment
                            <http://www.w3.org/2000/01/rdf-schema#> #comment
                        Ontology: #comment
                            <http://ontology-a.org/a2.omn> #comment
                            AnnotationProperty: rdfs:label
                            Class: #comment
                                ClassA2 #comment
                                Annotations: #comment
                                    rdfs:label #comment
                                    "Some class in A2", #comment
                                    test #comment
                                    "test" #comment
                            Class: test
                    "#
                        .into(),
                    },
                ],
            },
            WorkspaceMember::Folder {
                name: "ontology-b".into(),
                children: vec![
                    WorkspaceMember::CatalogFile(
                        Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                            .with_uri("http://ontology-b.org/b1.omn", "b1.omn")
                            .with_uri("http://ontology-b.org/b2.omn", "b2.omn"),
                    ),
                    WorkspaceMember::OmnFile {
                        name: "b2.omn".into(),
                        content: r#"
                        Prefix: : <http://ontology-a.org/ontology#>
                        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
                        Ontology: <http://ontology-b.org/b2.omn>
                            AnnotationProperty: rdfs:label
                            Class: ClassB2
                                Annotations:
                                    rdfs:label "Some class in B2"
                    "#
                        .into(),
                    },
                ],
            },
        ]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test wosrkpace".into(),
        }),
        vec![
            ("http://www.w3.org/2000/01/rdf-schema#", ""), //dummy
        ],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    let ontology = r#"
        Prefix: : <http://ontology-a.org/ontology#>
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://ontology-a.org/a1.omn>
            Import: <http://ontology-a.org/a2.omn>
            AnnotationProperty: rdfs:label
            Class: ClassA1
                Annotations:
                    rdfs:label "Some class in A1"
                SubClassOf: ClassA2,ClassB2

    "#;

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    (service, tmp_dir)
}

#[test(tokio::test)]
async fn backend_hover_in_multi_file_ontology_should_work() {
    setup();
    // Arrange
    let (service, tmp_dir) = arrange_multi_file_ontology().await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let hover_result = service
        .inner()
        .hover(HoverParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(9, 31),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    let hover_result = hover_result.unwrap();
    let range = hover_result.range.unwrap();
    assert_eq!(
        range,
        // Range of the "Janek" in the ontology
        lsp_types::Range {
            start: lsp_types::Position::new(9, 28),
            end: lsp_types::Position::new(9, 35)
        }
    );

    let contents = match hover_result.contents {
        HoverContents::Scalar(MarkedString::String(str)) => str,
        _ => panic!("Did not think of that"),
    };
    let sync = service.inner().read_sync().await;
    info!("{:#?}", sync.workspaces());
    info!("{contents}");
    assert!(contents.contains("Some class in A2"));
    assert!(contents.contains("test"));
}

#[test(tokio::test)]
async fn backend_hover_in_multi_file_ontology_on_not_imported_iri_should_not_work() {
    setup();
    // Arrange
    let (service, tmp_dir) = arrange_multi_file_ontology().await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let hover_result = service
        .inner()
        .hover(HoverParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(9, 38),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    let hover_result = hover_result.unwrap();

    let contents = match hover_result.contents {
        HoverContents::Scalar(MarkedString::String(str)) => str,
        _ => panic!("Did not think of that"),
    };
    assert!(contents.contains("ClassB2"));
    assert!(!contents.contains("Some class"));
    assert!(!contents.contains("b2"));
}

#[test(tokio::test)]
async fn backend_hover_on_external_simple_iri_should_show_external_info() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::Folder {
            name: "ontology-a".into(),
            children: vec![WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                    .with_uri("http://ontology-a.org/a1", "a1.omn")
                    .with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.owx"),
            )],
        }]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test wosrkpace".into(),
        }),
        vec![
            (
                "http://ontology-a.org/a2.owx",
                r##"
                <?xml version="1.0"?>
                <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                     xml:base="http://foo.org/ontology"
                     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                     xmlns:xml="http://www.w3.org/XML/1998/namespace"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                     ontologyIRI="http://foo.org/ontology#">

                     <Prefix name="" IRI="http://foo.org/ontology#"/>
                     <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>

                    <Declaration>
                        <Class IRI="ClassA2"/>
                    </Declaration>
    
                    <AnnotationAssertion>
                        <AnnotationProperty abbreviatedIRI="rdfs:label"/>
                        <IRI>ClassA2</IRI>
                        <Literal>Some class in A2</Literal>
                    </AnnotationAssertion>

                </Ontology>
            "##,
            ),
            ("http://www.w3.org/2000/01/rdf-schema#", "dummy"),
        ],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    let ontology = r#"
        Prefix: : <http://foo.org/ontology#>
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://ontology-a.org/a1>
            Import: <http://ontology-a.org/a2>
            Class: ClassA1
                Annotations:
                    rdfs:label "Some class in A1"
                SubClassOf: ClassA2,ClassB2
            AnnotationProperty: rdfs:label
    "#;

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let hover_result = service
        .inner()
        .hover(HoverParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(8, 32), // ClassA2
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    debug!("{diagnostics:#?}");
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    let hover_result = hover_result.unwrap();

    let contents = match hover_result.contents {
        HoverContents::Scalar(MarkedString::String(str)) => str,
        _ => panic!("Did not think of that"),
    };
    // info!("{:#?}", service.inner().workspaces.read());
    info!("contents={contents}");
    assert!(!contents.contains("**ClassA2**"));
    assert!(contents.contains("Some class in A2"));
}

#[test(tokio::test)]
async fn backend_hover_on_external_full_iri_should_show_external_info() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::Folder {
            name: "ontology-a".into(),
            children: vec![WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                    .with_uri("http://ontology-a.org/a1", "a1.omn")
                    .with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.owx"),
            )],
        }]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test wosrkpace".into(),
        }),
        vec![
            (
                "http://ontology-a.org/a2.owx",
                r##"
                <?xml version="1.0"?>
                <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                     xml:base="http://foo.org/a"
                     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                     xmlns:xml="http://www.w3.org/XML/1998/namespace"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                     ontologyIRI="http://ontology-a.org/a2.owx">

                     <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>

                    <Declaration>
                        <Class IRI="#ClassA2"/>
                    </Declaration>
    
                    <AnnotationAssertion>
                        <AnnotationProperty abbreviatedIRI="rdfs:label"/>
                        <IRI>#ClassA2</IRI>
                        <Literal>Some class in A2</Literal>
                    </AnnotationAssertion>

                </Ontology>
            "##,
            ),
            ("http://www.w3.org/2000/01/rdf-schema#", "dummy"),
        ],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    let ontology = r#"
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://ontology-a.org/a1>
            Import: <http://ontology-a.org/a2>
            Class: ClassA1
                Annotations:
                    rdfs:label "Some class in A1"
                SubClassOf: <http://ontology-a.org/a2.owx#ClassA2>,ClassB2

            AnnotationProperty: rdfs:label
    "#;

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let hover_result = service
        .inner()
        .hover(HoverParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(7, 32),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    debug!("{diagnostics:#?}");
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    let hover_result = hover_result.unwrap();

    let contents = match hover_result.contents {
        HoverContents::Scalar(MarkedString::String(str)) => str,
        _ => panic!("Did not think of that"),
    };
    info!("contents={contents}");
    assert!(!contents.contains("**ClassA2**"));
    assert!(contents.contains("Some class in A2"));
    assert!(contents.contains("IRI: http://ontology-a.org/a2.owx#ClassA2"));
}

#[test(tokio::test)]
async fn backend_hover_on_external_rdf_document_at_simple_iri_should_show_external_info() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::Folder {
            name: "ontology-a".into(),
            children: vec![WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                    .with_uri("http://ontology-a.org/a1", "a1.omn")
                    .with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.rdf"),
            )],
        }]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test wosrkpace".into(),
        }),
        vec![
            (
                "http://ontology-a.org/a2.rdf",
                r##"
                <?xml version="1.0"?>
                <rdf:RDF xmlns="http://www.w3.org/2002/07/owl#"
                     xml:base="http://www.w3.org/2002/07/owl"
                     xmlns:owl="http://www.w3.org/2002/07/owl#"
                     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                     xmlns:xml="http://www.w3.org/XML/1998/namespace"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
                    <Ontology rdf:about="http://ontology-a.org/a2.rdf"/>

                    <!-- http://ontology-a.org/a2#ClassA2 -->

                    <Class rdf:about="http://foo.org/ontology#ClassA2">
                        <rdfs:label>Some class in A2</rdfs:label>
                    </Class>
                </rdf:RDF>
            "##,
            ),
            ("http://foo.org/ontology#", "dummy"),
            ("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "dummy"),
            ("http://www.w3.org/2002/07/owl#", "dummy"),
            ("http://www.w3.org/2000/01/rdf-schema#", "dummy"),
        ],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    let ontology = r#"
        Prefix: : <http://foo.org/ontology#>
        Ontology: <http://ontology-a.org/a1>
            Import: <http://ontology-a.org/a2>
            Class: ClassA1
                Annotations:
                    rdfs:label "Some class in A1"
                SubClassOf: ClassA2,ClassB2
        AnnotationProperty: rdfs:label
    "#;

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let hover_result = service
        .inner()
        .hover(HoverParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(7, 32),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    debug!("{diagnostics:#?}");
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    let hover_result = hover_result.unwrap();

    let contents = match hover_result.contents {
        HoverContents::Scalar(MarkedString::String(str)) => str,
        _ => panic!("Did not think of that"),
    };
    // info!("{:#?}", service.inner().workspaces.read());
    info!("contents={contents}");
    assert!(!contents.contains("**ClassA2**"));
    assert!(contents.contains("Some class in A2"));
}

#[test(tokio::test)]
async fn backend_formatting_on_file_should_correctly_format() -> error::Result<()> {
    setup();
    // Arrange

    let source = indoc! {"
    Prefix:    a:    <http://a.b/c/>

    Prefix:    b:    <http://a.b/b/>
    Ontology:   foo    ver
    Class:    A    SubClassOf: Y    Annotations:   rdfs:label    \"Y\"    EquivalentTo:    Y

                 DisjointWith:    Y   DisjointUnionOf:    Y,Z    HasKey:    Y


     
    Datatype:     B
               EquivalentTo:    Y
    DataProperty:    C
    ObjectProperty:    D
    AnnotationProperty:    E
    Individual:    F
    Class: C
    SubClassOf: p some (A and B)
    SubClassOf: inverse p some (A and B)
    SubClassOf: inverse p some A and B
    Class: Y
    Class: Z
    AnnotationProperty: rdfs:label
    Class: p
    "};

    // Reordering frames is disabled on default
    let target = indoc! {"
    Prefix: a: <http://a.b/c/>
    Prefix: b: <http://a.b/b/>

    Ontology: foo ver


    Class: A
        SubClassOf: Y
        Annotations: rdfs:label \"Y\"
        EquivalentTo: Y
        DisjointWith: Y
        DisjointUnionOf: Y, Z
        HasKey: Y

    Datatype: B
        EquivalentTo: Y

    DataProperty: C


    ObjectProperty: D


    AnnotationProperty: E


    Individual: F


    Class: C
        SubClassOf: p some ( A and B )
        SubClassOf: inverse p some ( A and B )
        SubClassOf: inverse p some A and B

    Class: Y

    
    Class: Z

    
    AnnotationProperty: rdfs:label


    Class: p
    
    "};

    let tmp_dir = arrange_workspace_folders(|_| vec![]);

    let service = arrange_backend(
        None,
        vec![("http://a.b/c/", "dummy"), ("http://a.b/b/", "dummy")],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: source.to_string(),
            },
        })
        .await;

    // Act
    let result = service
        .inner()
        .formatting(DocumentFormattingParams {
            text_document: TextDocumentIdentifier { uri: url.clone() },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            options: FormattingOptions {
                tab_size: 4,
                insert_spaces: true,
                ..FormattingOptions::default()
            },
        })
        .await
        .unwrap();

    // Assert
    assert_empty_diagnostics(&service).await;
    let edits = result.unwrap();

    service
        .inner()
        .did_change(DidChangeTextDocumentParams {
            text_document: VersionedTextDocumentIdentifier {
                uri: url.clone(),
                version: 1,
            },
            content_changes: edits
                .iter()
                .sorted_by_key(|e| e.range.start)
                .rev()
                .map(|e| TextDocumentContentChangeEvent {
                    range: Some(e.range),
                    range_length: None,
                    text: e.new_text.clone(),
                })
                .collect(),
        })
        .await;

    let sync = service.inner().read_sync().await;
    let workspace = sync.get_workspace(&url).unwrap();

    let doc = workspace
        .get_internal_document(&url.to_file_path().unwrap())
        .unwrap();
    assert_empty_diagnostics(&service).await;
    assert_eq!(doc.rope().to_string(), target);
    Ok(())
}

#[test(tokio::test)]
async fn backend_inlay_hint_on_external_simple_iri_should_show_iri() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::Folder {
            name: "ontology-a".into(),
            children: vec![WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                    .with_uri("http://ontology-a.org/a1", "a1.omn")
                    .with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.owx"),
            )],
        }]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test wosrkpace".into(),
        }),
        vec![
            (
                "http://ontology-a.org/a2.owx",
                r##"
                <?xml version="1.0"?>
                <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                     xml:base="http://foo.org/a"
                     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                     xmlns:xml="http://www.w3.org/XML/1998/namespace"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                     ontologyIRI="http://foo.org/ontology#">

                     <Prefix name="" IRI="http://foo.org/ontology#"/>
                     <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>

                    <Declaration>
                        <Class IRI="ClassA2"/>
                    </Declaration>
    
                    <AnnotationAssertion>
                        <AnnotationProperty abbreviatedIRI="rdfs:label"/>
                        <IRI>ClassA2</IRI>
                        <Literal>Some class in A2</Literal>
                    </AnnotationAssertion>

                </Ontology>
            "##,
            ),
            ("http://www.w3.org/2000/01/rdf-schema#", ""), // dummy
        ],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    let ontology = r#"
        Prefix: : <http://foo.org/ontology#>
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://ontology-a.org/a1>
            Import: <http://ontology-a.org/a2>
            Class: ClassA1
                Annotations:
                    rdfs:label "Some class in A1"
                SubClassOf: ClassA2,ClassB2
        AnnotationProperty: rdfs:label
    "#;

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let result = service
        .inner()
        .inlay_hint(InlayHintParams {
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            text_document: TextDocumentIdentifier { uri: url.clone() },
            range: lsp_types::Range {
                start: lsp_types::Position::new(0, 0),
                end: lsp_types::Position::new(999, 0),
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    debug!("{diagnostics:#?}");
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    let result = result.unwrap();

    info!("result={result:#?}");
    assert_eq!(result.len(), 2); // no rdfs:label inlay hint anymore

    assert!(result.iter().any(|x| match &x.label {
        InlayHintLabel::String(a) => a.contains("Some class in A1"),
        InlayHintLabel::LabelParts(_) => unreachable!(),
    }));

    assert!(result.iter().any(|x| match &x.label {
        InlayHintLabel::String(a) => {
            a.contains("Some class in A2")
        }
        InlayHintLabel::LabelParts(_) => unreachable!(),
    }));
}

#[test(tokio::test)]
async fn backend_import_resolve_should_load_documents() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![
            WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                    .with_uri("http://external.org/shared.omn", "foobaronto.omn")
                    .with_uri("http://foobar.org/ontology/", "foo.omn"),
            ),
            WorkspaceMember::OmnFile {
                name: "foobaronto.omn".into(),
                content: "".into(),
            },
        ]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![],
    )
    .await;

    let file_url = Url::from_file_path(tmp_dir.path().join("foo.omn")).expect("valid url");
    let ontology = r#"
        Ontology: <http://foobar.org/ontology/>
        Import: <http://external.org/shared.omn>
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: file_url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    assert_empty_diagnostics(&service).await;
    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    assert_eq!(workspaces.len(), 1, "all files should be in one workspace");
    let workspace = workspaces.first().unwrap();
    info!(" Workspace documents {:#?}", workspace.internal_documents());
    let document_count = workspace.internal_documents().count();
    assert_eq!(document_count, 2);
}

#[test(tokio::test)]
async fn backend_did_change_should_remove_old_infos() {
    setup();
    // Arrange
    let service = arrange_backend(None, vec![]).await;

    let dir = TempDir::new("owl-ms-test").unwrap();
    let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();

    let ontology = r#"Ontology: <http://a.b/multi-file>
Class: class-in-first-file
    Annotations: rdfs:label "This class is in the first file"
"#;

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: ontology_url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act

    service
        .inner()
        .did_change(DidChangeTextDocumentParams {
            text_document: VersionedTextDocumentIdentifier {
                uri: ontology_url.clone(),
                version: 1,
            },
            content_changes: vec![TextDocumentContentChangeEvent {
                text: "".into(),
                range: Some(lsp_types::Range {
                    start: lsp_types::Position::new(2, 0),
                    end: lsp_types::Position::new(2, 61),
                }),
                range_length: None,
            }],
        })
        .await;

    // Assert

    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    let workspace = workspaces.iter().exactly_one().unwrap();
    let document = workspace
        .internal_documents()
        .exactly_one()
        .unwrap_or_else(|_| panic!("Multiple documents"));
    assert_eq!(
        document.rope().to_string(),
        r#"Ontology: <http://a.b/multi-file>
Class: class-in-first-file

"#
    );
    // let frame = document.frame_infos.get("class-in-first-file").unwrap();
    // TODO #32 assert_eq!(frame.annotations.get("rdfs:label"), None);
}

#[test(tokio::test)]
async fn backend_workspace_symbols_should_work() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![
            WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog.xml").to_str().unwrap())
                    .with_uri("http://foo.org/a.omn", "a.omn"),
            ),
            WorkspaceMember::OmnFile {
                name: "a.omn".into(),
                content: r#"
                Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
                Ontology: <http://foo.org/a>
                    AnnotationProperty: rdfs:label
                    Class: some-class # This will get found by iri
                        Annotations:
                            rdfs:label "Some class"
                    Class: C_123 # This will get found by label
                        Annotations:
                            rdfs:label "some number class"
                    Class: some-iri-class # This will get found by iri and has no label
                "#
                .into(),
            },
            WorkspaceMember::OmnFile {
                name: "b.omn".into(),
                content: r#"
                Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
                Ontology: <http://foo.org/b>
                    AnnotationProperty: rdfs:label
                    Import: <http://foo.org/a.omn>
                    Class: some-other-class # This will also be found by iri
                        Annotations:
                            rdfs:label "Some other class"
                "#
                .into(),
            },
        ]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("c.omn")).unwrap();

    let ontology = r#"
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://foo.org/c>
            Import: <http://foo.org/a.omn>
            Class: some-other-class-at-c
                Annotations:
                    rdfs:label "Some other class at c"
    "#;
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act

    let result = service
        .inner()
        .symbol(WorkspaceSymbolParams {
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            query: "some".to_string(),
        })
        .await;

    // Assert
    assert_empty_diagnostics(&service).await;
    let symbols = result
        .expect("Symbols should not throw errors")
        .expect("Symbols should contain something");

    info!("{symbols:#?}");
    assert_eq!(symbols.len(), 4);
    assert!(symbols.iter().any(|s| s.name == "Some class"));
    assert!(!symbols.iter().any(|s| s.name == "some-class"));
    assert!(symbols.iter().any(|s| s.name == "Some other class at c"));
    assert!(!symbols.iter().any(|s| s.name == "some-other-class-at-c"));
    assert!(symbols.iter().any(|s| s.name == "some number class"));
    assert!(!symbols.iter().any(|s| s.name == "C_123"));
    assert!(symbols.iter().any(|s| s.name == "some-iri-class"));
}

#[test(tokio::test)]
async fn backend_did_open_should_load_external_documents_via_http() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![
            WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                    .with_uri("http://foo.org/a.omn", "http://foo.org/version/file.owx")
                    .with_uri("http://foo.org/c.omn", "c.omn"),
            ),
            WorkspaceMember::OmnFile {
                name: "a.omn".into(),
                content: r#"
                Ontology: <http://foo.org/a>
                    AnnotationProperty: rdfs:label
                    Class: some-class
                        Annotations:
                            rdfs:label "Some class"
                "#
                .into(),
            },
        ]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![(
            "http://foo.org/version/file.owx",
            r##"
                <?xml version="1.0"?>
                <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                     xml:base="http://foo.org/a"
                     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                     xmlns:xml="http://www.w3.org/XML/1998/namespace"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                     ontologyIRI="http://foo.org/a">

                    <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>

                    <Declaration>
                        <Class IRI="#SomeOtherClass"/>
                    </Declaration>
    
                    <AnnotationAssertion>
                        <AnnotationProperty abbreviatedIRI="rdfs:label"/>
                        <IRI>#SomeOtherClass</IRI>
                        <Literal>Some other class at a</Literal>
                    </AnnotationAssertion>

                </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("c.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://foo.org/c>
            Import: <http://foo.org/a.omn>
            AnnotationProperty: rdfs:label
            Class: some-other-class-at-c
                Annotations:
                    rdfs:label "Some other class at c"
    "#;

    // Act

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert

    assert_empty_diagnostics(&service).await;
    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    let workspace = workspaces
        .iter()
        .exactly_one()
        .expect("Only one workspace should be crated");

    assert_eq!(
        workspace.internal_documents().len(),
        1,
        "One internal document should be loaded. It was given"
    );
    assert_eq!(
        workspace.external_documents().len(),
        1,
        "One external document should be loaded"
    );
}

#[test(tokio::test)]
async fn backend_did_open_should_load_external_rdf_via_http() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|_| vec![]);

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![
            (
                "http://www.w3.org/2000/01/rdf-schema#",
                r##"<?xml version="1.0"?>
                    <rdf:RDF xmlns="http://www.example.com/iri#"
                         xml:base="http://www.example.com/iri"
                         xmlns:o="http://www.example.com/iri#"
                         xmlns:owl="http://www.w3.org/2002/07/owl#"
                         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                         xmlns:xml="http://www.w3.org/XML/1998/namespace"
                         xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                         xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
                        <owl:Ontology rdf:about="http://www.example.com/iri">
                            <owl:versionIRI rdf:resource="http://www.example.com/viri"/>
                        </owl:Ontology>

                        <owl:Class rdf:about="http://www.example.com/iri#C"/>
                    </rdf:RDF>"##,
            ),
            ("http://www.example.com/", "dummy"),
        ],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("has-rdf.omn")).unwrap();

    let ontology = r"
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://foo.org/has-rdf>
            AnnotationProperty: rdfs:label
            Class: some-class
                Annotations:
                    rdfs:seeAlso some-other-class

            Class: some-other-class
            AnnotationProperty: rdfs:label
            AnnotationProperty: rdfs:seeAlso
    ";

    // Act

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert

    assert_empty_diagnostics(&service).await;

    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    let workspace = workspaces
        .iter()
        .exactly_one()
        .expect("Only one workspace should be crated");

    assert_eq!(
        workspace.internal_documents().len(),
        1,
        "One internal document should be loaded. It was given"
    );

    info!("External Documents: {:#?}", workspace.external_documents());
    assert_eq!(
        workspace.external_documents().len(),
        1,
        "One external document should be loaded"
    );
}

#[test(tokio::test)]
async fn backend_hover_should_use_external_rdf_info() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|_| vec![]);

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![
            (
                "http://www.w3.org/2000/01/rdf-schema#",
                r##"
                <rdf:RDF
                    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                    xmlns:owl="http://www.w3.org/2002/07/owl#"
                    xmlns:dc="http://purl.org/dc/elements/1.1/">

                <owl:Ontology
                     rdf:about="http://www.w3.org/2000/01/rdf-schema#"
                     dc:title="The RDF Schema vocabulary (RDFS)"/>

                <rdfs:Class rdf:about="http://www.w3.org/2000/01/rdf-schema#Resource">
                    <rdfs:isDefinedBy rdf:resource="http://www.w3.org/2000/01/rdf-schema#"/>
                    <rdfs:label>Resource</rdfs:label>
                    <rdfs:comment>The class resource, everything.</rdfs:comment>
                </rdfs:Class>

                <rdf:Property rdf:about="http://www.w3.org/2000/01/rdf-schema#seeAlso">
                    <rdfs:isDefinedBy rdf:resource="http://www.w3.org/2000/01/rdf-schema#"/>
                    <rdfs:label>Seeeeee Alsooooooo</rdfs:label>
                    <rdfs:comment>Further information about the subject resource.</rdfs:comment>
                    <rdfs:range rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
                    <rdfs:domain   rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
                </rdf:Property>

                </rdf:RDF>
            "##,
            ),
            ("http://purl.org/dc/elements/1.1/", "dummy"),
            ("http://purl.org/dc/elements/1.1", "dummy"),
            ("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "dummy"),
            ("http://www.w3.org/2002/07/owl#", "dummy"),
        ],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("has-rdf.omn")).unwrap();

    let ontology = indoc! {r#"
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://foo.org/has-rdf>
            Class: some-class
                Annotations:
                    rdfs:seeAlso "bar"

            Class: some-other-class
        AnnotationProperty: rdfs:seeAlso
    "#};

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act

    let result = service
        .inner()
        .hover(HoverParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url },
                position: tower_lsp::lsp_types::Position {
                    line: 2,
                    character: 13,
                },
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await;

    // Assert

    assert_empty_diagnostics(&service).await;
    let result = result.unwrap();
    let result = result.unwrap();
    let contents = result.contents;
    let string = match contents {
        HoverContents::Scalar(MarkedString::String(string)) => string,
        _ => todo!(),
    };
    info!("string={string}");
    assert!(string.contains("Seeeeee Alsooooooo"));
}

#[test(tokio::test)]
async fn backend_did_open_should_load_external_documents_via_file() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![
            WorkspaceMember::CatalogFile(
                Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                    .with_uri("http://foo.org/a", "a.owx")
                    .with_uri("http://foo.org/c", "c.omn"),
            ),
            WorkspaceMember::OwxFile {
                name: "a.owx".into(),
                content: r##"
                    <?xml version="1.0"?>
                    <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                         xml:base="http://foo.org/a"
                         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                         xmlns:xml="http://www.w3.org/XML/1998/namespace"
                         xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                         xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                         ontologyIRI="http://foo.org/a">

                        <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>

                        <Declaration>
                            <Class IRI="#SomeOtherClass"/>
                        </Declaration>
    
                        <AnnotationAssertion>
                            <AnnotationProperty abbreviatedIRI="rdfs:label"/>
                            <IRI>#SomeOtherClass</IRI>
                            <Literal>Some other class at a</Literal>
                        </AnnotationAssertion>

                    </Ontology>
                "##
                .into(),
            },
        ]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("c.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://foo.org/c>
            Import: <http://foo.org/a>
            AnnotationProperty: rdfs:label
            Class: some-other-class-at-c
                Annotations:
                    rdfs:label "Some other class at c"
    "#;

    // Act

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    assert_empty_diagnostics(&service).await;
    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    let workspace = workspaces
        .iter()
        .exactly_one()
        .expect("Only one workspace should be crated");

    assert_eq!(
        workspace.internal_documents().len(),
        1,
        "One internal document should be loaded. It was given"
    );
    assert_eq!(
        workspace.external_documents().len(),
        1,
        "One external document should be loaded"
    );
}

#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_class() {
    setup();
    let ontology = indoc! {r#"
        Ontology: <http://foo.org/a>

            <PARTIAL>

            Class: c
    "#};
    backend_completion_test_helper("Cl", "Class:", ontology).await;
}

#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_class_at_the_end() {
    setup();
    let ontology = indoc! {r#"
        Ontology: <http://foo.org/a> Version

        <PARTIAL>
    "#};
    backend_completion_test_helper("Cl", "Class:", ontology).await;
}

#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_datatype() {
    setup();
    let ontology = indoc! {r#"
        Ontology: <http://foo.org/a>

            <PARTIAL>

            Class: c
    "#};
    backend_completion_test_helper("Datat", "Datatype:", ontology).await;
}

#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_integer() {
    setup();
    let ontology = indoc! {r#"
        Ontology: <http://foo.org/a>

            Datatype: Foo
                EquivalentTo: (<PARTIAL>

            Class: c
    "#};
    backend_completion_test_helper("in", "integer", ontology).await;
}

#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_some() {
    setup();
    let ontology = indoc! {r#"
        Ontology: <http://foo.org/a>

            Ontology:
            	Class: Person
            		SubClassOf: hasAge <PARTIAL> 1 and hasAge only not NegInt

            Class: c
    "#};
    backend_completion_test_helper("so", "some", ontology).await;
}

#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_functionnal() {
    setup();
    let ontology = indoc! {r#"
        Ontology: <http://foo.org/a>

            Ontology:
            	ObjectProperty: Thing
            		Characteristics: <PARTIAL>

            Class: c
    "#};
    backend_completion_test_helper("Fu", "Functional", ontology).await;
}

async fn backend_completion_test_helper(partial: &str, full: &str, ontology: &str) -> Vec<String> {
    // Arrange

    let tmp_dir = arrange_workspace_folders(|_| vec![]);
    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();

    let rope = Rope::from_str(ontology);
    let pos = ontology
        .lines()
        .enumerate()
        .find_map(|(li, line)| {
            line.find("<PARTIAL>")
                .map(|ci| Position::new(li as u32, ci as u32))
        })
        .expect("Should contain <PARTIAL> str");

    let ontology = ontology.replace("<PARTIAL>", partial);

    let pos = pos.moved_right(partial.len() as u32, &rope);

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    let pos = pos.into_lsp(&rope, &PositionEncodingKind::UTF16).unwrap();

    // Act

    let result = service
        .inner()
        .completion(CompletionParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url },
                position: pos,
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
            context: None,
        })
        .await;

    // Assert

    let result = result.expect("Result shoud not produce error");
    let result = result.expect("Result should contain completion response");

    match result {
        CompletionResponse::Array(completion_items) => {
            let completion_items = completion_items
                .into_iter()
                .filter(|i| i.kind == Some(CompletionItemKind::KEYWORD))
                .collect_vec();
            assert_eq!(completion_items.len(), 1);
            assert_eq!(completion_items[0].label, full);
            completion_items.into_iter().map(|i| i.label).collect_vec()
        }
        CompletionResponse::List(_) => unimplemented!(),
    }
}

#[test(tokio::test)]
async fn backend_completion_should_not_panic() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|_| vec![]);
    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();

    let ontology = indoc! {r#"
        Ontology: <http://foo.org/a>

            ööööö
        
            Class: some-other-class-at-c
                Annotations:
                    rdfs:label "Some other class at c"
    "#};

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act

    let _ = service
        .inner()
        .completion(CompletionParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url },
                position: lsp_types::Position::new(2, 6),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
            context: None,
        })
        .await;

    // Assert
    // Does not panic!
}
#[test(tokio::test)]
async fn backend_completion_with_iri_should_complete_to_iri() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|_| vec![]);
    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();

    let ontology = indoc! {r#"
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://foo.org/a>
        Class: F_1234
            Annotations:
                rdfs:label "Some Class"
        Class: Other
            SubClassOf: Som #<--here
    "#};

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act

    let res = service
        .inner()
        .completion(CompletionParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url },
                position: lsp_types::Position::new(6, 19),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
            context: None,
        })
        .await;

    // Assert

    let res = res.unwrap();
    let res = res.unwrap();
    let items = match res {
        CompletionResponse::Array(completion_items) => completion_items,
        CompletionResponse::List(_) => unimplemented!(),
    };

    let item = items.into_iter().exactly_one().unwrap();

    assert_eq!(item.label, "Some Class");
    assert_eq!(item.insert_text, Some("F_1234".to_string()));
}

#[test(tokio::test)]
async fn backend_completion_with_iri_should_be_case_insensitive() {
    setup();
    // Arrange

    let tmp_dir = arrange_workspace_folders(|_| vec![]);
    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "foo".into(),
        }),
        vec![],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();

    // Use lowercase "som" to match "Some Class" (case-insensitive)
    let ontology = indoc! {r#"
        Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
        Ontology: <http://foo.org/a>
        Class: F_1234
            Annotations:
                rdfs:label "Some Class"
        Class: Other
            SubClassOf: som #<--here (lowercase)
    "#};

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act

    let res = service
        .inner()
        .completion(CompletionParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url },
                position: lsp_types::Position::new(6, 19),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
            context: None,
        })
        .await;

    // Assert

    let res = res.unwrap();
    let res = res.unwrap();
    let items = match res {
        CompletionResponse::Array(completion_items) => completion_items,
        CompletionResponse::List(_) => unimplemented!(),
    };

    let item = items.into_iter().exactly_one().unwrap();

    // Should match "Some Class" even though we typed "som" (lowercase)
    assert_eq!(item.label, "Some Class");
    assert_eq!(item.insert_text, Some("F_1234".to_string()));
}

#[test(tokio::test)]
async fn backend_references_in_multi_file_ontology_should_work() {
    setup();
    // Arrange
    let (service, tmp_dir) = arrange_multi_file_ontology().await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let result = service
        .inner()
        .references(ReferenceParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(9, 31),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
            context: ReferenceContext {
                include_declaration: true,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );

    let url2 = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();
    let result = result.unwrap();
    info!("{result:#?}");
    assert_eq!(result.len(), 2);
    assert!(result.iter().any(|l| l.uri == url));
    assert!(result.iter().any(|l| l.uri == url2));
}

#[test(tokio::test)]
async fn backend_references_without_def_should_not_show_def() {
    setup();
    // Arrange
    let (service, tmp_dir) = arrange_multi_file_ontology().await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let result = service
        .inner()
        .references(ReferenceParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(9, 31), // This is on "ClassA2" -> not a definition
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
            context: ReferenceContext {
                include_declaration: false,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );

    let result = result.unwrap();
    info!("{result:#?}");
    assert_eq!(result.len(), 1);
    assert!(result.iter().any(|l| l.uri == url));
}

#[test(tokio::test)]
async fn backend_goto_definition_in_multi_file_ontology_should_work() {
    setup();
    // Arrange
    let (service, tmp_dir) = arrange_multi_file_ontology().await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act
    let result = service
        .inner()
        .goto_definition(GotoDefinitionParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(9, 31),
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    // info!("{:#?}", service.inner().workspaces.read());
    let url2 = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();
    let result = result.unwrap();
    match result {
        GotoDefinitionResponse::Array(locations) => {
            let location = locations.into_iter().exactly_one().unwrap();
            assert_eq!(location.uri, url2);
        }
        _ => todo!(),
    }
}

#[test(tokio::test)]
async fn backend_document_symbols_in_multi_file_ontology_should_just_show_symbols_from_active_file()
{
    setup();
    // Arrange
    let (service, tmp_dir) = arrange_multi_file_ontology().await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();

    {
        let sync = service.inner().read_sync().await;
        debug!("Sync Backend: \n{sync:#?}");
    }

    // Act
    let result = service
        .inner()
        .document_symbol(DocumentSymbolParams {
            text_document: TextDocumentIdentifier { uri: url.clone() },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert!(
        diagnostics.len() == 1,
        "The not defined class B2 should generate a diagnostic"
    );
    match result.unwrap() {
        DocumentSymbolResponse::Flat(symbol_informations) => {
            let info = symbol_informations
                .iter()
                // These two are also defnined but we ignore them for the test
                .filter(|si| si.name != "rdfs:label")
                .filter(|si| si.name != "test")
                .exactly_one()
                .unwrap();
            assert_eq!(info.name, "Some class in A2"); // aka the label
        }
        DocumentSymbolResponse::Nested(_document_symbols) => todo!(),
    }
}

#[test(tokio::test)]
async fn backend_rename_simple_iri_should_work() {
    setup();
    let ontology = indoc! {"
        Prefix: : <https://example.com/ontology#>
        Prefix: o: <https://example.com/ontology#>

        Ontology:
        Class: B
            SubClassOf: o:B, B, <https://example.com/ontology#B>
    "};
    let new_ontology = indoc! {"
        Prefix: : <https://example.com/ontology#>
        Prefix: o: <https://example.com/ontology#>

        Ontology:
        Class: beta
            SubClassOf: beta, beta, beta
    "};

    backend_rename_helper(ontology, new_ontology, Position::new(4, 7), "beta").await;
}

#[test(tokio::test)]
async fn backend_rename_at_end_should_work() {
    setup();
    let ontology = indoc! {"
        Prefix: : <https://example.com/ontology#>

        Ontology:
        Class: B
    "};
    let new_ontology = indoc! {"
        Prefix: : <https://example.com/ontology#>

        Ontology:
        Class: beta
    "};

    backend_rename_helper(ontology, new_ontology, Position::new(3, 8), "beta").await;
}

#[test(tokio::test)]
async fn backend_rename_prefixless_simple_iri_should_work() {
    setup();
    let ontology = indoc! {"
        Ontology:
        Class: B
            SubClassOf: B
    "};
    let new_ontology = indoc! {"
        Ontology:
        Class: beta
            SubClassOf: beta
    "};

    backend_rename_helper(ontology, new_ontology, Position::new(1, 7), "beta").await;
}

#[test(tokio::test)]
async fn backend_rename_unknown_abbriviated_iri_should_work() {
    setup();
    let ontology = indoc! {"
        Ontology:
        Class: unknown:B
            SubClassOf: unknown:B
    "};
    let new_ontology = indoc! {"
        Ontology:
        Class: unknown:beta
            SubClassOf: unknown:beta
    "};

    backend_rename_helper(ontology, new_ontology, Position::new(1, 7), "beta").await;
}

#[test(tokio::test)]
async fn backend_rename_full_iri_should_shorten() {
    setup();
    let ontology = indoc! {"
        Prefix: : <https://example.com/ontology#>
        Prefix: o: <https://example.com/ontology#>

        Ontology:
        Class: <https://example.com/ontology#B>
            SubClassOf: o:B, B, <https://example.com/ontology#B>
    "};
    let new_ontology = indoc! {"
        Prefix: : <https://example.com/ontology#>
        Prefix: o: <https://example.com/ontology#>

        Ontology:
        Class: beta
            SubClassOf: beta, beta, beta
    "};

    backend_rename_helper(
        ontology,
        new_ontology,
        Position::new(4, 7),
        "https://example.com/ontology#beta",
    )
    .await;
}

#[test(tokio::test)]
async fn backend_rename_abbriviated_iri_should_work_for_matching() {
    setup();
    let ontology = indoc! {"
        Prefix: o: <https://example.com/ontology#>

        Ontology:
        Class: o:B
            SubClassOf: o:B, B, <https://example.com/ontology#B>
        Class: B
    "};
    let new_ontology = indoc! {"
        Prefix: o: <https://example.com/ontology#>

        Ontology:
        Class: o:beta
            SubClassOf: o:beta, B, o:beta
        Class: B
    "};

    backend_rename_helper(ontology, new_ontology, Position::new(3, 9), "beta").await;
}

#[test(tokio::test)]
async fn backend_rename_full_iri_should_work_for_matching() {
    setup();
    let ontology = indoc! {"
        Ontology:
        Class: <https://example.com/ontology#B>
            SubClassOf: o:B, B, <https://example.com/ontology#B>
        Class: B
        Class: o:B
    "};
    let new_ontology = indoc! {"
        Ontology:
        Class: <https://example.com/ontology#beta>
            SubClassOf: o:B, B, <https://example.com/ontology#beta>
        Class: B
        Class: o:B
    "};

    backend_rename_helper(
        ontology,
        new_ontology,
        Position::new(1, 9),
        "https://example.com/ontology#beta",
    )
    .await;
}

#[test(tokio::test)]
async fn backend_rename_full_iri_should_not_shorten() {
    setup();
    let ontology = indoc! {"
        Prefix: o: <https://example.com/ontology#>
        Prefix: : <https://example.com/ontology#>

        Ontology:
        Class: <https://example.com/ontology#B>
            SubClassOf: o:B, B, <https://example.com/ontology#B>
        Class: B
    "};
    let new_ontology = indoc! {"
        Prefix: o: <https://example.com/ontology#>
        Prefix: : <https://example.com/ontology#>

        Ontology:
        Class: <https://example.com/things#beta>
            SubClassOf: <https://example.com/things#beta>, <https://example.com/things#beta>, <https://example.com/things#beta>
        Class: <https://example.com/things#beta>
    "};

    backend_rename_helper(
        ontology,
        new_ontology,
        Position::new(4, 9),
        "https://example.com/things#beta",
    )
    .await;
}

async fn backend_rename_helper(
    old_ontology: &str,
    new_ontology: &str,
    position: Position,
    new_name: &str,
) {
    // Arrange
    let service = arrange_backend(None, vec![("https://example.com/ontology#", "dummy")]).await;

    let dir = TempDir::new("owl-ms-test").unwrap();
    let url = Url::from_file_path(dir.path().join("foo.omn")).unwrap();

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: old_ontology.to_string(),
            },
        })
        .await;

    let sync = service.inner().read_sync().await;
    let (doc, _) = sync.get_internal_document(&url).unwrap();

    let pos = position
        .into_lsp(doc.rope(), &PositionEncodingKind::UTF16)
        .unwrap();

    drop(sync);

    // Act
    let result = service
        .inner()
        .rename(RenameParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: pos,
            },
            new_name: new_name.to_string(),
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        })
        .await;

    // Assert
    assert_empty_diagnostics(&service).await;
    let result = result.unwrap();
    let result = result.unwrap();

    let changes = result.changes.unwrap();
    for (url, edits) in changes {
        service
            .inner()
            .did_change(DidChangeTextDocumentParams {
                text_document: VersionedTextDocumentIdentifier {
                    uri: url.clone(),
                    version: 1,
                },
                content_changes: edits
                    .iter()
                    .sorted_by_key(|e| e.range.start)
                    .rev()
                    .map(|e| TextDocumentContentChangeEvent {
                        range: Some(e.range),
                        range_length: None,
                        text: e.new_text.clone(),
                    })
                    .collect(),
            })
            .await;
    }

    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    let workspace = workspaces.iter().exactly_one().unwrap();
    let doc = workspace
        .get_internal_document(&url.to_file_path().unwrap())
        .unwrap();
    let doc_content = doc.rope().to_string();

    assert_eq!(doc_content, new_ontology);
}

#[test(tokio::test)]
async fn backend_did_open_should_load_external_documents_recursivly() {
    // Arrange
    let service = arrange_backend(
        None,
        vec![
            (
                "http://a/depth-1#",
                indoc! {r#"
                    <?xml version="1.0"?>
                    <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                     xml:base="http://a/depth-1#"
                     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                     xmlns:xml="http://www.w3.org/XML/1998/namespace"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                     ontologyIRI="http://a/depth-1#">
                        <Prefix name="" IRI="http://a/depth-1#"/>
                        <Prefix name="owl" IRI="http://www.w3.org/2002/07/owl#"/>
                        <Prefix name="rdf" IRI="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
                        <Prefix name="xml" IRI="http://www.w3.org/XML/1998/namespace"/>
                        <Prefix name="xsd" IRI="http://www.w3.org/2001/XMLSchema#"/>
                        <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
                        <Prefix name="d-2" IRI="http://a/depth-2#"/>

                        <Declaration>
                            <Class IRI="Depth1Class"/>
                        </Declaration>

                        <AnnotationAssertion>
                            <AnnotationProperty abbreviatedIRI="d-2:Depth2Annotation"/>
                            <IRI>Depth1Class</IRI>
                            <Literal xml:lang="en">Tutor</Literal>
                        </AnnotationAssertion>

                    </Ontology>
                "#},
            ),
            (
                "http://a/depth-2#",
                indoc! {r#"
                    <?xml version="1.0"?>
                    <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                     xml:base="http://a/depth-2"
                     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                     xmlns:xml="http://www.w3.org/XML/1998/namespace"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                     ontologyIRI="http://a/depth-2">
                        <Prefix name="" IRI="http://a/depth-2#"/>
                        <Prefix name="owl" IRI="http://www.w3.org/2002/07/owl#"/>
                        <Prefix name="rdf" IRI="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
                        <Prefix name="xml" IRI="http://www.w3.org/XML/1998/namespace"/>
                        <Prefix name="xsd" IRI="http://www.w3.org/2001/XMLSchema#"/>
                        <Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
                    </Ontology>
                "#},
            ),
        ],
    )
    .await;

    let dir = TempDir::new("owl-ms-test").unwrap();
    let url = Url::from_file_path(dir.path().join("foo.omn")).unwrap();

    let ontology = "
        Prefix: d-1: <http://a/depth-1#>
        Ontology: depth-0
    ";

    // Act

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.into(),
            },
        })
        .await;

    // Assert
    assert_empty_diagnostics(&service).await;

    let sync = service.inner().read_sync().await;
    let workspaces = sync.workspaces();
    let workspace = workspaces.iter().exactly_one().unwrap();

    assert_eq!(workspace.external_documents().len(), 2);
}

///////////////////////////////////////////
// Missing IRI Diagnostics Tests
///////////////////////////////////////////

#[test(tokio::test)]
async fn diagnostics_missing_class_with_external_document_should_report_error() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <Class IRI="#DefinedClass"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            Class: LocalClass
                SubClassOf: DefinedClass, UndefinedClass
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label.clone()).collect();
    let diagnostic_text = diagnostic_labels.join("\n");

    assert!(
        !diagnostics.is_empty(),
        "Should have at least one diagnostic for UndefinedClass"
    );
    assert!(
        diagnostic_text.contains("UndefinedClass"),
        "Diagnostic should mention UndefinedClass. Found diagnostics:\n{}",
        diagnostic_text
    );
    assert!(
        diagnostic_text.contains("used but not defined"),
        "Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
        diagnostic_text
    );
}

#[test(tokio::test)]
async fn diagnostics_missing_datatype_with_external_document_should_report_error() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <Datatype IRI="#DefinedDatatype"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            Datatype: LocalDatatype

            DataProperty: hasValue
                Range: DefinedDatatype, UndefinedDatatype
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label.clone()).collect();
    let diagnostic_text = diagnostic_labels.join("\n");

    assert!(
        !diagnostics.is_empty(),
        "Should have at least one diagnostic for UndefinedDatatype"
    );
    assert!(
        diagnostic_text.contains("UndefinedDatatype"),
        "Diagnostic should mention UndefinedDatatype. Found diagnostics:\n{}",
        diagnostic_text
    );
    assert!(
        diagnostic_text.contains("used but not defined"),
        "Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
        diagnostic_text
    );
}

#[test(tokio::test)]
async fn diagnostics_missing_object_property_with_external_document_should_report_error() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <ObjectProperty IRI="#definedProperty"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            Class: MyClass
                SubClassOf: definedProperty some MyClass, undefinedProperty some MyClass
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label.clone()).collect();
    let diagnostic_text = diagnostic_labels.join("\n");

    assert!(
        !diagnostics.is_empty(),
        "Should have at least one diagnostic for undefinedProperty"
    );
    assert!(
        diagnostic_text.contains("undefinedProperty"),
        "Diagnostic should mention undefinedProperty. Found diagnostics:\n{}",
        diagnostic_text
    );
    assert!(
        diagnostic_text.contains("used but not defined"),
        "Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
        diagnostic_text
    );
}

#[test(tokio::test)]
async fn diagnostics_missing_data_property_with_external_document_should_report_error() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <DataProperty IRI="#definedDataProperty"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            DataProperty: localDataProperty

            Class: MyClass
                SubClassOf: definedDataProperty some xsd:string, undefinedDataProperty some xsd:string
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label.clone()).collect();
    let diagnostic_text = diagnostic_labels.join("\n");

    assert!(
        !diagnostics.is_empty(),
        "Should have at least one diagnostic for undefinedDataProperty"
    );
    assert!(
        diagnostic_text.contains("undefinedDataProperty"),
        "Diagnostic should mention undefinedDataProperty. Found diagnostics:\n{}",
        diagnostic_text
    );
    assert!(
        diagnostic_text.contains("used but not defined"),
        "Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
        diagnostic_text
    );
}

#[test(tokio::test)]
async fn diagnostics_missing_annotation_property_with_external_document_should_report_error() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <AnnotationProperty IRI="#definedAnnotation"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            AnnotationProperty: localAnnotation

            Class: MyClass
                Annotations:
                    definedAnnotation "This works",
                    undefinedAnnotation "This should error"
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label.clone()).collect();
    let diagnostic_text = diagnostic_labels.join("\n");

    assert!(
        !diagnostics.is_empty(),
        "Should have at least one diagnostic for undefinedAnnotation"
    );
    assert!(
        diagnostic_text.contains("undefinedAnnotation"),
        "Diagnostic should mention undefinedAnnotation. Found diagnostics:\n{}",
        diagnostic_text
    );
    assert!(
        diagnostic_text.contains("used but not defined"),
        "Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
        diagnostic_text
    );
}

#[test(tokio::test)]
async fn diagnostics_missing_individual_with_external_document_should_report_error() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <NamedIndividual IRI="#definedIndividual"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            Individual: localIndividual

            Individual: definedIndividual
                DifferentFrom: undefinedIndividual
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label.clone()).collect();
    let diagnostic_text = diagnostic_labels.join("\n");

    assert!(
        !diagnostics.is_empty(),
        "Should have at least one diagnostic for undefinedIndividual"
    );
    assert!(
        diagnostic_text.contains("undefinedIndividual"),
        "Diagnostic should mention undefinedIndividual. Found diagnostics:\n{}",
        diagnostic_text
    );
    assert!(
        diagnostic_text.contains("used but not defined"),
        "Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
        diagnostic_text
    );
}

#[test(tokio::test)]
async fn diagnostics_all_defined_in_external_document_should_have_no_errors() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <Class IRI="http://example.org/external/ExternalClass"/>
                </Declaration>
                <Declaration>
                    <ObjectProperty IRI="http://example.org/external/externalObjectProperty"/>
                </Declaration>
                <Declaration>
                    <NamedIndividual IRI="http://example.org/external/externalIndividual"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            Class: <http://example.org/external/ExternalClass>

            Class: LocalClass
                SubClassOf: <http://example.org/external/ExternalClass>

            ObjectProperty: <http://example.org/external/externalObjectProperty>

            Individual: <http://example.org/external/externalIndividual>
                Types: LocalClass
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert_eq!(
        diagnostics.len(),
        0,
        "Should have no diagnostics when all entities are defined in external document. Found: {:#?}",
        diagnostics
    );
}

#[test(tokio::test)]
async fn diagnostics_multiple_missing_iris_with_external_document_should_report_all_errors() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
                "http://example.org/external",
                "http://example.org/external.owx",
            ),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![(
            "http://example.org/external.owx",
            r##"
            <?xml version="1.0"?>
            <Ontology xmlns="http://www.w3.org/2002/07/owl#"
                 xml:base="http://example.org/external"
                 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
                 ontologyIRI="http://example.org/external">

                <Declaration>
                    <Class IRI="#DefinedClass"/>
                </Declaration>

            </Ontology>
            "##,
        )],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();

    let ontology = r#"
        Ontology: <http://example.org/main>
            Import: <http://example.org/external>

            Class: LocalClass
                SubClassOf: DefinedClass, UndefinedClass1, UndefinedClass2, undefinedProperty some UndefinedClass3
    "#;

    // Act
    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl-ms".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Assert
    let diagnostics = service_diagnostics(&service).await;
    assert!(
        diagnostics.len() >= 4,
        "Should have at least 4 diagnostics for undefined entities. Found: {}",
        diagnostics.len()
    );

    let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label.clone()).collect();
    let diagnostic_text = diagnostic_labels.join(", ");

    assert!(
        diagnostic_text.contains("UndefinedClass1"),
        "Should report UndefinedClass1"
    );
    assert!(
        diagnostic_text.contains("UndefinedClass2"),
        "Should report UndefinedClass2"
    );
    assert!(
        diagnostic_text.contains("UndefinedClass3"),
        "Should report UndefinedClass3"
    );
    assert!(
        diagnostic_text.contains("undefinedProperty"),
        "Should report undefinedProperty"
    );
}

#[test(tokio::test)]
async fn backend_goto_definition_on_import_iri_should_navigate_to_imported_file() {
    setup();
    // Arrange
    let (service, tmp_dir) = arrange_multi_file_ontology().await;

    let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();

    // Act - position cursor on the import IRI at line 4 (Import: <http://ontology-a.org/a2.omn>)
    let result = service
        .inner()
        .goto_definition(GotoDefinitionParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(4, 30), // cursor on the IRI
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let expected_url =
        Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();
    let result = result.unwrap();
    match result {
        GotoDefinitionResponse::Scalar(location) => {
            assert_eq!(location.uri, expected_url);
            // Import IRIs navigate to the start of the file
            assert_eq!(location.range.start.line, 0);
            assert_eq!(location.range.start.character, 0);
        }
        _ => panic!("Expected Scalar response for import IRI goto definition"),
    }
}

#[test(tokio::test)]
async fn backend_goto_definition_on_import_iri_not_in_catalog_should_return_none() {
    setup();
    // Arrange
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::CatalogFile(
            Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
                .with_uri("http://example.org/other.omn", "other.omn"),
        )]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        // Provide a dummy response for the unknown URL to avoid panic
        vec![("http://unknown.org/not-in-catalog.omn", "dummy")],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("test.omn")).unwrap();

    // Open a document with an import that is NOT in the catalog
    let ontology = indoc! {r#"
        Prefix: : <http://example.org/ontology#>
        Ontology: <http://example.org/test.omn>
            Import: <http://unknown.org/not-in-catalog.omn>
            Class: TestClass
    "#};

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act - position cursor on the import IRI
    let result = service
        .inner()
        .goto_definition(GotoDefinitionParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(2, 20), // cursor on the unknown IRI
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
        })
        .await
        .unwrap();

    // Assert - should return None since the IRI cannot be resolved
    assert!(result.is_none());
}

#[test(tokio::test)]
async fn backend_goto_definition_on_import_iri_with_nested_catalog_should_work() {
    setup();
    // Arrange - create a nested folder structure with catalog
    let tmp_dir = arrange_workspace_folders(|dir| {
        vec![WorkspaceMember::Folder {
            name: "sub".into(),
            children: vec![
                WorkspaceMember::CatalogFile(
                    Catalog::new(dir.join("sub").join("catalog-v001.xml").to_str().unwrap())
                        .with_uri("http://example.org/imported.omn", "imported.omn"),
                ),
                WorkspaceMember::OmnFile {
                    name: "imported.omn".into(),
                    content: indoc! {r#"
                            Prefix: : <http://example.org/imported#>
                            Ontology: <http://example.org/imported.omn>
                                Class: ImportedClass
                        "#}
                    .into(),
                },
            ],
        }]
    });

    let service = arrange_backend(
        Some(WorkspaceFolder {
            uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
            name: "test workspace".into(),
        }),
        vec![],
    )
    .await;

    let url = Url::from_file_path(tmp_dir.path().join("sub").join("main.omn")).unwrap();

    let ontology = indoc! {r#"
        Prefix: : <http://example.org/main#>
        Ontology: <http://example.org/main.omn>
            Import: <http://example.org/imported.omn>
            Class: MainClass
    "#};

    service
        .inner()
        .did_open(DidOpenTextDocumentParams {
            text_document: TextDocumentItem {
                uri: url.clone(),
                language_id: "owl2md".to_string(),
                version: 0,
                text: ontology.to_string(),
            },
        })
        .await;

    // Act - position cursor on the import IRI
    let result = service
        .inner()
        .goto_definition(GotoDefinitionParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier { uri: url.clone() },
                position: lsp_types::Position::new(2, 20), // cursor on the import IRI
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: PartialResultParams {
                partial_result_token: None,
            },
        })
        .await
        .unwrap();

    // Assert
    let expected_url =
        Url::from_file_path(tmp_dir.path().join("sub").join("imported.omn")).unwrap();
    let result = result.unwrap();
    match result {
        GotoDefinitionResponse::Scalar(location) => {
            assert_eq!(location.uri, expected_url);
        }
        _ => panic!("Expected Scalar response for import IRI goto definition"),
    }
}
