use std::sync::Arc;

use sequoia_openpgp as openpgp;
use openpgp::Result;
use openpgp::packet;
use openpgp::parse::Parse;

use crate::capnp_relay;
use crate::capnp_relay::CapnProtoRelay;

use crate::keystore;
use crate::InaccessibleDecryptionKey;
use crate::Key;
use crate::server;

#[derive(thiserror::Error, Debug)]
/// Errors returned from the keystore.
// There errors are also defined in keystore_protocol.capnp.  If you
// add a variant here, then you'll probably need to add it there as
// well.
pub enum Error {
    // keystore_protocol.capnp errors:

    /// A generic error occurred.
    ///
    /// The other error variants aren't appropriate for representing
    /// this error.  The text describes what went wrong.
    #[error("Error: {}",
            .0.as_deref().unwrap_or("an unspecified error occurred"))]
    GenericError(Option<String>),

    /// An unspecified protocol error occurred.
    #[error("Unspecified protocol error: something went wrong")]
    ProtocolError,

    /// An end of file condition was reached.
    ///
    /// This is also used for iterations to indicate that there are no
    /// more items.
    #[error("EOF")]
    EOF,

    /// An operation couldn't be completed, because a required key is
    /// inaccessible (unavailable, or locked).
    ///
    /// This is returned by `Keystore::decrypt`.  Repeat the operation
    /// after ensuring that one of the keys is available by prompting
    /// the user to connect it, or unlocking the key.
    #[error("Can't decrypt a PKESK, the candidate keys are inaccessible")]
    InaccessibleDecryptionKey(Vec<InaccessibleDecryptionKey>),

    /// The key cannot be used for decryption.
    #[error("Key {0} cannot be used for decryption")]
    NotDecryptionCapable(String),

    /// The key cannot be used for signing.
    #[error("Key {0} cannot be used for signing")]
    NotSigningCapable(String),

    /// An internal server error occurred.
    #[error("Internal server error")]
    InternalError(String),

    /// The backend doesn't support importing keys using this interface.
    ///
    /// Some backends require backend-specific information in order to
    /// import a key.  For instance, when importing a key to a
    /// smartcard, the user needs to specify the smartcard, and the
    /// slot to use on the smartcard.  Rather than try and model these
    /// parameters using this generic interface, backends should just
    /// have their own tools.  The text should be a hint that is
    /// displayed to the user describing how to find the tool.
    #[error("Can't import keys into the backend: {}",
            .0.as_deref().unwrap_or("use another tool to import keys \
                                     into this backend"))]
    ExternalImportRequired(Option<String>),

    /// The secret key material cannot be exported.
    ///
    /// This error is returned by [`Key::export`] if the secret key
    /// material cannot be exported.  Many backends do not allow
    /// exporting secret key material.  Some may only allow exporting
    /// secret key material in admin mode.  The text is a more
    /// detailed description of why this is not possible, or what the
    /// user could do to export the secret key material.
    #[error("Can't export secret key material key: {}",
            .0.as_deref().unwrap_or("use another tool to import keys \
                                     into this backend"))]
    SecretKeyMaterialSealed(Option<String>),

    /// The key doesn't support inline passwords.
    ///
    /// Password entry is taken care of by the device managing the
    /// key.  For instance, the password may be obtained using an
    /// external PIN pad.
    #[error("Can't unlock using an inline password{}{}",
            .0.as_ref().map(|_| ": ").unwrap_or(""),
            .0.as_deref().map(|msg| msg).unwrap_or(""))]
    NoInlinePassword(Option<String>),

    /// The key doesn't support getting passwords.
    ///
    /// The password must be provided inline.  The password cannot be
    /// obtained using something like an external PIN pad.
    #[error("External password entry is not supported{}{}",
            .0.as_ref().map(|_| ": ").unwrap_or(""),
            .0.as_deref().map(|msg| msg).unwrap_or(""))]
    NoExternalPassword(Option<String>),

    // Other errors:

    /// A `capnp::Error` occurred.
    #[error("Internal RPC error")]
    RpcError(#[from] capnp::Error),

    // !!! If you add an error here, make sure you update the
    // following function.
}

/// Errors returned from the server, which first need to be converted
/// to the public form, `Error`.
#[derive(thiserror::Error, Debug)]
pub(crate) enum ServerError {
    /// An operation couldn't be completed, because a required key is
    /// inaccessible (unavailable, or locked).
    ///
    /// This is returned by `Keystore::decrypt`.  Repeat the operation
    /// after ensuring that one of the keys is available by prompting
    /// the user to connect it, or unlocking the key.
    #[error("Can't decrypt a PKESK, the candidate keys are inaccessible")]
    InaccessibleDecryptionKey(Vec<server::InaccessibleDecryptionKey>),
}

impl Error {
    /// Converts an error stored in a capnp buffer to our local error
    /// type.
    pub(crate) fn from_capnp(relay: Arc<CapnProtoRelay>,
                             captable: &mut capnp_relay::CapTable,
                             err: keystore::error::Reader<'_>)
        -> anyhow::Error
    {
        let mut try_from = || {
            match err.which() {
                Ok(keystore::error::GenericError(msg)) => {
                    let msg = msg?;
                    if msg.is_empty() {
                        Ok(Error::GenericError(None).into())
                    } else if let Ok(msg) = msg.to_string() {
                        Ok(Error::GenericError(Some(msg)).into())
                    } else {
                        // Yowzers: we couldn't extract the error
                        // message.
                        Ok(Error::GenericError(
                            Some("Error parsing error message".into())).into())
                    }
                },
                Ok(keystore::error::Protocol(())) => Ok(Error::ProtocolError.into()),
                Ok(keystore::error::Eof(())) => Ok(Error::EOF.into()),
                Ok(keystore::error::NotDecryptionCapable(s)) => {
                    let s = s?.to_string()?;
                    Ok(Error::NotDecryptionCapable(s).into())
                }
                Ok(keystore::error::NotSigningCapable(s)) => {
                    let s = s?.to_string()?;
                    Ok(Error::NotSigningCapable(s).into())
                }
                Ok(keystore::error::InaccessibleDecryptionKey(keys)) => {
                    let keys = keys?.into_iter()
                        .map(|inaccessible_key| {
                            let key = inaccessible_key.get_key_descriptor()?;

                            let cap = key.get_handle()?;

                            let pk = key.get_public_key()?;
                            let pk = packet::Key::<packet::key::UnspecifiedParts,
                                                   packet::key::UnspecifiedRole>
                                ::from_bytes(pk)?;
                            let pk = pk.parts_into_public();

                            let pkesk = inaccessible_key.get_pkesk()?;
                            let pkesk = packet::PKESK::from_bytes(pkesk)?;

                            Ok(InaccessibleDecryptionKey {
                                key: Key {
                                    relay: Arc::clone(&relay),
                                    cap: captable.insert(cap.client),
                                    key: pk,
                                },
                                pkesk: pkesk,
                            })
                        })
                        .collect::<Result<Vec<_>>>()?;
                    Ok(Error::InaccessibleDecryptionKey(keys).into())
                }
                Ok(keystore::error::InternalError(s)) => {
                    let s = s?.to_string()?;
                    Ok(Error::InternalError(s).into())
                }
                Ok(keystore::error::ExternalImportRequired(msg)) => {
                    let msg = msg?;
                    if msg.is_empty() {
                        Ok(Error::ExternalImportRequired(None).into())
                    } else if let Ok(msg) = msg.to_string() {
                        Ok(Error::ExternalImportRequired(Some(msg)).into())
                    } else {
                        // Yowzers: we couldn't extract the error
                        // message.
                        Ok(Error::ExternalImportRequired(None).into())
                    }
                }
                Ok(keystore::error::SecretKeyMaterialSealed(msg)) => {
                    let msg = msg?;
                    if msg.is_empty() {
                        Ok(Error::SecretKeyMaterialSealed(None).into())
                    } else if let Ok(msg) = msg.to_string() {
                        Ok(Error::SecretKeyMaterialSealed(Some(msg)).into())
                    } else {
                        // Yowzers: we couldn't extract the error
                        // message.
                        Ok(Error::SecretKeyMaterialSealed(None).into())
                    }
                }
                Ok(keystore::error::NoInlinePassword(msg)) => {
                    let msg = msg?;
                    if msg.is_empty() {
                        Ok(Error::NoInlinePassword(None).into())
                    } else if let Ok(msg) = msg.to_string() {
                        Ok(Error::NoInlinePassword(Some(msg)).into())
                    } else {
                        // Yowzers: we couldn't extract the error
                        // message.
                        Ok(Error::NoInlinePassword(None).into())
                    }
                }
                Ok(keystore::error::NoExternalPassword(msg)) => {
                    let msg = msg?;
                    if msg.is_empty() {
                        Ok(Error::NoExternalPassword(None).into())
                    } else if let Ok(msg) = msg.to_string() {
                        Ok(Error::NoExternalPassword(Some(msg)).into())
                    } else {
                        // Yowzers: we couldn't extract the error
                        // message.
                        Ok(Error::NoExternalPassword(None).into())
                    }
                }
                Err(err) => {
                    // The error is incorrectly formatted.  Turn that into
                    // a protocol error.
                    log::debug!("Protocol violation while parsing error: {}",
                                err);
                    Ok(Error::ProtocolError.into())
                }
            }
        };

        match try_from() {
            Ok(err) => err,
            Err(err) => err,
        }
    }
}

impl keystore::error::Builder<'_> {
    /// Sets the error on the wire from an `anyhow::Error`.
    ///
    /// This converts an error to the wire format.  This only handles
    /// the local error type, [`keystore::error::Error]`; everything
    /// else is mapped to [`Error::UnspecifiedError`].
    ///
    /// Note: client code never needs this.
    pub(crate) fn from_anyhow(&mut self, err: &anyhow::Error) {
        match err.downcast_ref::<ServerError>() {
            Some(ServerError::InaccessibleDecryptionKey(keys)) => {
                let mut keys_wire = self
                    .reborrow()
                    .init_inaccessible_decryption_key(keys.len() as u32);
                for (i, key) in keys.into_iter().enumerate() {
                    key.serialize(keys_wire.reborrow().get(i as u32));
                }

                return;
            }
            None => (),
        }

        match err.downcast_ref::<Error>() {
            Some(Error::GenericError(msg)) =>
                self.set_generic_error(msg.as_deref().unwrap_or("")),
            Some(Error::ProtocolError) =>
                self.set_protocol(()),
            Some(Error::EOF) =>
                self.set_eof(()),
            Some(Error::NotDecryptionCapable(fpr)) => {
                let mut builder = self.reborrow()
                    .init_not_decryption_capable(fpr.len() as u32);
                builder.push_str(&fpr);
            }
            Some(Error::NotSigningCapable(fpr)) => {
                let mut builder = self.reborrow()
                    .init_not_signing_capable(fpr.len() as u32);
                builder.push_str(&fpr);
            }
            Some(Error::InaccessibleDecryptionKey(_keys)) => {
                // We never have to serialize an Error::Inaccessible:
                // that's a client variant.  On the server, we use
                // ServerError::Inaccessible.
                log::debug!("Invalid attempt to serialize Error::InaccessibleDecryptionKey");
                self.set_internal_error("InaccessibleDecryptionKey shouldn't \
                                         be returned in this context");
            }
            Some(Error::InternalError(err)) => {
                let mut builder = self.reborrow()
                    .init_internal_error(err.len() as u32);
                builder.push_str(&err);
            }
            Some(Error::ExternalImportRequired(msg)) => {
                self.set_external_import_required(msg.as_deref().unwrap_or(""));
            }
            Some(Error::SecretKeyMaterialSealed(msg)) => {
                self.set_secret_key_material_sealed(msg.as_deref().unwrap_or(""));
            }
            Some(Error::NoInlinePassword(msg)) => {
                self.set_no_inline_password(msg.as_deref().unwrap_or(""));
            }
            Some(Error::NoExternalPassword(msg)) => {
                self.set_no_external_password(msg.as_deref().unwrap_or(""));
            }
            Some(Error::RpcError(_err)) =>
                self.set_protocol(()),
            None => {
                // This is the best we can do.
                self.set_generic_error(err.to_string());
            }
        }
    }
}

impl From<capnp::NotInSchema> for Error {
    fn from(_: capnp::NotInSchema) -> Self {
        Error::ProtocolError
    }
}
