use crate::be::dbvalue::DbValueCertificate;
use crate::prelude::*;
use crate::schema::SchemaAttribute;
use crate::valueset::ScimResolveStatus;
use crate::valueset::{DbValueSetV2, ValueSet, ValueSetResolveStatus, ValueSetScimPut};
use crypto_glue::{
    s256::Sha256Output,
    traits::{DecodeDer, EncodeDer, EncodePem, LineEndingPem},
    x509::{x509_digest_public_key_sha256, Certificate},
};
use kanidm_proto::scim_v1::client::ScimCertificate as ClientScimCertificate;
use kanidm_proto::scim_v1::server::ScimCertificate;
use kanidm_proto::scim_v1::JsonValue;
use std::collections::BTreeMap;

#[derive(Debug, Clone)]
pub struct ValueSetCertificate {
    map: BTreeMap<Sha256Output, Box<Certificate>>,
}

impl ValueSetCertificate {
    pub fn new(certificate: Box<Certificate>) -> Result<Box<Self>, OperationError> {
        let mut map = BTreeMap::new();

        let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
            error!("Unable to digest public key");
            OperationError::VS0002CertificatePublicKeyDigest
        })?;
        map.insert(pk_s256, certificate);

        Ok(Box::new(ValueSetCertificate { map }))
    }

    pub fn from_dbvs2(data: Vec<DbValueCertificate>) -> Result<ValueSet, OperationError> {
        Self::from_dbv_iter(data.into_iter())
    }

    fn from_dbv_iter(
        certs: impl Iterator<Item = DbValueCertificate>,
    ) -> Result<ValueSet, OperationError> {
        let mut map = BTreeMap::new();

        for db_cert in certs {
            match db_cert {
                DbValueCertificate::V1 { certificate_der } => {
                    // Parse the DER
                    let certificate = Certificate::from_der(&certificate_der)
                        .map(Box::new)
                        .map_err(|x509_err| {
                            error!(?x509_err, "Unable to restore certificate from DER");
                            OperationError::VS0003CertificateDerDecode
                        })?;

                    // sha256 the public key
                    let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
                        error!("Unable to digest public key");
                        OperationError::VS0004CertificatePublicKeyDigest
                    })?;

                    map.insert(pk_s256, certificate);
                }
            }
        }

        Ok(Box::new(ValueSetCertificate { map }))
    }

    fn to_vec_dbvs(&self) -> Vec<DbValueCertificate> {
        self.map
            .iter()
            .filter_map(|(pk_s256, cert)| {
                cert.to_der()
                    .map_err(|der_err| {
                        error!(
                            ?pk_s256,
                            ?der_err,
                            "Failed to serialise certificate to der. This value will be dropped!"
                        );
                    })
                    .ok()
            })
            .map(|certificate_der| DbValueCertificate::V1 { certificate_der })
            .collect()
    }

    #[allow(clippy::should_implement_trait)]
    pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
    where
        T: IntoIterator<Item = Box<Certificate>>,
    {
        let mut map = BTreeMap::new();

        for certificate in iter {
            let pk_s256 = x509_digest_public_key_sha256(&certificate)?;
            map.insert(pk_s256, certificate);
        }

        Some(Box::new(ValueSetCertificate { map }))
    }
}

impl ValueSetScimPut for ValueSetCertificate {
    fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
        let der_values: Vec<ClientScimCertificate> =
            serde_json::from_value(value).map_err(|err| {
                error!(?err, "SCIM Certificate syntax invalid");
                OperationError::SC0012CertificateSyntaxInvalid
            })?;

        // For each one, check it's a real der certificate.
        let mut map = BTreeMap::new();

        for ClientScimCertificate { der } in der_values {
            // Parse the DER
            let certificate = Certificate::from_der(&der)
                .map(Box::new)
                .map_err(|x509_err| {
                    error!(?x509_err, "Unable to restore certificate from DER");
                    OperationError::SC0013CertificateInvalidDer
                })?;

            // sha256 the public key
            let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
                error!("Unable to digest public key");
                OperationError::SC0014CertificateInvalidDigest
            })?;

            map.insert(pk_s256, certificate);
        }

        Ok(ValueSetResolveStatus::Resolved(Box::new(
            ValueSetCertificate { map },
        )))
    }
}

impl ValueSetT for ValueSetCertificate {
    fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
        match value {
            Value::Certificate(certificate) => {
                let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
                    error!("Unable to digest public key");
                    OperationError::VS0005CertificatePublicKeyDigest
                })?;

                // bool -> true if the insert did not trigger a duplicate.
                Ok(self.map.insert(pk_s256, certificate).is_none())
            }
            _ => {
                debug_assert!(false);
                Err(OperationError::InvalidValueState)
            }
        }
    }

    fn clear(&mut self) {
        self.map.clear();
    }

    fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
        match pv {
            PartialValue::HexString(hs) => {
                let mut buf = Sha256Output::default();
                if hex::decode_to_slice(hs, &mut buf).is_ok() {
                    self.map.remove(&buf).is_some()
                } else {
                    false
                }
            }
            _ => false,
        }
    }

    fn contains(&self, pv: &PartialValue) -> bool {
        match pv {
            PartialValue::HexString(hs) => {
                let mut buf = Sha256Output::default();
                if hex::decode_to_slice(hs, &mut buf).is_ok() {
                    self.map.contains_key(&buf)
                } else {
                    false
                }
            }
            _ => false,
        }
    }

    fn substring(&self, _pv: &PartialValue) -> bool {
        false
    }

    fn startswith(&self, _pv: &PartialValue) -> bool {
        false
    }

    fn endswith(&self, _pv: &PartialValue) -> bool {
        false
    }

    fn lessthan(&self, _pv: &PartialValue) -> bool {
        false
    }

    fn len(&self) -> usize {
        self.map.len()
    }

    fn generate_idx_eq_keys(&self) -> Vec<String> {
        self.map.keys().map(hex::encode).collect()
    }

    fn syntax(&self) -> SyntaxType {
        SyntaxType::Certificate
    }

    fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
        true
    }

    fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
        Box::new(self.map.iter().filter_map(|(pk_s256, cert)| {
            cert.to_pem(LineEndingPem::LF)
                .ok()
                .map(|pem| format!("{}\n{}", hex::encode(pk_s256), pem))
        }))
    }

    fn to_scim_value(&self) -> Option<ScimResolveStatus> {
        let vals: Vec<ScimCertificate> = self
            .map
            .iter()
            .filter_map(|(s256, cert)| {
                cert.to_der()
                    .map_err(|der_err| {
                        error!(
                            ?s256,
                            ?der_err,
                            "Failed to serialise certificate to der. This value will be dropped!"
                        );
                    })
                    .ok()
                    .map(|der| (s256, der))
            })
            .map(|(s256, cert_der)| ScimCertificate {
                s256: s256.to_vec(),
                der: cert_der,
            })
            .collect::<Vec<_>>();

        if vals.is_empty() {
            None
        } else {
            Some(ScimValueKanidm::from(vals).into())
        }
    }

    fn to_db_valueset_v2(&self) -> DbValueSetV2 {
        let data = self.to_vec_dbvs();
        DbValueSetV2::Certificate(data)
    }

    fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
        Box::new(
            self.map
                .keys()
                .map(hex::encode)
                .map(PartialValue::HexString),
        )
    }

    fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
        Box::new(self.map.values().cloned().map(Value::Certificate))
    }

    fn equal(&self, other: &ValueSet) -> bool {
        if let Some(other) = other.as_certificate_set() {
            &self.map == other
        } else {
            debug_assert!(false);
            false
        }
    }

    fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
        if let Some(b) = other.as_certificate_set() {
            mergemaps!(self.map, b)
        } else {
            debug_assert!(false);
            Err(OperationError::InvalidValueState)
        }
    }

    fn to_certificate_single(&self) -> Option<&Certificate> {
        if self.map.len() == 1 {
            self.map.values().take(1).map(|b| b.as_ref()).next()
        } else {
            None
        }
    }

    fn as_certificate_set(&self) -> Option<&BTreeMap<Sha256Output, Box<Certificate>>> {
        Some(&self.map)
    }
}

#[cfg(test)]
mod tests {
    use super::ValueSetCertificate;
    use crate::prelude::{ScimValueKanidm, ValueSet};
    use crypto_glue::{traits::DecodePem, x509::Certificate};

    // Generated with:
    //
    // openssl ecparam -out ec_key.pem -name secp256r1 -genkey
    // openssl req -new -key ec_key.pem -x509 -nodes -days 365 -out cert.pem
    const PEM_DATA: &str = r#"-----BEGIN CERTIFICATE-----
MIIB3zCCAYWgAwIBAgIUdJ6IWvI+8M6nwK7ykUK7/iBq7yQwCgYIKoZIzj0EAwIw
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA4MjEwNjQ2MzBaFw0yNTA4MjEw
NjQ2MzBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAAS2Szn4NPmgxawC1+MRC41jqobemNkXkRZ9AgozK0zRDFc6k1IHUZ++
wN0USpXDQYDnJfATqvlpKPebnHxTytt6o1MwUTAdBgNVHQ4EFgQU1oR1x2CnoPap
JMKPCVVzqWf2ANYwHwYDVR0jBBgwFoAU1oR1x2CnoPapJMKPCVVzqWf2ANYwDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBpy0o2CY97MIxeQ0HgG44Y
raBy6edj7W0EIH+yQxkDEwIhAI0nVKaI6duHLAvtKW6CfEQFG6jKg7dyk37YYiRD
2jS0
-----END CERTIFICATE-----"#;

    #[test]
    fn test_scim_certificate() {
        let cert = Certificate::from_pem(PEM_DATA).unwrap();

        let vs: ValueSet = ValueSetCertificate::new(Box::new(cert)).unwrap();

        let scim_value = vs.to_scim_value().unwrap().assume_resolved();

        let cert = match scim_value {
            ScimValueKanidm::ArrayCertificate(mut set) => set.pop().unwrap(),
            _ => unreachable!(),
        };

        let expect_s256 =
            hex::decode("8c98a09d0a50db92ccb6d05be846d9b9315015520d19a3e1739aeb8d84ebc28d")
                .unwrap();

        assert_eq!(cert.s256, expect_s256);

        // Test that we can parse json values into a valueset.
        crate::valueset::scim_json_put_reflexive::<ValueSetCertificate>(&vs, &[])
    }
}
