//! [RFC 3161] definitions.
//!
//! [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161

use asn1::{SimpleAsn1Readable, SimpleAsn1Writable};

/// RFC 3161 2.4.1
///
/// ```asn1
/// MessageImprint ::= SEQUENCE  {
///   hashAlgorithm AlgorithmIdentifier,
///   hashedMessage OCTET STRING  }
/// ```
#[derive(asn1::Asn1Read, asn1::Asn1Write, Clone)]
pub struct MessageImprint<'a> {
    pub hash_algorithm: cryptography_x509::common::AlgorithmIdentifier<'a>,
    pub hashed_message: &'a [u8],
}

/// RFC 3161 2.4.1
///
/// ```asn1
/// TimeStampReq ::= SEQUENCE  {
///   version                  INTEGER  { v1(1) },
///   messageImprint           MessageImprint,
///     --a hash algorithm OID and the hash value of the data to be
///     --time-stamped
///   reqPolicy                TSAPolicyId                OPTIONAL,
///   nonce                    INTEGER                    OPTIONAL,
///   certReq                  BOOLEAN                    DEFAULT FALSE,
///   extensions               [0] IMPLICIT Extensions    OPTIONAL  }
/// ```
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub struct RawTimeStampReq<'a> {
    pub version: u8,

    pub message_imprint: MessageImprint<'a>,

    pub req_policy: Option<asn1::ObjectIdentifier>,

    pub nonce: Option<asn1::BigUint<'a>>,

    #[default(false)]
    pub cert_req: bool,

    pub extensions: Option<cryptography_x509::extensions::RawExtensions<'a>>,
}

//    PKIStatus ::= INTEGER {
//       granted                (0),
//       -- when the PKIStatus contains the value zero a TimeStampToken, as
//          requested, is present.
//       grantedWithMods        (1),
//        -- when the PKIStatus contains the value one a TimeStampToken,
//          with modifications, is present.
//       rejection              (2),
//       waiting                (3),
//       revocationWarning      (4),
//        -- this message contains a warning that a revocation is
//        -- imminent
//       revocationNotification (5)
//        -- notification that a revocation has occurred  }
// TODO(dm) = Implement me

/// RFC 3161 2.4.2
///
/// ```asn1
/// PKIStatusInfo ::= SEQUENCE {
///   status        PKIStatus,
///   statusString  PKIFreeText     OPTIONAL,
///   failInfo      PKIFailureInfo  OPTIONAL  }
/// ```
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub struct PKIStatusInfo<'a> {
    pub status: u8,
    pub status_string: Option<asn1::SequenceOf<'a, asn1::Utf8String<'a>>>,
    pub fail_info: Option<asn1::BitString<'a>>,
}

/// Inner type for [`Accuracy`]. This represents the basic structure,
/// but does not enforce the value range invariants on the `millis`
/// or `micros` fields.
#[derive(asn1::Asn1Read, asn1::Asn1Write, Copy, Clone)]
struct AccuracyInner<'a> {
    pub seconds: Option<asn1::BigUint<'a>>,
    #[implicit(0)]
    pub millis: Option<u16>,
    #[implicit(1)]
    pub micros: Option<u16>,
}

/// RFC 3161 2.4.2
///
/// ```asn1
/// Accuracy ::= SEQUENCE {
///   seconds        INTEGER              OPTIONAL,
///   millis     [0] INTEGER  (1..999)    OPTIONAL,
///   micros     [1] INTEGER  (1..999)    OPTIONAL  }
/// ```
#[derive(Copy, Clone)]
pub struct Accuracy<'a>(AccuracyInner<'a>);

impl<'a> Accuracy<'a> {
    pub fn seconds(&self) -> Option<asn1::BigUint<'a>> {
        self.0.seconds
    }

    pub fn millis(&self) -> Option<u16> {
        self.0.millis
    }

    pub fn micros(&self) -> Option<u16> {
        self.0.micros
    }
}

impl<'a> SimpleAsn1Readable<'a> for Accuracy<'a> {
    const TAG: asn1::Tag = <AccuracyInner as SimpleAsn1Readable<'a>>::TAG;

    fn parse_data(data: &'a [u8]) -> asn1::ParseResult<Self> {
        let inner = AccuracyInner::parse_data(data)?;

        let valid_range = 1..=999;
        if !inner
            .millis
            .map_or(true, |millis| valid_range.contains(&millis))
        {
            return Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue));
        }

        if !inner
            .micros
            .map_or(true, |micros| valid_range.contains(&micros))
        {
            return Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue));
        }

        Ok(Self(inner))
    }
}

impl<'a> SimpleAsn1Writable for Accuracy<'a> {
    type Error = asn1::WriteError;
    const TAG: asn1::Tag = <AccuracyInner as SimpleAsn1Writable>::TAG;

    fn write_data(&self, dest: &mut asn1::WriteBuf) -> asn1::WriteResult {
        self.0.write_data(dest)
    }

    fn data_length(&self) -> Option<usize> {
        self.0.data_length()
    }
}

/// RFC 3161 2.4.2
///
/// ```asn1
/// TimeStampToken ::= ContentInfo
///   -- contentType is id-signedData ([CMS])
///   -- content is SignedData ([CMS])
/// ```
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub struct TimeStampToken<'a> {
    pub _content_type: asn1::DefinedByMarker<asn1::ObjectIdentifier>,

    #[defined_by(_content_type)]
    pub content: Content<'a>,
}

pub const PKCS7_SIGNED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 2);

/// RFC 3161 2.4.2
///
/// See RFC 5652 for the definition of `SignedData`.
#[derive(asn1::Asn1DefinedByWrite, asn1::Asn1DefinedByRead)]
pub enum Content<'a> {
    #[defined_by(PKCS7_SIGNED_DATA_OID)]
    SignedData(asn1::Explicit<Box<crate::cms::SignedData<'a>>, 0>),
}

// https://www.ietf.org/rfc/rfc3161.txt - Section 2.4.2
pub const TST_INFO_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 16, 1, 4);

impl<'a> crate::cms::ContentInfo<'a> {
    pub fn tst_info(&self) -> Result<TSTInfo<'a>, asn1::ParseError> {
        // Check if the content_type matches TST_INFO_OID
        if self.content_type != TST_INFO_OID {
            return Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue));
        }

        // Unwrap the content, return an error if it's None
        let content_bytes = self
            .content
            .ok_or_else(|| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?;

        // Parse the content bytes into a TSTInfo instance
        let tst_info = asn1::parse_single::<TSTInfo>(content_bytes)?;

        // Return the parsed TSTInfo
        Ok(tst_info)
    }
}

/// RFC 3161 2.4.2
///
/// ```asn1
/// TSTInfo ::= SEQUENCE  {
///   version                      INTEGER  { v1(1) },
///   policy                       TSAPolicyId,
///   messageImprint               MessageImprint,
///     -- MUST have the same value as the similar field in
///     -- TimeStampReq
///   serialNumber                 INTEGER,
///    -- Time-Stamping users MUST be ready to accommodate integers
///    -- up to 160 bits.
///   genTime                      GeneralizedTime,
///   accuracy                     Accuracy                 OPTIONAL,
///   ordering                     BOOLEAN             DEFAULT FALSE,
///   nonce                        INTEGER                  OPTIONAL,
///     -- MUST be present if the similar field was present
///     -- in TimeStampReq.  In that case it MUST have the same value.
///   tsa                          [0] GeneralName          OPTIONAL,
///   extensions                   [1] IMPLICIT Extensions  OPTIONAL   }
/// ```
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub struct TSTInfo<'a> {
    pub version: u8,
    pub policy: Option<asn1::ObjectIdentifier>,
    pub message_imprint: MessageImprint<'a>,
    pub serial_number: asn1::BigInt<'a>,
    pub gen_time: asn1::GeneralizedTime,
    pub accuracy: Option<Accuracy<'a>>,
    #[default(false)]
    pub ordering: bool,
    pub nonce: Option<asn1::BigUint<'a>>,
    #[explicit(0)]
    pub tsa: Option<cryptography_x509::name::GeneralName<'a>>,
    #[implicit(1)]
    pub extensions: Option<cryptography_x509::extensions::RawExtensions<'a>>,
}

/// RFC 3161 2.4.2
///
/// ```asn1
/// TimeStampResp ::= SEQUENCE  {
///    status                  PKIStatusInfo,
///    timeStampToken          TimeStampToken     OPTIONAL  }
/// ```
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub struct RawTimeStampResp<'a> {
    pub status: PKIStatusInfo<'a>,
    pub time_stamp_token: Option<TimeStampToken<'a>>,
}

#[cfg(test)]
mod tests {
    use crate::cms;

    use super::*;

    #[test]
    fn test_parse_timestamp_request() {
        // See TESTING.md
        // openssl ts -query -data README.md -sha512 -cert -out file.tsq
        let enc_request = hex::decode("30640201013051300d060960864801650304020305000440c05812f7df5c643047235d400de272f03bd3d319f71f0c75c4b57948ccf2cdf85f8feb422dedcfa9ec26c102b08f778332ad9be18f759aebe93d64dcc65f49c3020900fce125b1e42c03110101ff")
                          .unwrap();
        let request = asn1::parse_single::<RawTimeStampReq>(&enc_request).unwrap();

        assert_eq!(request.version, 1);

        assert_eq!(
            request.message_imprint.hash_algorithm,
            cryptography_x509::common::AlgorithmIdentifier {
                oid: asn1::DefinedByMarker::marker(),
                params: cryptography_x509::common::AlgorithmParameters::Sha512(Some(()))
            }
        );

        assert_eq!(
            request.message_imprint.hashed_message,
            &hex::decode("C05812F7DF5C643047235D400DE272F03BD3D319F71F0C75C4B57948CCF2CDF85F8FEB422DEDCFA9EC26C102B08F778332AD9BE18F759AEBE93D64DCC65F49C3").unwrap()
        );

        match request.req_policy {
            Some(request_policy) => {
                println!("request_policy  {}", request_policy.to_string());
            }
            None => {}
        }

        match request.nonce {
            Some(nonce_value) => {
                assert_eq!(
                    nonce_value.as_bytes(),
                    hex::decode("00fce125b1e42c0311").unwrap()
                )
            }
            None => {
                assert!(false, "Missing nonce.");
            }
        }

        assert_eq!(request.cert_req, true);
    }

    #[test]
    fn test_timestamp_fields() {
        let enc_response = hex::decode("3003020100").unwrap();
        let pki_status_info = asn1::parse_single::<PKIStatusInfo>(&enc_response).unwrap();
        assert_eq!(pki_status_info.status, 0);

        let enc_tst_info = hex::decode("3082019502010106042a0304013051300d060960864801650304020305000440c05812f7df5c643047235d400de272f03bd3d319f71f0c75c4b57948ccf2cdf85f8feb422dedcfa9ec26c102b08f778332ad9be18f759aebe93d64dcc65f49c30204035daf44180f32303234303932343132353733355a0101ff02080b7df056edda4995a0820111a482010d308201093111300f060355040a13084672656520545341310c300a060355040b130354534131763074060355040d136d54686973206365727469666963617465206469676974616c6c79207369676e7320646f63756d656e747320616e642074696d65207374616d70207265717565737473206d616465207573696e672074686520667265657473612e6f7267206f6e6c696e65207365727669636573311830160603550403130f7777772e667265657473612e6f72673122302006092a864886f70d0109011613627573696c657a617340676d61696c2e636f6d3112301006035504071309577565727a62757267310b3009060355040613024445310f300d0603550408130642617965726e").unwrap(); //let enc_tst_info = hex::decode("3082019502010106042a0304013051300d060960864801650304020305000440c05812f7df5c643047235d400de272f03bd3d319f71f0c75c4b57948ccf2cdf85f8feb422dedcfa9ec26c102b08f778332ad9be18f759aebe93d64dcc65f49c30204035daf44180f32303234303932343132353733355a0101ff02080b7df056edda4995a0820111a482010d308201093111300f060355040a13084672656520545341310c300a060355040b130354534131763074060355040d136d54686973206365727469666963617465206469676974616c6c79207369676e7320646f63756d656e747320616e642074696d65207374616d70207265717565737473206d616465207573696e672074686520667265657473612e6f7267206f6e6c696e65207365727669636573311830160603550403130f7777772e667265657473612e6f72673122302006092a864886f70d0109011613627573696c657a617340676d61696c2e636f6d3112301006035504071309577565727a62757267310b3009060355040613024445310f300d0603550408130642617965726e").unwrap();
        let tst_info = asn1::parse_single::<TSTInfo>(&enc_tst_info).unwrap();
        assert_eq!(tst_info.version, 1);

        let enc_general_name = hex::decode("a482010d308201093111300f060355040a13084672656520545341310c300a060355040b130354534131763074060355040d136d54686973206365727469666963617465206469676974616c6c79207369676e7320646f63756d656e747320616e642074696d65207374616d70207265717565737473206d616465207573696e672074686520667265657473612e6f7267206f6e6c696e65207365727669636573311830160603550403130f7777772e667265657473612e6f72673122302006092a864886f70d0109011613627573696c657a617340676d61696c2e636f6d3112301006035504071309577565727a62757267310b3009060355040613024445310f300d0603550408130642617965726e").unwrap();
        asn1::parse_single::<cryptography_x509::name::GeneralName>(&enc_general_name).unwrap();

        let enc_content_info = hex::decode("308201ae060b2a864886f70d0109100104a082019d048201993082019502010106042a0304013051300d060960864801650304020305000440c05812f7df5c643047235d400de272f03bd3d319f71f0c75c4b57948ccf2cdf85f8feb422dedcfa9ec26c102b08f778332ad9be18f759aebe93d64dcc65f49c30204035daf44180f32303234303932343132353733355a0101ff02080b7df056edda4995a0820111a482010d308201093111300f060355040a13084672656520545341310c300a060355040b130354534131763074060355040d136d54686973206365727469666963617465206469676974616c6c79207369676e7320646f63756d656e747320616e642074696d65207374616d70207265717565737473206d616465207573696e672074686520667265657473612e6f7267206f6e6c696e65207365727669636573311830160603550403130f7777772e667265657473612e6f72673122302006092a864886f70d0109011613627573696c657a617340676d61696c2e636f6d3112301006035504071309577565727a62757267310b3009060355040613024445310f300d0603550408130642617965726e").unwrap();
        asn1::parse_single::<cms::ContentInfo>(&enc_content_info).unwrap();
    }

    #[test]
    fn test_parse_timestamp_response() {
        let enc_response = hex::decode("[encoded data, 11008 characters]").unwrap();
        let response = asn1::parse_single::<RawTimeStampResp>(&enc_response).unwrap();

        assert_eq!(response.status.status, 0);

        let tst_info_content = response.time_stamp_token.unwrap().content;
        match tst_info_content {
            Content::SignedData(signed_data) => {
                let explicit_signed_data = signed_data.as_inner();
                assert_eq!(explicit_signed_data.version, 3);

                let tst_info = explicit_signed_data.content_info.tst_info().unwrap();

                assert_eq!(tst_info.version, 1);
                assert_eq!(
                    tst_info.nonce.expect("Nonce must be set").as_bytes(),
                    hex::decode("0B7DF056EDDA4995").unwrap()
                );
            }
        }
    }

    #[test]
    fn test_parse_accuracy() {
        let accuracy = AccuracyInner {
            seconds: None,
            // NOTE: `AccuracyInner` does not enforce range invariant.
            millis: Some(9999),
            micros: None,
        };

        let bytes = asn1::write_single(&accuracy).unwrap();
        assert_eq!(bytes, b"0\x04\x80\x02'\x0f");

        let enc = hex::decode("3004800201F4").unwrap();
        let response = asn1::parse_single::<Accuracy>(&enc).unwrap();
        assert_eq!(response.millis().unwrap(), 500);
    }

    #[test]
    fn test_parse_accuracy_bad_ranges() {
        for (millis, micros) in &[
            (Some(9999), None),
            (None, Some(9999)),
            (Some(0), Some(0)),
            (Some(1000), Some(0)),
        ] {
            let accuracy = AccuracyInner {
                seconds: None,
                millis: *millis,
                micros: *micros,
            };

            let enc = asn1::write_single(&accuracy).unwrap();

            assert!(asn1::parse_single::<Accuracy>(&enc).is_err());
        }
    }
}
