use core::fmt;
use std::borrow::Cow;

use base64::Engine;
use json_patch::Patch;
use serde::{Deserialize, Serialize};
use ssi_dids_core::{
    document::{service::Endpoint as ServiceEndpoint, Service},
    registration::{DIDDocumentOperation, DIDDocumentOperationKind, DIDTransactionCreationError},
};
use ssi_jwk::{Base64urlUInt, JWK};
use ssi_verification_methods::ProofPurpose;

mod client;
mod did;
mod operation;
mod resolver;

pub use client::*;
pub use did::*;
pub use operation::*;
pub use resolver::*;

const MULTIHASH_SHA2_256_PREFIX: &[u8] = &[0x12];
const MULTIHASH_SHA2_256_SIZE: &[u8] = &[0x20];

/// Verification method type for Create operation
///
/// This is used when converting JWK to [verification method map][vmm] for the Create operation.
///
/// Reference: [Sidetree §12.1.1 `add-public-keys`][apk] Step 3.2
///
/// [apk]: https://identity.foundation/sidetree/spec/v1.0.0/#add-public-keys
/// [vmm]: https://www.w3.org/TR/did-core/#verification-methods
pub const VERIFICATION_METHOD_TYPE: &str = "JsonWebSignature2020";

#[derive(Debug, thiserror::Error)]
#[error("key generation failed")]
pub struct KeyGenerationFailed;

#[derive(Debug, thiserror::Error)]
pub enum CreateError {
    #[error("same update and recovery keys")]
    SameUpdateAndRecoveryKeys,

    #[error(transparent)]
    KeyGenerationFailed(#[from] KeyGenerationFailed),

    #[error("invalid update key")]
    InvalidUpdateKey,

    #[error("invalid recovery key")]
    InvalidRecoveryKey,
}

impl From<CreateError> for DIDTransactionCreationError {
    fn from(value: CreateError) -> Self {
        match value {
            CreateError::SameUpdateAndRecoveryKeys => {
                DIDTransactionCreationError::SameUpdateAndRecoveryKeys
            }
            CreateError::KeyGenerationFailed(_) => DIDTransactionCreationError::KeyGenerationFailed,
            CreateError::InvalidUpdateKey => DIDTransactionCreationError::InvalidUpdateKey,
            CreateError::InvalidRecoveryKey => DIDTransactionCreationError::InvalidRecoveryKey,
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum UpdateError {
    #[error("invalid update key")]
    InvalidUpdateKey,

    #[error("update key unchanged")]
    UpdateKeyUnchanged,

    #[error("signature failed")]
    SignatureFailed,
}

impl From<UpdateError> for DIDTransactionCreationError {
    fn from(value: UpdateError) -> Self {
        match value {
            UpdateError::InvalidUpdateKey => Self::InvalidUpdateKey,
            UpdateError::UpdateKeyUnchanged => Self::UpdateKeyUnchanged,
            UpdateError::SignatureFailed => Self::SignatureFailed,
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum DeactivateError {
    #[error("invalid recovery key")]
    InvalidRecoveryKey,

    #[error("signature failed")]
    SignatureFailed,
}

impl From<DeactivateError> for DIDTransactionCreationError {
    fn from(value: DeactivateError) -> Self {
        match value {
            DeactivateError::InvalidRecoveryKey => Self::InvalidRecoveryKey,
            DeactivateError::SignatureFailed => Self::SignatureFailed,
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum RecoverError {
    #[error("invalid recovery key")]
    InvalidRecoveryKey,

    #[error("recovery key unchanged")]
    RecoveryKeyUnchanged,

    #[error("signature failed")]
    SignatureFailed,

    #[error(transparent)]
    KeyGenerationFailed(#[from] KeyGenerationFailed),
}

impl From<RecoverError> for DIDTransactionCreationError {
    fn from(value: RecoverError) -> Self {
        match value {
            RecoverError::InvalidRecoveryKey => Self::InvalidRecoveryKey,
            RecoverError::RecoveryKeyUnchanged => Self::RecoveryKeyUnchanged,
            RecoverError::SignatureFailed => Self::SignatureFailed,
            RecoverError::KeyGenerationFailed(_) => Self::KeyGenerationFailed,
        }
    }
}

/// Parameters for a Sidetree client implementation
///
/// This trait consistest of the subset of parameters defined in [Sidetree §5. Default Parameters][default-params] that are needed to implemented a Sidetree client, that is a client to the [Sidetree REST API][sidetree-rest].
///
/// [default-params]: https://identity.foundation/sidetree/spec/v1.0.0/#default-parameters
/// [sidetree-rest]: https://identity.foundation/sidetree/api/
pub trait Sidetree {
    /// [`HASH_PROTOCOL`](https://identity.foundation/sidetree/spec/v1.0.0/#hash-protocol)
    ///
    /// This should be implemented using [hash_algorithm].
    ///
    /// Default implementation calls [hash_protocol_algorithm] and returns the concatenation of the
    /// prefix and hash.
    ///
    /// This function must correspond with [hash_algorithm]. To ensure that correspondence,
    /// implementers may want to override [hash_protocol_algorithm] instead of this function.
    ///
    /// [hash_algorithm]: Self::hash_algorithm
    /// [hash_protocol_algorithm]: Self::hash_protocol_algorithm
    fn hash_protocol(data: &[u8]) -> Vec<u8> {
        let (prefix, hash) = Self::hash_protocol_algorithm(data);
        [prefix, hash].concat()
    }

    /// [`HASH_ALGORITHM`](https://identity.foundation/sidetree/spec/v1.0.0/#hash-algorithm)
    ///
    /// Default implementation calls [hash_protocol_algorithm] and returns the hash, discarding the
    /// prefix.
    ///
    /// This function must correspond with [hash_protocol]. To ensure that correspondence,
    /// implementers may want to override [hash_protocol_algorithm] instead of this function.
    ///
    /// [hash_protocol]: Self::hash_protocol
    /// [hash_protocol_algorithm]: Self::hash_protocol_algorithm
    fn hash_algorithm(data: &[u8]) -> Vec<u8> {
        let (_prefix, hash) = Self::hash_protocol_algorithm(data);
        hash
    }

    /// Combination of [hash_protocol] and [hash_algorithm]
    ///
    /// Returns multihash prefix and hash.
    ///
    /// Default implementation: SHA-256 (`sha2-256`)
    ///
    /// [hash_protocol] and [hash_algorithm] must correspond, and their default implementations
    /// call this function ([hash_protocol_algorithm]). Implementers are therefore encouraged to
    /// overwrite this function ([hash_protocol_algorithm]) rather than those ([hash_protocol] and
    /// [hash_algorithm]).
    ///
    /// [hash_protocol]: Self::hash_protocol
    /// [hash_algorithm]: Self::hash_algorithm
    /// [hash_protocol_algorithm]: Self::hash_protocol_algorithm
    fn hash_protocol_algorithm(data: &[u8]) -> (Vec<u8>, Vec<u8>) {
        use sha2::{Digest, Sha256};
        let mut hasher = Sha256::new();
        hasher.update(data);
        let hash = hasher.finalize().to_vec();
        (
            [MULTIHASH_SHA2_256_PREFIX, MULTIHASH_SHA2_256_SIZE].concat(),
            hash,
        )
    }

    /// [`DATA_ENCODING_SCHEME`](https://identity.foundation/sidetree/spec/v1.0.0/#data-encoding-scheme)
    fn data_encoding_scheme(data: &[u8]) -> String {
        base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(data)
    }

    /// Generate a new keypair ([KEY_ALGORITHM][ka])
    ///
    /// [ka]: https://identity.foundation/sidetree/spec/v1.0.0/#key-algorithm
    fn generate_key() -> JWK;

    /// Ensure that a keypair is valid for this Sidetree DID Method
    ///
    /// Check that the key uses this Sidetree DID method's [KEY_ALGORITHM][ka].
    ///
    /// [ka]: https://identity.foundation/sidetree/spec/v1.0.0/#key-algorithm
    fn validate_key(key: &JWK) -> bool;

    /// [`SIGNATURE_ALGORITHM`](https://identity.foundation/sidetree/spec/v1.0.0/#sig-algorithm) (JWS alg)
    const SIGNATURE_ALGORITHM: ssi_jwk::Algorithm;

    /// [`REVEAL_VALUE`](https://identity.foundation/sidetree/spec/v1.0.0/#reveal-value)
    fn reveal_value(commitment_value: &[u8]) -> String {
        // The spec implies that REVEAL_VALUE uses HASH_PROTOCOL, in §6.2.1:
        //   "Use the implementation’s HASH_PROTOCOL to hash the canonicalized public key to generate the REVEAL_VALUE"
        //   https://identity.foundation/sidetree/spec/v1.0.0/#public-key-commitment-scheme
        let hash = Self::hash_protocol(commitment_value);
        Self::data_encoding_scheme(&hash)
    }

    /// [`MAX_OPERATION_HASH_LENGTH`](https://identity.foundation/sidetree/spec/v1.0.0/#max-operation-hash-length)
    const MAX_OPERATION_HASH_LENGTH: usize = 100;

    /// [`NONCE_SIZE`](https://identity.foundation/sidetree/spec/v1.0.0/#nonce-size)
    const NONCE_SIZE: usize = 16;

    /// Method name for Sidetree-based DID
    ///
    /// Mentioned in [Sidetree §9. DID URI Composition](https://identity.foundation/sidetree/spec/v1.0.0/#did-uri-composition)
    const METHOD: &'static str;

    /// Network instance
    ///
    /// Additional segment after the method-id (METHOD), as a prefix for the method-specific-id
    /// (DID Suffix), identifiying a network instance. e.g. "testnet"
    ///
    /// Mentioned in [Note 1](https://identity.foundation/sidetree/spec/v1.0.0/#note-1)
    const NETWORK: Option<&'static str> = None;

    /// Maximum length of `controller` property
    ///
    /// Reference: [Sidetree §12.1.1 `add-public-keys`](https://identity.foundation/sidetree/spec/v1.0.0/#add-public-keys)
    const MAX_CONTROLLER_LENGTH: Option<usize> = None;

    /// Maximum length of `publicKeyMultibase` property
    ///
    /// Reference: [Sidetree §12.1.1 `add-public-keys`](https://identity.foundation/sidetree/spec/v1.0.0/#add-public-keys)
    const MAX_PKMB_LENGTH: Option<usize> = None;

    /// Hash and encode data
    ///
    /// [Sidetree §6.1 Hashing Process](https://identity.foundation/sidetree/spec/#hashing-process)
    fn hash(data: &[u8]) -> String {
        let hash = Self::hash_protocol(data);
        /*
        ensure!(
            hash.len() <= Self::MAX_OPERATION_HASH_LENGTH,
            "Hash is too long"
        );
        */
        Self::data_encoding_scheme(&hash)
    }

    /// [Public Key Commitment Scheme (Sidetree §6.2.1)][pkcs]
    ///
    /// [pkcs]: https://identity.foundation/sidetree/spec/v1.0.0/#public-key-commitment-scheme
    fn commitment_scheme(pkjwk: &PublicKeyJwk) -> String {
        let canonicalized_public_key = json_canonicalization_scheme(&pkjwk).unwrap();
        // Note: hash_algorithm called here instead of reveal_value, since the underlying hash is
        // used, not the encoded/prefixed one.
        let reveal_value = Self::hash_algorithm(canonicalized_public_key.as_bytes());
        Self::hash(&reveal_value)
    }

    /// Create a Sidetree-based DID using existing keys
    ///
    /// This function creates a Sidetree-based DID using existing public keys for
    /// the update key and recovery key and respective
    /// [commitments][].
    ///
    /// Sidetree specifies in ([§11.1 Create][create]) that creating a Sidetree DID involves
    /// generating a Update keypair and Recovery keypair. That is implemented in [Self::create].
    ///
    /// **Note**: The Sidetree specification ([§6.2.1 Public Key Commitment
    /// Scheme][pkcs]) recommends not reusing public keys across different commitment invocations, and
    /// requires not using public key JWK payloads across commitment invocations.
    ///
    /// [commitments]: https://identity.foundation/sidetree/spec/v1.0.0/#commitment
    /// [create]: https://identity.foundation/sidetree/spec/v1.0.0/#create
    /// [pkcs]: https://identity.foundation/sidetree/spec/v1.0.0/#public-key-commitment-scheme
    fn create_existing(
        update_pk: &PublicKeyJwk,
        recovery_pk: &PublicKeyJwk,
        patches: Vec<DIDStatePatch>,
    ) -> Result<Operation, CreateError> {
        if update_pk == recovery_pk {
            return Err(CreateError::SameUpdateAndRecoveryKeys);
        }

        let update_commitment = Self::commitment_scheme(update_pk);

        let create_operation_delta_object = Delta {
            patches,
            update_commitment,
        };
        let delta_string = json_canonicalization_scheme(&create_operation_delta_object).unwrap();
        let delta_hash = Self::hash(delta_string.as_bytes());

        let recovery_commitment = Self::commitment_scheme(recovery_pk);

        let create_operation_suffix_data_object = SuffixData {
            r#type: None,
            delta_hash,
            recovery_commitment,
            anchor_origin: None,
        };

        let create_operation = CreateOperation {
            suffix_data: create_operation_suffix_data_object,
            delta: create_operation_delta_object,
        };

        Ok(Operation::Create(create_operation))
    }

    /// Create a Sidetree-based DID
    ///
    /// Generate keypairs and construct a Create Operation according to [Sidetree §11.1
    /// Create][create]. Returns the private keys and the create operation.
    ///
    /// [create]: https://identity.foundation/sidetree/spec/v1.0.0/#create
    fn create(patches: Vec<DIDStatePatch>) -> Result<(Operation, JWK, JWK), CreateError> {
        let update_keypair = Self::generate_key();
        let recovery_keypair = Self::generate_key();
        let update_pk = PublicKeyJwk::try_from(update_keypair.to_public())
            .map_err(|_| CreateError::InvalidUpdateKey)?;
        let recovery_pk = PublicKeyJwk::try_from(recovery_keypair.to_public())
            .map_err(|_| CreateError::InvalidRecoveryKey)?;
        let create_op = Self::create_existing(&update_pk, &recovery_pk, patches)?;
        Ok((create_op, update_keypair, recovery_keypair))
    }

    /// Create a Sidetree-based DID
    ///
    /// Construct a DID Update Operation according to [Sidetree §11.2
    /// Update][update]. Returns the update operation.
    ///
    /// Unlike [Self::create] and [Self::recover], this does not generate keys, since the specification does not
    /// call for that here. Instead, the caller must generate a new update keypair, and pass
    /// its public key in the `new_update_pk` argument.
    ///
    /// Using a `update_key` with a [JWK Nonce][jwkn] is not yet supported.
    ///
    /// [update]: https://identity.foundation/sidetree/spec/v1.0.0/#update
    /// [jwkn]: https://identity.foundation/sidetree/spec/#jwk-nonce
    fn update(
        did_suffix: DIDSuffix,
        update_key: &JWK,
        new_update_pk: &PublicKeyJwk,
        patches: Vec<DIDStatePatch>,
    ) -> Result<UpdateOperation, UpdateError> {
        let update_pk = PublicKeyJwk::try_from(update_key.to_public())
            .map_err(|_| UpdateError::InvalidUpdateKey)?;
        let canonicalized_update_pk = json_canonicalization_scheme(&update_pk).unwrap();
        let update_reveal_value = Self::reveal_value(canonicalized_update_pk.as_bytes());

        if new_update_pk == &update_pk {
            return Err(UpdateError::UpdateKeyUnchanged);
        }

        let new_update_commitment = Self::commitment_scheme(new_update_pk);

        let update_operation_delta_object = Delta {
            patches,
            update_commitment: new_update_commitment,
        };

        let delta_string = json_canonicalization_scheme(&update_operation_delta_object).unwrap();
        let delta_hash = Self::hash(delta_string.as_bytes());

        let algorithm = Self::SIGNATURE_ALGORITHM;
        let claims = UpdateClaims {
            update_key: update_pk,
            delta_hash,
        };
        let signed_data = ssi_jwt::encode_sign(algorithm, &claims, update_key)
            .map_err(|_| UpdateError::SignatureFailed)?;
        let update_op = UpdateOperation {
            did_suffix,
            reveal_value: update_reveal_value,
            delta: update_operation_delta_object,
            signed_data,
        };

        Ok(update_op)
    }

    /// Recover a Sidetree-based DID using existing keys
    ///
    /// Like [Self::recover] but does not generate or handle the new update key pair and recovery
    /// key pair; instead, their public keys must be provided by the caller in the `new_update_pk`
    /// and `new_recovery_pk` arguments.
    ///
    /// Returns the constructed DID Recover operation.
    fn recover_existing(
        did_suffix: DIDSuffix,
        recovery_key: &JWK,
        new_update_pk: &PublicKeyJwk,
        new_recovery_pk: &PublicKeyJwk,
        patches: Vec<DIDStatePatch>,
    ) -> Result<Operation, RecoverError> {
        let recovery_pk = PublicKeyJwk::try_from(recovery_key.to_public())
            .map_err(|_| RecoverError::InvalidRecoveryKey)?;

        if new_recovery_pk == &recovery_pk {
            return Err(RecoverError::RecoveryKeyUnchanged);
        }

        let canonicalized_recovery_pk = json_canonicalization_scheme(&recovery_pk).unwrap();
        let recover_reveal_value = Self::reveal_value(canonicalized_recovery_pk.as_bytes());
        let new_update_commitment = Self::commitment_scheme(new_update_pk);
        let new_recovery_commitment = Self::commitment_scheme(new_recovery_pk);

        let recover_operation_delta_object = Delta {
            patches,
            update_commitment: new_update_commitment,
        };

        let delta_string = json_canonicalization_scheme(&recover_operation_delta_object).unwrap();
        let delta_hash = Self::hash(delta_string.as_bytes());

        let algorithm = Self::SIGNATURE_ALGORITHM;
        let claims = RecoveryClaims {
            recovery_commitment: new_recovery_commitment,
            recovery_key: recovery_pk,
            delta_hash,
            anchor_origin: None,
        };
        let signed_data = ssi_jwt::encode_sign(algorithm, &claims, recovery_key)
            .map_err(|_| RecoverError::SignatureFailed)?;
        let recover_op = RecoverOperation {
            did_suffix,
            reveal_value: recover_reveal_value,
            delta: recover_operation_delta_object,
            signed_data,
        };
        Ok(Operation::Recover(recover_op))
    }

    /// Recover a Sidetree-based DID
    ///
    /// Generate keypairs and construct a Recover Operation according to [Sidetree §11.3
    /// Recover][recover]. Returns the recover operation.
    ///
    /// [recover]: https://identity.foundation/sidetree/spec/v1.0.0/#recover
    fn recover(
        did_suffix: DIDSuffix,
        recovery_key: &JWK,
        patches: Vec<DIDStatePatch>,
    ) -> Result<(Operation, JWK, JWK), RecoverError> {
        let new_update_keypair = Self::generate_key();
        let new_update_pk = PublicKeyJwk::try_from(new_update_keypair.to_public()).unwrap();

        let new_recovery_keypair = Self::generate_key();
        let new_recovery_pk = PublicKeyJwk::try_from(new_recovery_keypair.to_public()).unwrap();

        let recover_op = Self::recover_existing(
            did_suffix,
            recovery_key,
            &new_update_pk,
            &new_recovery_pk,
            patches,
        )?;

        Ok((recover_op, new_update_keypair, new_recovery_keypair))
    }

    /// Deactivate a Sidetree-based DID
    ///
    /// Construct a Deactivate Operation according to [Sidetree §11.4
    /// Deactivate][deactivate]. Returns the deactivate operation.
    ///
    /// [deactivate]: https://identity.foundation/sidetree/spec/v1.0.0/#deactivate
    fn deactivate(
        did_suffix: DIDSuffix,
        recovery_key: JWK,
    ) -> Result<DeactivateOperation, DeactivateError> {
        let recovery_pk = PublicKeyJwk::try_from(recovery_key.to_public())
            .map_err(|_| DeactivateError::InvalidRecoveryKey)?;
        let canonicalized_recovery_pk = json_canonicalization_scheme(&recovery_pk).unwrap();
        let recover_reveal_value = Self::reveal_value(canonicalized_recovery_pk.as_bytes());
        let algorithm = Self::SIGNATURE_ALGORITHM;
        let claims = DeactivateClaims {
            did_suffix: did_suffix.clone(),
            recovery_key: recovery_pk,
        };
        let signed_data = ssi_jwt::encode_sign(algorithm, &claims, &recovery_key)
            .map_err(|_| DeactivateError::SignatureFailed)?;
        let recover_op = DeactivateOperation {
            did_suffix,
            reveal_value: recover_reveal_value,
            signed_data,
        };
        Ok(recover_op)
    }

    /// Serialize and hash [Suffix Data][SuffixData], to generate a [Short-Form Sidetree
    /// DID][SidetreeDID::Short] ([`DIDSuffix`]).
    ///
    /// Reference: <https://identity.foundation/sidetree/spec/v1.0.0/#did-uri-composition>
    fn serialize_suffix_data(suffix_data: &SuffixData) -> DIDSuffix {
        let string = json_canonicalization_scheme(suffix_data).unwrap();
        let hash = Self::hash(string.as_bytes());
        DIDSuffix(hash)
    }

    /// Check that a DID Suffix looks valid
    fn validate_did_suffix(suffix: &DIDSuffix) -> Result<(), InvalidSidetreeDIDSuffix> {
        let bytes = base64::prelude::BASE64_URL_SAFE_NO_PAD
            .decode(&suffix.0)
            .map_err(|_| InvalidSidetreeDIDSuffix::Base64)?;

        if bytes.len() != 34 {
            return Err(InvalidSidetreeDIDSuffix::Length(bytes.len()));
        }

        if &bytes[0..1] != MULTIHASH_SHA2_256_PREFIX || &bytes[1..2] != MULTIHASH_SHA2_256_SIZE {
            return Err(InvalidSidetreeDIDSuffix::Prefix);
        }

        Ok(())
    }
}

/// [`JSON_CANONICALIZATION_SCHEME`](https://identity.foundation/sidetree/spec/v1.0.0/#json-canonicalization-scheme)
fn json_canonicalization_scheme<T: Serialize + ?Sized>(
    value: &T,
) -> Result<String, serde_json::Error> {
    serde_jcs::to_string(value)
}

#[derive(Debug, thiserror::Error)]
pub enum InvalidSidetreeDIDSuffix {
    #[error("invalid base64")]
    Base64,

    #[error("unexpected DID suffix length ({0})")]
    Length(usize),

    #[error("unexpected DID suffix prefix")]
    Prefix,
}

/// Public key as JWK or Multibase
///
/// Property of a public key / verification method containing public key data,
/// as part of a [PublicKeyEntry][].
///
/// per [Sidetree §12.1.1 `add-public-keys`: Step 4][apk].
///
/// [apk]: https://identity.foundation/sidetree/spec/v1.0.0/#add-public-keys
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub enum PublicKey {
    /// [`publicKeyJwk`](https://www.w3.org/TR/did-core/#dfn-publickeyjwk) as defined in DID Core.
    ///
    /// JSON Web Key (JWK) is specified in [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517).
    PublicKeyJwk(PublicKeyJwk),

    /// [`publicKeyMultibase`](https://www.w3.org/TR/did-core/#dfn-publickeymultibase) as defined in DID Core.
    ///
    /// Maximum length may be set in [Sidetree::MAX_PKMB_LENGTH].
    PublicKeyMultibase(String),
}

/// Public Key Entry
///
/// Used by the [`add-public-keys`](DIDStatePatch::AddPublicKeys) and
/// [`replace`](DIDStatePatch::Replace) DID state patch actions.
///
/// Specified in [Sidetree §12.1.1 `add-public-keys`][apk].
///
/// [apk]: https://identity.foundation/sidetree/spec/v1.0.0/#add-public-keys
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyEntry {
    /// `id` property
    ///
    /// Maximum length: 50 in Base64url
    pub id: String,

    /// Verification method type
    pub r#type: String,

    /// Verification method controller (DID)
    ///
    /// Maximum length may be set in [Sidetree::MAX_CONTROLLER_LENGTH].
    #[serde(skip_serializing_if = "Option::is_none")]
    pub controller: Option<String>,

    /// `publicKeyJwk` or `publicKeyMultibase` property
    #[serde(flatten)]
    pub public_key: PublicKey,

    /// Verification relationships
    ///
    /// Defined in [DID Core](https://www.w3.org/TR/did-core/#verification-relationships).
    ///
    /// Corresponds to [`proofPurpose`](https://www.w3.org/TR/did-core/#verification-relationships) in VC Data Model.
    pub purposes: Vec<ProofPurpose>,
}

#[derive(Debug, thiserror::Error)]
#[error("invalid public key entry")]
pub struct InvalidPublicKeyEntry(pub JWK);

impl TryFrom<JWK> for PublicKeyEntry {
    type Error = InvalidPublicKeyEntry;

    fn try_from(jwk: JWK) -> Result<Self, Self::Error> {
        let Ok(id) = jwk.thumbprint() else {
            return Err(InvalidPublicKeyEntry(jwk));
        };

        let Ok(pkjwk) = PublicKeyJwk::try_from(jwk.to_public()) else {
            return Err(InvalidPublicKeyEntry(jwk));
        };

        let public_key = PublicKey::PublicKeyJwk(pkjwk);
        Ok(PublicKeyEntry {
            id,
            r#type: VERIFICATION_METHOD_TYPE.to_owned(),
            controller: None,
            public_key,
            purposes: vec![
                ProofPurpose::Assertion,
                ProofPurpose::Authentication,
                ProofPurpose::KeyAgreement,
                ProofPurpose::CapabilityInvocation,
                ProofPurpose::CapabilityDelegation,
            ],
        })
    }
}

/// Service Endpoint Entry
///
/// Used by the [`add-services`](DIDStatePatch::AddServices) and
/// [`replace`](DIDStatePatch::Replace) DID state patch actions.
///
/// Specified in [Sidetree §12.1.3 `add-services`][as].
///
/// [as]: https://identity.foundation/sidetree/spec/v1.0.0/#add-services
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ServiceEndpointEntry {
    /// `id` property
    ///
    /// Maximum length: 50 in Base64Url
    pub id: String,

    /// Service type
    ///
    /// Maximum length: 30 in Base64Url
    pub r#type: String,

    /// Service endpoint URL or object
    pub service_endpoint: ServiceEndpoint,
}

/// DID PKI metadata state
///
/// Used by the [`replace`](DIDStatePatch::Replace) DID state patch.
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct DocumentState {
    /// Public key entries
    #[serde(skip_serializing_if = "Option::is_none")]
    pub public_keys: Option<Vec<PublicKeyEntry>>,

    /// Services
    #[serde(skip_serializing_if = "Option::is_none")]
    pub services: Option<Vec<ServiceEndpointEntry>>,
}

/// [DID State Patch][dsp] using a [Sidetree Standard Patch action][spa]
///
/// [dsp]: https://identity.foundation/sidetree/spec/v1.0.0/#did-state-patches
/// [spa]: https://identity.foundation/sidetree/spec/v1.0.0/#standard-patch-actions
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "action")]
#[serde(rename_all = "kebab-case")]
pub enum DIDStatePatch {
    /// [`add-public-keys`][apk] Patch Action
    ///
    /// [apk]: https://identity.foundation/sidetree/spec/v1.0.0/#add-public-keys
    AddPublicKeys {
        /// Keys to add or over overwrite
        #[serde(rename = "publicKeys")]
        public_keys: Vec<PublicKeyEntry>,
    },

    /// [`remove-public-keys`][rpk] Patch Action
    ///
    /// [rpk]: https://identity.foundation/sidetree/spec/v1.0.0/#remove-public-keys
    RemovePublicKeys {
        /// IDs of keys to remove
        ids: Vec<String>,
    },

    /// [`add-services`][as] Patch Action
    ///
    /// [as]: https://identity.foundation/sidetree/spec/v1.0.0/#add-services
    AddServices {
        /// Service entries to add
        services: Vec<ServiceEndpointEntry>,
    },

    /// [`remove-services`][rs] Patch Action
    ///
    /// [rs]: https://identity.foundation/sidetree/spec/v1.0.0/#remove-services
    RemoveServices {
        /// IDs of service endpoints to remove
        ids: Vec<String>,
    },

    /// [`replace`][r] Patch Action
    ///
    /// [r]: https://identity.foundation/sidetree/spec/v1.0.0/#replace
    Replace {
        /// Reset DID state
        document: DocumentState,
    },

    /// [`ietf-json-patch`][ijp] Patch Action
    ///
    /// [ijp]: https://identity.foundation/sidetree/spec/v1.0.0/#ietf-json-patch
    ///
    IetfJsonPatch {
        /// JSON Patches according to [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902).
        patches: Patch,
    },
}

/// Create/Update/Recover Delta Object
///
/// ### References
/// - [Sidetree §11.1 Create - Create Operation Delta Object][codo]
/// - [Sidetree §11.2 Update - Update Operation Delta Object][uodo]
/// - [Sidetree §11.3 Recover - Recover Operation Delta Object][rodo]
///
/// [codo]: https://identity.foundation/sidetree/spec/v1.0.0/#create-delta-object
/// [uodo]: https://identity.foundation/sidetree/spec/v1.0.0/#update-delta-object
/// [rodo]: https://identity.foundation/sidetree/spec/v1.0.0/#recover-delta-object
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Delta {
    /// DID state patches to apply.
    pub patches: Vec<DIDStatePatch>,

    /// Update commitment generated as part of a Sidetree Create or Update operation.
    pub update_commitment: String,
}

/// Public Key JWK (JSON Web Key)
///
/// Wraps [ssi_jwk::JWK], while allowing a `nonce` property, and disallowing private key
/// properties ("d").
///
/// Sidetree may allow a `nonce` property in public key JWKs ([§6.2.2 JWK Nonce][jwkn]).
///
/// [jwkn]: https://identity.foundation/sidetree/spec/#jwk-nonce
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyJwk {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub nonce: Option<Base64urlUInt>,
    #[serde(flatten)]
    jwk: serde_json::Value,
}

/// Error resulting from [converting JWK to PublicKeyJwk][PublicKeyJwk::try_from]
#[derive(thiserror::Error, Debug)]
pub enum PublicKeyJwkFromJWKError {
    /// Public Key JWK must not contain private key parameters (e.g. "d")
    #[error("Public Key JWK must not contain private key parameters")]
    PrivateKeyParameters,
}

/// Error resulting from attempting to convert [PublicKeyJwk] to JWK
#[derive(thiserror::Error, Debug)]
pub enum JWKFromPublicKeyJwkError {
    /// Unable to convert [`serde_json::Value`] to JWK
    #[error("Unable to convert Value to JWK")]
    FromValue(#[from] serde_json::Error),
}

impl TryFrom<JWK> for PublicKeyJwk {
    type Error = PublicKeyJwkFromJWKError;
    fn try_from(jwk: JWK) -> Result<Self, Self::Error> {
        let jwk_value = serde_json::to_value(jwk).unwrap();
        if jwk_value.get("d").is_some() {
            return Err(PublicKeyJwkFromJWKError::PrivateKeyParameters);
        };
        Ok(Self {
            jwk: jwk_value,
            nonce: None,
        })
    }
}

/// Convert [PublicKeyJwk] to [JWK].
///
/// Note: `nonce` property is dropped.
impl TryFrom<PublicKeyJwk> for JWK {
    type Error = JWKFromPublicKeyJwkError;
    fn try_from(pkjwk: PublicKeyJwk) -> Result<Self, Self::Error> {
        let jwk = serde_json::from_value(pkjwk.jwk).map_err(JWKFromPublicKeyJwkError::FromValue)?;
        Ok(jwk)
    }
}

fn b64len(s: &str) -> usize {
    base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(s).len()
}

impl DIDStatePatch {
    /// Convert a [DID Document Operation][ddo] and DID to a Sidetree [DID State Patch][dsp].
    ///
    /// [ddp]: https://identity.foundation/did-registration/#diddocumentoperation
    /// [dsp]: https://identity.foundation/sidetree/spec/v1.0.0/#did-state-patches
    fn try_from_with_did<S: Sidetree>(
        did_doc_op: DIDDocumentOperation,
        did: &SidetreeDID<S>,
    ) -> Result<Self, DIDTransactionCreationError> {
        match did_doc_op {
            DIDDocumentOperation::SetDidDocument(_doc) => {
                Err(DIDTransactionCreationError::UnimplementedDocumentOperation(
                    DIDDocumentOperationKind::SetDidDocument,
                ))
            }
            DIDDocumentOperation::AddToDidDocument(_props) => {
                Err(DIDTransactionCreationError::UnimplementedDocumentOperation(
                    DIDDocumentOperationKind::AddToDidDocument,
                ))
            }
            DIDDocumentOperation::RemoveFromDidDocument(_props) => {
                Err(DIDTransactionCreationError::UnimplementedDocumentOperation(
                    DIDDocumentOperationKind::RemoveFromDidDocument,
                ))
            }
            DIDDocumentOperation::SetVerificationMethod { vmm, purposes } => {
                let sub_id = did_url_to_id(&vmm.id, did)?;
                let mut value = serde_json::to_value(vmm).unwrap();
                value["id"] = serde_json::Value::String(sub_id);
                value["purposes"] = serde_json::to_value(purposes).unwrap();
                let entry: PublicKeyEntry = serde_json::from_value(value)
                    .map_err(|_| DIDTransactionCreationError::InvalidVerificationMethod)?;
                // TODO: allow omitted controller property
                Ok(DIDStatePatch::AddPublicKeys {
                    public_keys: vec![entry],
                })
            }
            DIDDocumentOperation::SetService(service) => {
                let Service {
                    id,
                    type_,
                    service_endpoint,
                    property_set,
                } = service;

                if !property_set.is_empty() {
                    return Err(DIDTransactionCreationError::UnsupportedServiceProperty);
                }

                let service_endpoint = match service_endpoint {
                    None => return Err(DIDTransactionCreationError::MissingServiceEndpoint),
                    Some(values) => match values.into_single() {
                        Some(value) => value,
                        None => return Err(DIDTransactionCreationError::AmbiguousServiceEndpoint),
                    },
                };

                let sub_id = did_url_to_id(&id, did)?;
                let service_type = match type_.into_single() {
                    Some(type_) => type_,
                    None => return Err(DIDTransactionCreationError::AmbiguousServiceType),
                };

                if b64len(&service_type) > 30 {
                    return Err(DIDTransactionCreationError::UnsupportedService {
                        reason: Cow::Borrowed("Sidetree service type must contain no more than 30 Base64Url-encoded characters")
                    });
                }

                if b64len(&sub_id) > 50 {
                    return Err(DIDTransactionCreationError::UnsupportedService {
                        reason: Cow::Borrowed("Sidetree service id must contain no more than 50 Base64Url-encoded characters")
                    });
                }

                let entry = ServiceEndpointEntry {
                    id: sub_id,
                    r#type: service_type,
                    service_endpoint,
                };

                Ok(DIDStatePatch::AddServices {
                    services: vec![entry],
                })
            }
            DIDDocumentOperation::RemoveVerificationMethod(did_url) => {
                let id = did_url.to_string();
                Ok(DIDStatePatch::RemovePublicKeys { ids: vec![id] })
            }
            DIDDocumentOperation::RemoveService(did_url) => {
                let id = did_url.to_string();
                Ok(DIDStatePatch::RemoveServices { ids: vec![id] })
            }
        }
    }
}

/// Convert a DID URL to an object id given a DID
///
/// Object id is an id of a [ServiceEndpointEntry] or [PublicKeyEntry].
fn did_url_to_id<S: Sidetree>(
    did_url: &str,
    did: &SidetreeDID<S>,
) -> Result<String, DIDTransactionCreationError> {
    let did_string = did.to_string();
    let unprefixed = did_url
        .strip_prefix(&did_string)
        .ok_or(DIDTransactionCreationError::InvalidDIDURL)?;
    let fragment = unprefixed
        .strip_prefix('#')
        .ok_or(DIDTransactionCreationError::InvalidDIDURL)?;
    Ok(fragment.to_string())
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SidetreeAPIError {
    // List of error codes: https://github.com/decentralized-identity/sidetree/blob/v1.0.0/lib/core/versions/1.0/ErrorCode.ts
    pub code: String,
    pub message: Option<String>,
}

impl fmt::Display for SidetreeAPIError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Sidetree error {}", self.code)?;
        if let Some(ref message) = self.message {
            write!(f, ": {}", message)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use crate::ion::is_secp256k1;

    use super::*;
    use serde_json::json;
    use ssi_jwk::Algorithm;

    struct Example;

    impl Sidetree for Example {
        fn generate_key() -> JWK {
            JWK::generate_secp256k1()
        }
        fn validate_key(key: &JWK) -> bool {
            is_secp256k1(key)
        }
        const SIGNATURE_ALGORITHM: Algorithm = Algorithm::ES256K;
        const METHOD: &'static str = "sidetree";
    }

    /// <https://identity.foundation/sidetree/spec/v1.0.0/#did>
    static LONGFORM_DID: &str = "did:sidetree:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJwdWJsaWNLZXlNb2RlbDFJZCIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0WFNLQl9ydWJYUzdzQ2pYcXVwVkpFelRjVzNNc2ptRXZxMVlwWG45NlpnIiwieSI6ImRPaWNYcWJqRnhvR0otSzAtR0oxa0hZSnFpY19EX09NdVV3a1E3T2w2bmsifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iLCJrZXlBZ3JlZW1lbnQiXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoic2VydmljZTFJZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly93d3cuc2VydmljZTEuY29tIiwidHlwZSI6InNlcnZpY2UxVHlwZSJ9XX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpREtJa3dxTzY5SVBHM3BPbEhrZGI4Nm5ZdDBhTnhTSFp1MnItYmhFem5qZEEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNmRFdSbllsY0Q5RUdBM2RfNVoxQUh1LWlZcU1iSjluZmlxZHo1UzhWRGJnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCZk9aZE10VTZPQnc4UGs4NzlRdFotMkotOUZiYmpTWnlvYUFfYnFENHpoQSJ9fQ";
    static SHORTFORM_DID: &str = "did:sidetree:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg";

    lazy_static::lazy_static! {

        /// <https://identity.foundation/sidetree/spec/v1.0.0/#create-2>
        static ref CREATE_OPERATION: Operation = serde_json::from_value(json!({
          "type": "create",
          "suffixData": {
            "deltaHash": "EiCfDWRnYlcD9EGA3d_5Z1AHu-iYqMbJ9nfiqdz5S8VDbg",
            "recoveryCommitment": "EiBfOZdMtU6OBw8Pk879QtZ-2J-9FbbjSZyoaA_bqD4zhA"
          },
          "delta": {
            "updateCommitment": "EiDKIkwqO69IPG3pOlHkdb86nYt0aNxSHZu2r-bhEznjdA",
            "patches": [
              {
                "action": "replace",
                "document": {
                  "publicKeys": [
                    {
                      "id": "publicKeyModel1Id",
                      "type": "EcdsaSecp256k1VerificationKey2019",
                      "publicKeyJwk": {
                        "kty": "EC",
                        "crv": "secp256k1",
                        "x": "tXSKB_rubXS7sCjXqupVJEzTcW3MsjmEvq1YpXn96Zg",
                        "y": "dOicXqbjFxoGJ-K0-GJ1kHYJqic_D_OMuUwkQ7Ol6nk"
                      },
                      "purposes": [
                        "authentication",
                        "keyAgreement"
                      ]
                    }
                  ],
                  "services": [
                    {
                      "id": "service1Id",
                      "type": "service1Type",
                      "serviceEndpoint": "http://www.service1.com"
                    }
                  ]
                }
              }
            ]
          }
        })).unwrap();

        /// <https://identity.foundation/sidetree/spec/v1.0.0/#update-2>
        static ref UPDATE_OPERATION: Operation = serde_json::from_value(json!({
          "type": "update",
          "didSuffix": "EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg",
          "revealValue": "EiBkRSeixqX-PhOij6PIpuGfPld5Nif5MxcrgtGCw-t6LA",
          "delta": {
            "patches": [
              {
                "action": "add-public-keys",
                "publicKeys": [
                  {
                    "id": "additional-key",
                    "type": "EcdsaSecp256k1VerificationKey2019",
                    "publicKeyJwk": {
                      "kty": "EC",
                      "crv": "secp256k1",
                      "x": "aN75CTjy3VCgGAJDNJHbcb55hO8CobEKzgCNrUeOwAY",
                      "y": "K9FhCEpa_jG09pB6qriXrgSvKzXm6xtxBvZzIoXXWm4"
                    },
                    "purposes": [
                      "authentication",
                      "assertionMethod",
                      "capabilityInvocation",
                      "capabilityDelegation",
                      "keyAgreement"
                    ]
                  }
                ]
              }
            ],
            "updateCommitment": "EiDOrcmPtfMHuwIWN6YoihdeIPxOKDHy3D6sdMXu_7CN0w"
          },
          "signedData": "eyJhbGciOiJFUzI1NksifQ.eyJ1cGRhdGVLZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJzZWNwMjU2azEiLCJ4Ijoid2Z3UUNKM09ScVZkbkhYa1Q4UC1MZ19HdHhCRWhYM3R5OU5VbnduSHJtdyIsInkiOiJ1aWU4cUxfVnVBblJEZHVwaFp1eExPNnFUOWtQcDNLUkdFSVJsVHBXcmZVIn0sImRlbHRhSGFzaCI6IkVpQ3BqTjQ3ZjBNcTZ4RE5VS240aFNlZ01FcW9EU19ycFEyOVd5MVY3M1ZEYncifQ.RwZK1DG5zcr4EsrRImzStb0VX5j2ZqApXZnuoAkA3IoRdErUscNG8RuxNZ0FjlJtjMJ0a-kn-_MdtR0wwvWVgg"
        })).unwrap();

        /// <https://identity.foundation/sidetree/spec/v1.0.0/#recover-2>
        static ref RECOVER_OPERATION: Operation = serde_json::from_value(json!({
          "type": "recover",
          "didSuffix": "EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg",
          "revealValue": "EiAJ-97Is59is6FKAProwDo870nmwCeP8n5nRRFwPpUZVQ",
          "signedData": "eyJhbGciOiJFUzI1NksifQ.eyJkZWx0YUhhc2giOiJFaUNTem1ZSk0yWGpaWE00a1Q0bGpKcEVGTjVmVkM1QVNWZ3hSekVtMEF2OWp3IiwicmVjb3ZlcnlLZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJzZWNwMjU2azEiLCJ4IjoibklxbFJDeDBleUJTWGNRbnFEcFJlU3Y0enVXaHdDUldzc29jOUxfbmo2QSIsInkiOiJpRzI5Vks2bDJVNXNLQlpVU0plUHZ5RnVzWGdTbEsyZERGbFdhQ004RjdrIn0sInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQ3NBN1NHTE5lZGE1SW5sb3Fub2tVY0pGejZ2S1Q0SFM1ZGNLcm1ubEpocEEifQ.lxWnrg5jaeCAhYuz1fPhidKw6Z2cScNlEc6SWcs15DtJbrHZFxl5IezGJ3cWdOSS2DlzDl4M1ZF8dDE9kRwFeQ",
          "delta": {
            "patches": [
              {
                "action": "replace",
                "document": {
                  "publicKeys": [
                    {
                      "id": "newKey",
                      "type": "EcdsaSecp256k1VerificationKey2019",
                      "publicKeyJwk": {
                        "kty": "EC",
                        "crv": "secp256k1",
                        "x": "JUWp0pAMGevNLhqq_Qmd48izuLYfO5XWpjSmy5btkjc",
                        "y": "QYaSu1NHYnxR4qfk-RkXb4NQnQf1X3XQCpDYuibvlNc"
                      },
                      "purposes": [
                        "authentication",
                        "assertionMethod",
                        "capabilityInvocation",
                        "capabilityDelegation",
                        "keyAgreement"
                      ]
                    }
                  ],
                  "services": [
                    {
                      "id": "serviceId123",
                      "type": "someType",
                      "serviceEndpoint": "https://www.url.com"
                    }
                  ]
                }
              }
            ],
            "updateCommitment": "EiD6_csybTfxELBoMgkE9O2BTCmhScG_RW_qaZQkIkJ_aQ"
          }
        })).unwrap();

        /// <https://identity.foundation/sidetree/spec/v1.0.0/#deactivate-2>
        static ref DEACTIVATE_OPERATION: Operation = serde_json::from_value(json!({
          "type": "deactivate",
          "didSuffix": "EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg",
          "revealValue": "EiB-dib5oumdaDGH47TB17Qg1nHza036bTIGibQOKFUY2A",
          "signedData": "eyJhbGciOiJFUzI1NksifQ.eyJkaWRTdWZmaXgiOiJFaUR5T1FiYlpBYTNhaVJ6ZUNrVjdMT3gzU0VSampIOTNFWG9JTTNVb040b1dnIiwicmVjb3ZlcnlLZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJzZWNwMjU2azEiLCJ4IjoiSk1ucF9KOW5BSGFkTGpJNmJfNVU3M1VwSEZqSEZTVHdtc1ZUUG9FTTVsMCIsInkiOiJ3c1QxLXN0UWJvSldPeEJyUnVINHQwVV9zX1lSQy14WXQyRkFEVUNHR2M4In19.ARTZrvupKdShOFNAJ4EWnsuaONKBgXUiwY5Ct10a9IXIp1uFsg0UyDnZGZtJT2v2bgtmYsQBmT6L9kKaaDcvUQ"
        })).unwrap();
    }

    #[test]
    fn test_did_parse_format() {
        let longform_did = SidetreeDID::<Example>::from_str(LONGFORM_DID).unwrap();
        let shortform_did = SidetreeDID::<Example>::from_str(SHORTFORM_DID).unwrap();
        assert_eq!(longform_did.to_string(), LONGFORM_DID);
        assert_eq!(shortform_did.to_string(), SHORTFORM_DID);
        assert!(LONGFORM_DID.starts_with(SHORTFORM_DID));
    }

    #[test]
    fn test_longform_did_construction() {
        let create_operation = match &*CREATE_OPERATION {
            Operation::Create(op) => op,
            _ => panic!("Expected Create Operation"),
        };
        let did: SidetreeDID<Example> = create_operation.to_sidetree_did();
        assert_eq!(did.to_string(), LONGFORM_DID);
    }

    #[test]
    fn test_update_verify_reveal() {
        let create_pvo = CREATE_OPERATION
            .clone()
            .partial_verify::<Example>()
            .unwrap();
        let update_pvo = UPDATE_OPERATION
            .clone()
            .partial_verify::<Example>()
            .unwrap();
        update_pvo.follows::<Example>(&create_pvo).unwrap();
    }

    #[test]
    fn test_recover_verify_reveal() {
        let create_pvo = CREATE_OPERATION
            .clone()
            .partial_verify::<Example>()
            .unwrap();
        let recover_pvo = RECOVER_OPERATION
            .clone()
            .partial_verify::<Example>()
            .unwrap();
        recover_pvo.follows::<Example>(&create_pvo).unwrap();
    }

    #[test]
    fn test_deactivate_verify_reveal() {
        let recover_pvo = RECOVER_OPERATION
            .clone()
            .partial_verify::<Example>()
            .unwrap();
        let deactivate_pvo = DEACTIVATE_OPERATION
            .clone()
            .partial_verify::<Example>()
            .unwrap();
        deactivate_pvo.follows::<Example>(&recover_pvo).unwrap();
    }
}
