// Copyright (c) 2024 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use xso::{AsXml, FromXml};

use crate::date::DateTime;
use crate::ns;

generate_elem_id!(
    /// A `<mechanism/>` element, describing one mechanism allowed by the server to authenticate.
    Mechanism, "mechanism", FAST
);

/// This is the `<fast/>` element sent by the server as a SASL2 inline feature.
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::FAST, name = "fast")]
pub struct FastQuery {
    /// Whether TLS zero-roundtrip is possible.
    #[xml(attribute(default, name = "tls-0rtt"))]
    pub tls_0rtt: bool,

    /// A list of `<mechanism/>` elements, listing all server allowed mechanisms.
    #[xml(child(n = ..))]
    pub mechanisms: Vec<Mechanism>,
}

/// This is the `<fast/>` element the client MUST include within its SASL2 authentication request.
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::FAST, name = "fast")]
pub struct FastResponse {
    /// Servers MUST reject any authentication requests received via TLS 0-RTT payloads that do not
    /// include a 'count' attribute, or where the count is less than or equal to a count that has
    /// already been processed for this token.  This protects against replay attacks that 0-RTT is
    /// susceptible to.
    #[xml(attribute)]
    pub count: u32,

    /// If true and the client has successfully authenticated, the server MUST invalidate the
    /// token.
    #[xml(attribute(default))]
    pub invalidate: bool,
}

/// This is the `<request-token/>` element sent by the client in the SASL2 authenticate step.
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::FAST, name = "request-token")]
pub struct RequestToken {
    /// This element MUST contain a 'mechanism' attribute, the value of which MUST be one of the
    /// FAST mechanisms advertised by the server.
    #[xml(attribute)]
    pub mechanism: String,
}

/// This is the `<token/>` element sent by the server on successful SASL2 authentication containing
/// a `<request-token/>` element.
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::FAST, name = "token")]
pub struct Token {
    /// The secret token to be used for subsequent authentications, as generated by the server.
    #[xml(attribute)]
    pub token: String,

    /// The timestamp at which the token will expire.
    #[xml(attribute)]
    pub expiry: DateTime,
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::str::FromStr;
    use minidom::Element;

    #[test]
    fn test_simple() {
        let elem: Element = "<fast xmlns='urn:xmpp:fast:0'><mechanism>FOO-BAR</mechanism></fast>"
            .parse()
            .unwrap();
        let request = FastQuery::try_from(elem).unwrap();
        assert_eq!(request.tls_0rtt, false);
        assert_eq!(request.mechanisms, [Mechanism(String::from("FOO-BAR"))]);

        let elem: Element = "<fast xmlns='urn:xmpp:fast:0' count='123'/>"
            .parse()
            .unwrap();
        let response = FastResponse::try_from(elem).unwrap();
        assert_eq!(response.count, 123);
        assert_eq!(response.invalidate, false);

        let elem: Element = "<request-token xmlns='urn:xmpp:fast:0' mechanism='FOO-BAR'/>"
            .parse()
            .unwrap();
        let request_token = RequestToken::try_from(elem).unwrap();
        assert_eq!(request_token.mechanism, "FOO-BAR");

        let elem: Element =
            "<token xmlns='urn:xmpp:fast:0' token='ABCD' expiry='2024-06-30T17:13:57+02:00'/>"
                .parse()
                .unwrap();
        let token = Token::try_from(elem).unwrap();
        assert_eq!(token.token, "ABCD");
        assert_eq!(
            token.expiry,
            DateTime::from_str("2024-06-30T17:13:57+02:00").unwrap()
        );
    }
}
