use std::{fmt::Write as _, path::Path};

use anyxml::{
    sax::parser::{ParserOption, XMLReaderBuilder},
    tree::{TreeBuildHandler, convert::NodeKind},
    uri::URIString,
    xpath::{self, XPathObject},
};

#[test]
fn xpath_evaluation_tests() {
    let mut reader = XMLReaderBuilder::new()
        .set_handler(TreeBuildHandler::default())
        .enable_option(ParserOption::ExternalGeneralEntities)
        .build();
    reader
        .parse_uri(
            URIString::parse_file_path(
                Path::new("resources/xpath/testsuite.xml")
                    .canonicalize()
                    .unwrap(),
            )
            .unwrap(),
            None,
        )
        .unwrap();
    let document = reader.handler.document.clone();

    let mut expression = xpath::compile("//test-cases").unwrap();
    let XPathObject::NodeSet(test_cases) = expression.evaluate(document).unwrap() else {
        unreachable!()
    };

    for test_cases in test_cases.iter().map(|case| case.as_element().unwrap()) {
        let input_file = test_cases
            .get_elements_by_qname("input-file")
            .next()
            .unwrap()
            .text_content();
        reader
            .parse_uri(
                URIString::parse_file_path(Path::new(input_file.as_str()).canonicalize().unwrap())
                    .unwrap(),
                None,
            )
            .unwrap();
        let document = reader.handler.document.clone();
        for test_case in test_cases.get_elements_by_qname("test-case") {
            let xpath = test_case
                .get_elements_by_qname("xpath")
                .next()
                .unwrap()
                .text_content();
            let output = test_case
                .get_elements_by_qname("output")
                .next()
                .unwrap()
                .text_content();

            let mut buf = String::new();
            eprintln!("input_file: {input_file}, xpath: {xpath}");
            let mut expression = xpath::compile(&xpath).unwrap();
            match expression.evaluate(document.clone()).unwrap() {
                XPathObject::Boolean(boolean) => write!(buf, "{boolean},").unwrap(),
                XPathObject::Number(number) => write!(buf, "{number},").unwrap(),
                XPathObject::String(string) => write!(buf, "{string},").unwrap(),
                XPathObject::NodeSet(nodeset) => {
                    for node in &nodeset {
                        match node.downcast() {
                            NodeKind::Document(document) => {
                                let object = XPathObject::from(document);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            NodeKind::Element(element) => {
                                let object = XPathObject::from(element);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            NodeKind::Attribute(attribute) => {
                                let object = XPathObject::from(attribute);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            NodeKind::Namespace(namespace) => {
                                let object = XPathObject::from(namespace);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            NodeKind::ProcessingInstruction(pi) => {
                                let object = XPathObject::from(pi);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            NodeKind::Comment(comment) => {
                                let object = XPathObject::from(comment);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            NodeKind::Text(text) => {
                                let object = XPathObject::from(text);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            NodeKind::CDATASection(cdata) => {
                                let object = XPathObject::from(cdata);
                                write!(buf, "{},", object.as_string().unwrap()).unwrap()
                            }
                            _ => unreachable!(),
                        }
                    }
                }
            }

            assert_eq!(
                buf, output,
                "\n--- input-file: {input_file} xpath: {xpath} ---\nbuf: {buf}\noutput:{output}"
            );
        }
    }
}
