use crate::api::{ApiError, PseudonymServiceErrorHandler};
use libpep::data::simple::{EncryptedPseudonym, Pseudonym};
use libpep::factors::PseudonymizationDomain;
use paas_client::pseudonym_service::PseudonymService;
use paas_client::sessions::EncryptionContexts;
use rand::{CryptoRng, Rng};
use serde::{Deserialize, Serialize};

/// Encrypted information about a participant that still needs to be transcrypted.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PEPParticipantInfo {
    /// An encrypted pseudonym for the participant.
    pub encrypted_pseudonym: EncryptedPseudonym,
    /// The sessions in which the pseudonym was encrypted.
    pub sessions: EncryptionContexts,
    /// The domain of the pseudonym before encryption.
    pub domain_from: PseudonymizationDomain,
}

/// Information about a participant within the DataSource.
#[derive(Debug, Clone)]
pub struct ParticipantInfo {
    /// A pseudonym for the participant existing in the domain.
    pub pseudonym: Pseudonym,
    /// The domain of the pseudonym.
    /// This often maps one-to-one with the DataSource itself (each data source has its own domain),
    /// but in some cases, a data source may have multiple domains.
    /// In this case, the data source should treat the participant in every domain as a separate entity.
    pub domain: PseudonymizationDomain,
}

impl PEPParticipantInfo {
    /// Decrypt [PEPParticipantInfo] into [ParticipantInfo] in a specific [PseudonymizationDomain], using a [PseudonymService].
    pub async fn decrypt(
        &self,
        ps: &mut PseudonymService,
        domain_to: PseudonymizationDomain,
    ) -> Result<ParticipantInfo, ApiError> {
        let transcrypted = ps
            .pseudonymize(
                &self.encrypted_pseudonym,
                &self.sessions,
                &self.domain_from,
                &domain_to,
            )
            .await
            .handle_pseudonym_error()?;
        let pseudonym = ps.decrypt(&transcrypted).handle_pseudonym_error()?;
        Ok(ParticipantInfo {
            domain: domain_to,
            pseudonym,
        })
    }
}

impl ParticipantInfo {
    /// Encrypt [ParticipantInfo] into [PEPParticipantInfo] using a [PseudonymService].
    pub async fn encrypt<R: Rng + CryptoRng>(
        &self,
        ps: &mut PseudonymService,
        mut rng: R,
    ) -> Result<PEPParticipantInfo, ApiError> {
        let (encrypted_pseudonym, sessions) = ps
            .encrypt(&self.pseudonym, &mut rng)
            .handle_pseudonym_error()?;
        Ok(PEPParticipantInfo {
            encrypted_pseudonym,
            sessions,
            domain_from: self.domain.clone(),
        })
    }
}
