use {
    crate::BlackHole, ring::signature::ED25519_PKCS8_V2_LEN, std::fmt::Display, std::ops::Deref,
    untrusted::Input,
};

pub use ring::signature::{
    VerificationAlgorithm,

    ECDSA_P256_SHA384_ASN1, // not recommended. verification of asn.1 der-encoded ecdsa signatures using the p-256 curve and sha-384.
    ECDSA_P384_SHA256_ASN1, // not recommended. verification of asn.1 der-encoded ecdsa signatures using the p-384 curve and sha-256.
    ECDSA_P384_SHA384_ASN1, // verification of asn.1 der-encoded ecdsa signatures using the p-384 curve and sha-384.
    ECDSA_P384_SHA384_FIXED, // verification of fixed-length (pkcs#11 style) ecdsa signatures using the p-384 curve and sha-384.
    ED25519,                 // verification of ed25519 signatures.
    RSA_PKCS1_2048_8192_SHA1, // verification of signatures using rsa keys of 2048-8192 bits, pkcs#1.5 padding, and sha-1.
    RSA_PKCS1_2048_8192_SHA256, // verification of signatures using rsa keys of 2048-8192 bits, pkcs#1.5 padding, and sha-256.
    RSA_PKCS1_2048_8192_SHA384, // verification of signatures using rsa keys of 2048-8192 bits, pkcs#1.5 padding, and sha-384.
    RSA_PKCS1_2048_8192_SHA512, // verification of signatures using rsa keys of 2048-8192 bits, pkcs#1.5 padding, and sha-512.
    RSA_PKCS1_3072_8192_SHA384, // verification of signatures using rsa keys of 3072-8192 bits, pkcs#1.5 padding, and sha-384.
    RSA_PKCS1_SHA256,           // pkcs#1 1.5 padding using sha-256 for rsa signatures.
    RSA_PKCS1_SHA384,           // pkcs#1 1.5 padding using sha-384 for rsa signatures.
    RSA_PKCS1_SHA512,           // pkcs#1 1.5 padding using sha-512 for rsa signatures.
    RSA_PSS_2048_8192_SHA256, // verification of signatures using rsa keys of 2048-8192 bits, pss padding, and sha-256.
    RSA_PSS_2048_8192_SHA384, // verification of signatures using rsa keys of 2048-8192 bits, pss padding, and sha-384.
    RSA_PSS_2048_8192_SHA512, // verification of signatures using rsa keys of 2048-8192 bits, pss padding, and sha-512.
    RSA_PSS_SHA256,           // rsa pss padding using sha-256 for rsa signatures.
    RSA_PSS_SHA384,           // rsa pss padding using sha-384 for rsa signatures.
    RSA_PSS_SHA512,           // rsa pss padding using sha-512 for rsa signatures.
};

pub struct Signature {
    underlying: ring::signature::Signature,
}

impl AsRef<[u8]> for Signature {
    fn as_ref(&self) -> &[u8] {
        self.underlying.as_ref()
    }
}

impl Deref for Signature {
    type Target = [u8];

    fn deref(&self) -> &[u8] {
        self.underlying.as_ref()
    }
}

pub struct Ed25519KeyPair {
    underlying: ring::signature::Ed25519KeyPair,
}

impl Ed25519KeyPair {
    /// generates a new key pair and returns the key pair serialized as a pkcs#8 document.
    ///
    /// the pkcs#8 document will be a v2 `oneasymmetrickey` with the public key, as described in [rfc 5958 section 2].
    /// see also https://tools.ietf.org/html/draft-ietf-curdle-pkix-04.
    ///
    /// [rfc 5958 section 2]: https://tools.ietf.org/html/rfc5958#section-2
    pub fn generate_pkcs8(
        random: &ring::rand::SecureRandom,
    ) -> Result<[u8; ED25519_PKCS8_V2_LEN], BlackHole> {
        ring::signature::Ed25519KeyPair::generate_pkcs8(random).map_err(From::from)
    }

    /// constructs an ed25519 key pair by parsing an unencrypted pkcs#8 v2 ed25519 private key.
    ///
    /// the input must be in pkcs#8 v2 format, and in particular it must contain the public key in addition to the
    /// private key. `from_pkcs8()` will verify that the public key and the private key are consistent with each other.
    ///
    /// if you need to parse pkcs#8 v1 files (without the public key) then use
    /// `ed25519keypair::from_pkcs8_maybe_unchecked()` instead.
    pub fn from_pkcs8(key: &[u8]) -> Result<Ed25519KeyPair, BlackHole> {
        let input = Input::from(key);
        let underlying = ring::signature::Ed25519KeyPair::from_pkcs8(input)?;

        Ok(Ed25519KeyPair { underlying })
    }

    /// constructs an ed25519 key pair by parsing an unencrypted pkcs#8 v1 or v2 ed25519 private key.
    ///
    /// it is recommended to use `ed25519keypair::from_pkcs8()`, which accepts only pkcs#8 v2 files that contain the
    /// public key. `from_pkcs8_maybe_unchecked()` parses pkcs#2 files exactly like `from_pkcs8()`. it also accepts v1
    /// files. pkcs#8 v1 files do not contain the public key, so when a v1 file is parsed the public key will be
    /// computed from the private key, and there will be no consistency check between the public key and the private
    /// key.
    ///
    /// pkcs#8 v2 files are parsed exactly like `ed25519keypair::from_pkcs8()`.
    pub fn from_pkcs8_maybe_unchecked(input: &[u8]) -> Result<Ed25519KeyPair, BlackHole> {
        let input = Input::from(input);
        let underlying = ring::signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(input)?;

        Ok(Ed25519KeyPair { underlying })
    }

    /// constructs an ed25519 key pair from the private key seed `seed` and its public key `public_key`.
    ///
    /// it is recommended to use `ed25519keypair::from_pkcs8()` instead.
    ///
    /// the private and public keys will be verified to be consistent with each other. this helps avoid misuse of the
    /// key (e.g. accidentally swapping the private key and public key, or using the wrong private key for the public
    /// key). this also detects any corruption of the public or private key.
    pub fn from_seed_and_public_key(
        seed: &[u8],
        public_key: &[u8],
    ) -> Result<Ed25519KeyPair, BlackHole> {
        let seed = Input::from(seed);
        let public_key = Input::from(public_key);
        let underlying =
            ring::signature::Ed25519KeyPair::from_seed_and_public_key(seed, public_key)?;

        Ok(Ed25519KeyPair { underlying })
    }

    /// constructs a ed25519 key pair from the private key seed `seed`.
    ///
    /// it is recommended to use `ed25519keypair::from_pkcs8()` instead. when that is not practical, it is recommended
    /// to use `ed25519keypair::from_seed_and_public_key()` instead.
    ///
    /// since the public key is not given, the public key will be computed from the private key. it is not possible to
    /// detect misuse or corruption of the private key since the public key isn't given as input.
    pub fn from_seed_unchecked(seed: &[u8]) -> Result<Ed25519KeyPair, BlackHole> {
        let seed = Input::from(seed);
        let underlying = ring::signature::Ed25519KeyPair::from_seed_unchecked(seed)?;

        Ok(Ed25519KeyPair { underlying })
    }

    /// returns a reference to the little-endian encoded bytes for this pair's public key.
    pub fn public_key_bytes(&self) -> &[u8] {
        self.underlying.public_key_bytes()
    }

    /// signs `message` using this pair's private key, and returns its signature.
    pub fn sign(&self, message: &[u8]) -> Signature {
        Signature {
            underlying: self.underlying.sign(message),
        }
    }
}

#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct SignatureError;

impl Display for SignatureError {
    fn fmt(&self, _: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        Ok(())
    }
}

pub fn validate_signature(
    algorithm: &VerificationAlgorithm,
    public_key: &[u8],
    message: &[u8],
    signature: &[u8],
) -> Result<(), SignatureError> {
    ring::signature::verify(
        algorithm,
        Input::from(public_key),
        Input::from(message),
        Input::from(signature),
    )
    .map_err(|_| SignatureError)
}