//! Tests for security-related features

use qualname::{NamespaceUri, NcName, QName};
use std::rc::Rc;
use chadpath::ErrorKind;
use chadpath::item::{Item, Node};
use chadpath::pattern::Pattern;
use chadpath::security::{Feature, Policy};
use chadpath::transform::callable::ActualParameters;
use chadpath::transform::context::{ContextBuilder, StaticContextBuilder};
use chadpath::transform::template::Template;
use chadpath::transform::{Axis, KindTest, NodeMatch, NodeTest, Transform};
use chadpath::value::Value;
use chadpath::xdmerror::Error;
use chadpath::xslt::from_document;

// Max Depth feature not set - will use default.
// Small number of evaluations (1), should pass.
pub fn max_depth_np_1<N: Node, G>(make_empty_doc: G) -> Result<(), Error>
where
    G: Fn() -> N,
{
    let mut stctxt = StaticContextBuilder::new()
        .message(|_| Ok(()))
        .fetcher(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .parser(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .build();

    let mut src_doc = make_empty_doc();
    let top = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    src_doc.push(top).expect("unable to add element");

    let x = Transform::ApplyTemplates(Box::new(Transform::Root), None, vec![]);
    let ctxt = ContextBuilder::new()
        .template(Template::new(
            // pattern "Top"
            Pattern::try_from("child::Top").expect("unable to create Pattern for \"child::Top\""),
            Transform::Literal(Item::<N>::Value(Rc::new(Value::from("at the top")))),
            Some(0.0), // priority
            vec![0],   // import
            Some(1),   // document order
            None,      // mode
            String::from("child::Test"),
        ))
        .template(Template::new(
            // pattern "/",
            Pattern::try_from("/").expect("unable to create Pattern for \"/\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ), // body "apply-templates select=node()",
            None,    // priority
            vec![0], // import
            None,    // document order
            None,    // mode
            String::from("/"),
        ))
        .template(Template::new(
            // pattern child::text()
            Pattern::try_from("child::text()")
                .expect("unable to create Pattern for \"child::text()\""),
            Transform::ContextItem, // body value-of select='.'
            None,                   // priority
            vec![0],                // import
            None,                   // document order
            None,                   // mode
            String::from("child::text()"),
        ))
        .context(vec![Item::Node(src_doc)])
        .build();
    ctxt.dispatch(&mut stctxt, &x).map(|_| ())
}

// Max Depth feature not set - will use default.
// Large number of evaluations (infinite), should terminate with error.
pub fn max_depth_np_2<N: Node, G>(make_empty_doc: G) -> Result<(), Error>
where
    G: Fn() -> N,
{
    let mut stctxt = StaticContextBuilder::new()
        .message(|_| Ok(()))
        .fetcher(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .parser(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .build();

    let mut src_doc = make_empty_doc();
    let mut top1 = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    src_doc.push(top1.clone()).expect("unable to add element");
    let mut top2 = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    top1.push(top2.clone()).expect("unable to add element");
    let top3 = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    top2.push(top3.clone()).expect("unable to add element");

    let x = Transform::ApplyTemplates(Box::new(Transform::Root), None, vec![]);
    let ctxt = ContextBuilder::new()
        .template(Template::new(
            // pattern "Top"
            Pattern::try_from("child::Top").expect("unable to create Pattern for \"child::Top\""),
            Transform::ApplyTemplates(Box::new(Transform::ContextItem), None, vec![]), // infinite loop
            Some(0.0),                                                                 // priority
            vec![0],                                                                   // import
            Some(1), // document order
            None,    // mode
            String::from("child::Test"),
        ))
        .template(Template::new(
            // pattern "/",
            Pattern::try_from("/").expect("unable to create Pattern for \"/\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ), // body "apply-templates select=node()",
            None,    // priority
            vec![0], // import
            None,    // document order
            None,    // mode
            String::from("/"),
        ))
        .template(Template::new(
            // pattern child::text()
            Pattern::try_from("child::text()")
                .expect("unable to create Pattern for \"child::text()\""),
            Transform::ContextItem, // body value-of select='.'
            None,                   // priority
            vec![0],                // import
            None,                   // document order
            None,                   // mode
            String::from("child::text()"),
        ))
        .context(vec![Item::Node(src_doc)])
        .build();
    if ctxt
        .dispatch(&mut stctxt, &x)
        .map(|_| ())
        .is_err_and(|e| e.kind == ErrorKind::LimitExceeded)
    {
        Ok(())
    } else {
        panic!("failed to fail")
    }
}

// Create security policy with Max Depth feature set to None.
// Should allow arbitrary depth of evaluation.
// Large depth of evaluations (255), should run OK.
pub fn max_depth_pol_none<N: Node, G>(make_empty_doc: G) -> Result<(), Error>
where
    G: Fn() -> N,
{
    let mut stctxt = StaticContextBuilder::new()
        .message(|_| Ok(()))
        .fetcher(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .parser(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .build();

    let mut src_doc = make_empty_doc();
    let mut top = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    src_doc.push(top.clone()).expect("unable to add element");
    for _ in 0..252 {
        let nxt = src_doc
            .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
            .expect("unable to create element");
        top.push(nxt.clone()).expect("unable to add element");
        top = nxt;
    }

    let mut policy: Policy<N> = Policy::new(QName::from_local_name(
        NcName::try_from("test_policy").unwrap(),
    ));
    policy.add(
        QName::new_from_parts(
            NcName::try_from("maximum-depth").unwrap(),
            Some(
                NamespaceUri::try_from(
                    "http://gitlab.gnome.org/World/Rust/markup-rs/xrust/transform",
                )
                .unwrap(),
            ),
        ),
        Feature::Permitted(None),
    );

    let x = Transform::ApplyTemplates(Box::new(Transform::Root), None, vec![]);
    let ctxt = ContextBuilder::new()
        .policy(Rc::new(policy))
        .expect("unable to set security policy")
        .template(Template::new(
            // pattern "Top"
            Pattern::try_from("child::Top").expect("unable to create Pattern for \"child::Top\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ),
            Some(0.0), // priority
            vec![0],   // import
            Some(1),   // document order
            None,      // mode
            String::from("child::Test"),
        ))
        .template(Template::new(
            // pattern "/",
            Pattern::try_from("/").expect("unable to create Pattern for \"/\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ), // body "apply-templates select=node()",
            None,    // priority
            vec![0], // import
            None,    // document order
            None,    // mode
            String::from("/"),
        ))
        .template(Template::new(
            // pattern child::text()
            Pattern::try_from("child::text()")
                .expect("unable to create Pattern for \"child::text()\""),
            Transform::ContextItem, // body value-of select='.'
            None,                   // priority
            vec![0],                // import
            None,                   // document order
            None,                   // mode
            String::from("child::text()"),
        ))
        .context(vec![Item::Node(src_doc)])
        .build();
    ctxt.dispatch(&mut stctxt, &x).map(|_| ())
}

// Create security policy with Max Depth feature set to a value.
// Evaluate to a depth less than this value, should run OK.
pub fn max_depth_pol_set_1<N: Node, G>(make_empty_doc: G) -> Result<(), Error>
where
    G: Fn() -> N,
{
    let mut stctxt = StaticContextBuilder::new()
        .message(|_| Ok(()))
        .fetcher(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .parser(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .build();

    let mut src_doc = make_empty_doc();
    let mut top = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    src_doc.push(top.clone()).expect("unable to add element");
    // make iteration less than feature value
    for _ in 0..50 {
        let nxt = src_doc
            .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
            .expect("unable to create element");
        top.push(nxt.clone()).expect("unable to add element");
        top = nxt;
    }

    let mut policy: Policy<N> = Policy::new(QName::from_local_name(
        NcName::try_from("test_policy").unwrap(),
    ));
    policy.add(
        QName::new_from_parts(
            NcName::try_from("maximum-depth").unwrap(),
            Some(
                NamespaceUri::try_from(
                    "http://gitlab.gnome.org/World/Rust/markup-rs/xrust/transform",
                )
                .unwrap(),
            ),
        ),
        Feature::Permitted(Some(Transform::Literal(Item::Value(Rc::new(Value::from(
            100,
        )))))),
    );

    let x = Transform::ApplyTemplates(Box::new(Transform::Root), None, vec![]);
    let ctxt = ContextBuilder::new()
        .policy(Rc::new(policy))
        .expect("unable to set security policy")
        .template(Template::new(
            // pattern "Top"
            Pattern::try_from("child::Top").expect("unable to create Pattern for \"child::Top\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ),
            Some(0.0), // priority
            vec![0],   // import
            Some(1),   // document order
            None,      // mode
            String::from("child::Test"),
        ))
        .template(Template::new(
            // pattern "/",
            Pattern::try_from("/").expect("unable to create Pattern for \"/\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ), // body "apply-templates select=node()",
            None,    // priority
            vec![0], // import
            None,    // document order
            None,    // mode
            String::from("/"),
        ))
        .template(Template::new(
            // pattern child::text()
            Pattern::try_from("child::text()")
                .expect("unable to create Pattern for \"child::text()\""),
            Transform::ContextItem, // body value-of select='.'
            None,                   // priority
            vec![0],                // import
            None,                   // document order
            None,                   // mode
            String::from("child::text()"),
        ))
        .context(vec![Item::Node(src_doc)])
        .build();
    ctxt.dispatch(&mut stctxt, &x).map(|_| ())
}

// Create security policy with Max Depth feature set to a value.
// Evaluate to a depth greater than this value, should error.
pub fn max_depth_pol_set_2<N: Node, G>(make_empty_doc: G) -> Result<(), Error>
where
    G: Fn() -> N,
{
    let mut stctxt = StaticContextBuilder::new()
        .message(|_| Ok(()))
        .fetcher(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .parser(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .build();

    let mut src_doc = make_empty_doc();
    let mut top = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    src_doc.push(top.clone()).expect("unable to add element");
    // make iteration less than feature value
    for _ in 0..1150 {
        let nxt = src_doc
            .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
            .expect("unable to create element");
        top.push(nxt.clone()).expect("unable to add element");
        top = nxt;
    }

    let mut policy: Policy<N> = Policy::new(QName::from_local_name(
        NcName::try_from("test_policy").unwrap(),
    ));
    policy.add(
        QName::new_from_parts(
            NcName::try_from("maximum-depth").unwrap(),
            Some(
                NamespaceUri::try_from(
                    "http://gitlab.gnome.org/World/Rust/markup-rs/xrust/transform",
                )
                .unwrap(),
            ),
        ),
        Feature::Permitted(Some(Transform::Literal(Item::Value(Rc::new(Value::from(
            1000,
        )))))),
    );

    let x = Transform::ApplyTemplates(Box::new(Transform::Root), None, vec![]);
    let ctxt = ContextBuilder::new()
        .policy(Rc::new(policy))
        .expect("unable to set security policy")
        .template(Template::new(
            // pattern "Top"
            Pattern::try_from("child::Top").expect("unable to create Pattern for \"child::Top\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ),
            Some(0.0), // priority
            vec![0],   // import
            Some(1),   // document order
            None,      // mode
            String::from("child::Test"),
        ))
        .template(Template::new(
            // pattern "/",
            Pattern::try_from("/").expect("unable to create Pattern for \"/\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ), // body "apply-templates select=node()",
            None,    // priority
            vec![0], // import
            None,    // document order
            None,    // mode
            String::from("/"),
        ))
        .template(Template::new(
            // pattern child::text()
            Pattern::try_from("child::text()")
                .expect("unable to create Pattern for \"child::text()\""),
            Transform::ContextItem, // body value-of select='.'
            None,                   // priority
            vec![0],                // import
            None,                   // document order
            None,                   // mode
            String::from("child::text()"),
        ))
        .context(vec![Item::Node(src_doc)])
        .build();
    if ctxt
        .dispatch(&mut stctxt, &x)
        .map(|_| ())
        .is_err_and(|e| e.kind == ErrorKind::LimitExceeded)
    {
        Ok(())
    } else {
        panic!("failed to fail")
    }
}

// Call a named template in an inifinite recursion, should error.
pub fn max_depth_callable_1<N: Node, G, H>(make_empty_doc: G, make_from_str: H) -> Result<(), Error>
where
    G: Fn() -> N,
    H: Fn(&str) -> Result<N, Error>,
{
    let styledoc = make_from_str(
        "<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template name='recurse'>
  <xsl:param name='count' select='0'/>
  <xsl:message>recurse called <xsl:sequence select='$count'/> times</xsl:message>
  <xsl:choose>
    <xsl:when test='$count lt 250'>
      <xsl:call-template name='recurse'>
        <xsl:with-param name='count' select='$count + 1'/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:message>reached 250 recursions</xsl:message>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
<xsl:template match='/'>
  <xsl:call-template name='recurse'>
    <xsl:with-param name='count' select='1'/>
  </xsl:call-template>
</xsl:template>
</xsl:stylesheet>",
    )
    .map_err(|e| Error::new(e.kind, format!("error parsing stylesheet: {}", e.message)))?;
    let mut stctxt = StaticContextBuilder::new()
        .message(|m| {
            eprintln!("{}", m);
            Ok(())
        })
        .fetcher(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .parser(|_| {
            Err(Error::new(
                chadpath::ErrorKind::NotImplemented,
                "not implemented",
            ))
        })
        .build();

    let mut src_doc = make_empty_doc();
    let top = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    src_doc.push(top.clone()).expect("unable to add element");

    let mut ctxt = from_document(styledoc, None, |s| make_from_str(s), |_| Ok(String::new()))?;
    ctxt.context(vec![Item::Node(src_doc.clone())], 0);

    if ctxt
        .evaluate(&mut stctxt)
        .map(|_| ())
        .is_err_and(|e| e.kind == ErrorKind::LimitExceeded)
    {
        Ok(())
    } else {
        panic!("failed to fail")
    }
}

/// Test security_feature method
pub fn sec_feature<N: Node, G>(make_empty_doc: G) -> Result<(), Error>
where
    G: Fn() -> N,
{
    let mut stctxt = StaticContextBuilder::new()
        .message(|_| Ok(()))
        .fetcher(|_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
        .parser(|_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
        .build();
    let mut src_doc = make_empty_doc();
    let top = src_doc
        .new_element(QName::from_local_name(NcName::try_from("Top").unwrap()))
        .expect("unable to create element");
    src_doc.push(top.clone()).expect("unable to add element");

    let mut policy: Policy<N> = Policy::new(QName::from_local_name(
        NcName::try_from("test_policy").unwrap(),
    ));
    policy.add(
        QName::new_from_parts(
            NcName::try_from("maximum-depth").unwrap(),
            Some(
                NamespaceUri::try_from(
                    "http://gitlab.gnome.org/World/Rust/markup-rs/xrust/transform",
                )
                .unwrap(),
            ),
        ),
        Feature::Permitted(Some(Transform::Literal(Item::Value(Rc::new(Value::from(
            1000,
        )))))),
    );

    let x: Transform<N> = Transform::ApplyTemplates(Box::new(Transform::Root), None, vec![]);
    let ctxt = ContextBuilder::new()
        .policy(Rc::new(policy))
        .expect("unable to set security policy")
        .template(Template::new(
            // pattern "Top"
            Pattern::try_from("child::Top").expect("unable to create Pattern for \"child::Top\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ),
            Some(0.0), // priority
            vec![0],   // import
            Some(1),   // document order
            None,      // mode
            String::from("child::Test"),
        ))
        .template(Template::new(
            // pattern "/",
            Pattern::try_from("/").expect("unable to create Pattern for \"/\""),
            Transform::ApplyTemplates(
                Box::new(Transform::Step(NodeMatch {
                    axis: Axis::Child,
                    nodetest: NodeTest::Kind(KindTest::Any),
                })),
                None,
                vec![],
            ), // body "apply-templates select=node()",
            None,    // priority
            vec![0], // import
            None,    // document order
            None,    // mode
            String::from("/"),
        ))
        .template(Template::new(
            // pattern child::text()
            Pattern::try_from("child::text()")
                .expect("unable to create Pattern for \"child::text()\""),
            Transform::ContextItem, // body value-of select='.'
            None,                   // priority
            vec![0],                // import
            None,                   // document order
            None,                   // mode
            String::from("child::text()"),
        ))
        .context(vec![Item::Node(src_doc)])
        .build();
    let _ = ctxt.security_feature(
        &QName::new_from_parts(
            NcName::try_from("maximum-depth").unwrap(),
            Some(
                NamespaceUri::try_from(
                    "http://gitlab.gnome.org/World/Rust/markup-rs/xrust/transform",
                )
                .unwrap(),
            ),
        ),
        ActualParameters::Named(vec![]),
    )?;

    ctxt.dispatch(&mut stctxt, &x).map(|_| ())
}
