//! This module is where all relay related keys are declared along their key specifier for the
//! KeyMgr so some of them can be stored on disk.

use std::fmt;

use derive_deftly::Deftly;
use derive_more::Constructor;
use derive_more::derive::{From, Into};

use tor_error::Bug;
use tor_keymgr::{
    InvalidKeyPathComponentValue, KeySpecifier, KeySpecifierComponent,
    derive_deftly_template_CertSpecifier, derive_deftly_template_KeySpecifier,
};
use tor_persist::slug::{Slug, timestamp::Iso8601TimeSlug};
use web_time_compat::SystemTime;

/// The key specifier of the relay long-term identity key (RelayIdentityKeypair)
#[non_exhaustive]
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KS_relayid_ed")]
#[deftly(summary = "Relay long-term identity keypair")]
pub(crate) struct RelayIdentityKeypairSpecifier;

/// The public part of the long-term identity key of the relay.
#[non_exhaustive]
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KP_relayid_ed")]
#[deftly(summary = "Public part of the relay long-term identity keypair")]
pub(crate) struct RelayIdentityPublicKeySpecifier;

/// The key specifier of the legacy RSA relay long-term identity key (RelayIdentityRsaKeypair)
#[non_exhaustive]
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KS_relayid_rsa")]
#[deftly(summary = "Legacy RSA long-term relay identity keypair")]
pub(crate) struct RelayIdentityRsaKeypairSpecifier;

/// The public part of the long-term identity key of the relay.
#[non_exhaustive]
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KP_relayid_rsa")]
#[deftly(summary = "Public part of the relay long-term identity keypair")]
pub(crate) struct RelayIdentityRsaPublicKeySpecifier;

/// The key specifier of the relay medium-term signing key.
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KS_relaysign_ed")]
#[deftly(summary = "Relay medium-term signing keypair")]
pub(crate) struct RelaySigningKeypairSpecifier {
    /// The expiration time of this key.
    ///
    /// This **must** be the same as the expiration timestamp from the
    /// `K_relaysign_ed` certificate of this key.
    ///
    /// This serves as a unique identifier for this key instance,
    /// and is used for deciding which `K_relaysign_ed` key to use
    /// (we use the newest key that is not yet expired according to
    /// the `valid_until` timestamp from its specifier).
    ///
    /// **Important**: this timestamp should not be used for anything other than
    /// distinguishing between different signing keypair instances.
    /// In particular, it should **not** be used for validating the keypair,
    /// or for checking its timeliness.
    #[deftly(denotator)]
    pub(crate) valid_until: Timestamp,
}

/// The key specifier of the public part of the relay medium-term signing key.
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KP_relaysign_ed")]
#[deftly(summary = "Public part of the relay medium-term signing keypair")]
#[deftly(keypair_specifier = RelaySigningKeypairSpecifier)]
pub(crate) struct RelaySigningPublicKeySpecifier {
    /// The expiration time of this key.
    ///
    /// This **must** be the same as the expiration timestamp from the
    /// `K_relaysign_ed` certificate of this key.
    ///
    /// This serves as a unique identifier for this key instance,
    /// and is used for deciding which `K_relaysign_ed` key to use
    /// (we use the newest key that is not yet expired according to
    /// the `valid_until` timestamp from its specifier).
    ///
    /// **Important**: this timestamp should not be used for anything other than
    /// distinguishing between different signing keypair instances.
    /// In particular, it should **not** be used for validating the keypair,
    /// or for checking its timeliness.
    #[deftly(denotator)]
    pub(crate) valid_until: Timestamp,
}

impl From<&RelaySigningPublicKeySpecifier> for RelaySigningKeypairSpecifier {
    fn from(public_key_specifier: &RelaySigningPublicKeySpecifier) -> RelaySigningKeypairSpecifier {
        RelaySigningKeypairSpecifier::new(public_key_specifier.valid_until)
    }
}

/// The key specifier of the relay medium-term ntor circuit extension key.
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KS_ntor")]
#[deftly(summary = "Relay medium-term ntor keypair")]
pub(crate) struct RelayNtorKeypairSpecifier {
    /// The expiration time of this key.
    #[deftly(denotator)]
    pub(crate) valid_until: Timestamp,
}

/// The key specifier of the public part of the relay medium-term ntor circuit extension key.
#[derive(Deftly, PartialEq, Debug, Constructor, Copy, Clone)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KP_ntor")]
#[deftly(summary = "Public part of the relay medium-term ntor keypair")]
#[deftly(keypair_specifier = RelayNtorKeypairSpecifier)]
pub(crate) struct RelayNtorPublicKeySpecifier {
    /// The expiration time of this key.
    #[deftly(denotator)]
    pub(crate) valid_until: Timestamp,
}

impl From<&RelayNtorPublicKeySpecifier> for RelayNtorKeypairSpecifier {
    fn from(public_key_specifier: &RelayNtorPublicKeySpecifier) -> RelayNtorKeypairSpecifier {
        RelayNtorKeypairSpecifier::new(public_key_specifier.valid_until)
    }
}

/// Certificate specifier for the [`RelaySigningPublicKeySpecifier`] certificate.
///
/// Represents `KP_relaysign_ed` signed by the `KS_relayid_ed` identity key.
#[derive(Deftly, Constructor)]
#[derive_deftly(CertSpecifier)]
pub(crate) struct RelaySigningKeyCertSpecifier {
    /// The subject key of this certificate.
    #[deftly(subject)]
    subject: RelaySigningPublicKeySpecifier,
}

/// The approximate time when a [`RelaySigningKeypairSpecifier`] was generated.
///
/// Used as a denotator to distinguish between the different signing keypair instances
/// that might be stored in the keystore.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] //
#[derive(Into, From)]
pub(crate) struct Timestamp(Iso8601TimeSlug);

impl From<SystemTime> for Timestamp {
    fn from(t: SystemTime) -> Self {
        Self(t.into())
    }
}

impl From<Timestamp> for SystemTime {
    fn from(t: Timestamp) -> Self {
        t.0.into()
    }
}

impl KeySpecifierComponent for Timestamp {
    fn to_slug(&self) -> Result<Slug, Bug> {
        self.0.try_into()
    }

    fn from_slug(s: &Slug) -> Result<Self, InvalidKeyPathComponentValue>
    where
        Self: Sized,
    {
        use std::str::FromStr as _;

        let timestamp = Iso8601TimeSlug::from_str(s.as_ref())
            .map_err(|e| InvalidKeyPathComponentValue::Slug(e.to_string()))?;

        Ok(Self(timestamp))
    }

    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

/// The key specifier of the relay link authentication key.
#[derive(Deftly, PartialEq, Debug, Constructor)]
#[derive_deftly(KeySpecifier)]
#[deftly(prefix = "relay")]
#[deftly(role = "KS_link_ed")]
#[deftly(summary = "Relay short-term link authentication keypair")]
pub(crate) struct RelayLinkSigningKeypairSpecifier {
    /// The expiration time of this key.
    ///
    /// This **must** be the same as the expiration timestamp from the
    /// `KP_link_ed` certificate of this key.
    ///
    /// This serves as a unique identifier for this key instance,
    /// and is used for deciding which `KP_link_ed` key to use
    /// (we use the newest key that is not yet expired according to
    /// the `valid_until` timestamp from its specifier).
    ///
    /// **Important**: this timestamp should not be used for anything other than
    /// distinguishing between different signing keypair instances.
    /// In particular, it should **not** be used for validating the keypair,
    /// or for checking its timeliness.
    #[deftly(denotator)]
    pub(crate) valid_until: Timestamp,
}

#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_time_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    use super::*;

    use tor_keymgr::test_utils::check_key_specifier;
    use tor_keymgr::{CertSpecifierPattern, KeyCertificateSpecifier, KeyPathPattern};

    #[test]
    fn relay_signing_key_specifiers() {
        let ts = SystemTime::UNIX_EPOCH;
        let key_spec = RelaySigningKeypairSpecifier::new(ts.into());

        assert_eq!(
            key_spec.arti_path().unwrap().as_str(),
            "relay/ks_relaysign_ed+19700101000000"
        );

        check_key_specifier(&key_spec, "relay/ks_relaysign_ed+19700101000000");

        let pubkey_spec = RelaySigningPublicKeySpecifier::new(ts.into());

        assert_eq!(
            pubkey_spec.arti_path().unwrap().as_str(),
            "relay/kp_relaysign_ed+19700101000000"
        );

        check_key_specifier(&pubkey_spec, "relay/kp_relaysign_ed+19700101000000");
        let cert_spec = RelaySigningKeyCertSpecifier {
            subject: pubkey_spec,
        };

        assert_eq!(
            cert_spec.subject_key_specifier().arti_path().unwrap(),
            pubkey_spec.arti_path().unwrap()
        );

        assert_eq!(
            RelaySigningKeyCertSpecifierPattern::new_any()
                .arti_pattern()
                .unwrap(),
            KeyPathPattern::Arti("relay/kp_relaysign_ed+*".to_string())
        );
    }

    #[test]
    fn relay_identity_key_specifiers() {
        let key_spec = RelayIdentityKeypairSpecifier::new();

        assert_eq!(
            key_spec.arti_path().unwrap().as_str(),
            "relay/ks_relayid_ed"
        );

        check_key_specifier(&key_spec, "relay/ks_relayid_ed");

        let key_spec = RelayIdentityPublicKeySpecifier::new();

        assert_eq!(
            key_spec.arti_path().unwrap().as_str(),
            "relay/kp_relayid_ed"
        );

        check_key_specifier(&key_spec, "relay/kp_relayid_ed");
    }
}
