/*!
 * # Decentralized Identifiers (DIDs)
 *
 * Various helper functions for working with DIDs.
 */

#[cfg(feature = "did-peer")]
use affinidi_did_common::one_or_many::OneOrMany;
use affinidi_did_key::DIDKey;
use affinidi_secrets_resolver::secrets::{KeyType as SecretsKeyType, Secret};
use affinidi_tdk_common::errors::{Result, TDKError};
#[cfg(feature = "did-peer")]
use did_peer::{
    DIDPeer, DIDPeerCreateKeys, DIDPeerKeys, DIDPeerService, PeerServiceEndPoint,
    PeerServiceEndPointLong, PeerServiceEndPointLongMap,
};
use std::fmt::Display;

/// Supported DID Methods
pub enum DIDMethod {
    /// did:key - Simple single key method
    Key,

    /// did:peer - Allows for multiple keys and services
    Peer,

    /// did:web - Allows for a DID to be resolved to a URL
    Web,
}

/// Supported Key types
#[derive(Debug)]
pub enum KeyType {
    /// P256 - NIST P-256 curve
    P256,

    /// P384 - NIST P-384 curve
    P384,

    /// Ed25519 - EdDSA over the edwards25519 curve
    Ed25519,

    /// Secp256k1 - ECDSA over the secp256k1 curve
    Secp256k1,
}

impl Display for KeyType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KeyType::P256 => write!(f, "P-256"),
            KeyType::P384 => write!(f, "P-384"),
            KeyType::Ed25519 => write!(f, "Ed25519"),
            KeyType::Secp256k1 => write!(f, "secp256k1"),
        }
    }
}

impl TryFrom<&str> for KeyType {
    type Error = String;

    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
        match value.to_lowercase().as_str() {
            "p-256" => Ok(KeyType::P256),
            "p-384" => Ok(KeyType::P384),
            "ed25519" => Ok(KeyType::Ed25519),
            "secp256k1" => Ok(KeyType::Secp256k1),
            _ => Err(format!("Unsupported key type: {}", value)),
        }
    }
}

pub struct DID;

impl DID {
    /// Generate a new DID:key
    /// Returns the DID and the associated secret
    pub fn generate_did_key(key_type: KeyType) -> Result<(String, Secret)> {
        match key_type {
            KeyType::P256 => DIDKey::generate(SecretsKeyType::P256)
                .map_err(|e| TDKError::DIDMethod(format!("Couldn't create P256 did:key  : {}", e))),
            KeyType::P384 => DIDKey::generate(SecretsKeyType::P384)
                .map_err(|e| TDKError::DIDMethod(format!("Couldn't create P384 did:key  : {}", e))),
            KeyType::Ed25519 => DIDKey::generate(SecretsKeyType::Ed25519).map_err(|e| {
                TDKError::DIDMethod(format!("Couldn't create Ed25519 did:key  : {}", e))
            }),
            KeyType::Secp256k1 => DIDKey::generate(SecretsKeyType::Secp256k1).map_err(|e| {
                TDKError::DIDMethod(format!("Couldn't create Secp256k1 did:key  : {}", e))
            }),
        }
    }

    #[cfg(feature = "did-peer")]
    /// Generate a new DID:peer
    pub fn generate_did_peer(
        keys: Vec<(DIDPeerKeys, KeyType)>,
        didcomm_service_uri: Option<String>,
    ) -> Result<(String, Vec<Secret>)> {
        let mut peer_keys: Vec<DIDPeerCreateKeys> = Vec::new();
        let mut secrets: Vec<Secret> = Vec::new();
        for key in keys {
            let (did, secret) = Self::generate_did_key(key.1)?;
            peer_keys.push(DIDPeerCreateKeys {
                purpose: key.0,
                type_: None,
                public_key_multibase: Some(did[8..].to_string()),
            });
            secrets.push(secret);
        }

        let services = didcomm_service_uri.map(|service_uri| {
            vec![DIDPeerService {
                _type: "dm".into(),
                service_end_point: PeerServiceEndPoint::Long(PeerServiceEndPointLong::Map(
                    OneOrMany::One(PeerServiceEndPointLongMap {
                        uri: service_uri,
                        accept: vec!["didcomm/v2".into()],
                        routing_keys: vec![],
                    }),
                )),
                id: None,
            }]
        });

        let peer = DIDPeer::create_peer_did(&peer_keys, services.as_ref())?;

        for (id, secret) in secrets.iter_mut().enumerate() {
            secret.id = [&peer.0, "#key-", (id + 1).to_string().as_str()].concat();
        }

        Ok((peer.0, secrets))
    }
}
