use crate::api::ApiError;
use crate::participant_info::{PEPParticipantInfo, ParticipantInfo};
use libpep::factors::PseudonymizationDomain;
use paas_client::pseudonym_service::PseudonymService;
use rand::{CryptoRng, Rng};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Debug;

pub trait HasParticipantInfo {
    fn participant_info(&self) -> &ParticipantInfo;
}

pub trait HasPEPParticipantInfo {
    fn pep_participant_info(&self) -> &PEPParticipantInfo;
    fn domain_to(&self) -> &PseudonymizationDomain;
}

pub trait PEPMessageType: Debug {
    type PEPMessage: HasPEPParticipantInfo + Serialize + Debug;
    type Message: HasParticipantInfo + Debug;
    #[allow(async_fn_in_trait)]
    async fn pack<R: Rng + CryptoRng>(
        request: Self::Message,
        domain_to: PseudonymizationDomain,
        ps: &mut PseudonymService,
        rng: &mut R,
    ) -> Result<Self::PEPMessage, ApiError>;

    #[allow(async_fn_in_trait)]
    async fn unpack(
        request: Self::PEPMessage,
        ps: &mut PseudonymService,
    ) -> Result<Self::Message, ApiError>;
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PEPActivityRequest {
    pub participant: PEPParticipantInfo,
    pub domain_to: PseudonymizationDomain, // We need to know in which domain to create the participant. It may often be 1-1 with the data source, but not always. It may also be 1-1 with the column, but not always.
    pub activity: String,
    pub extra_data: Value,
}

#[derive(Debug)]
pub struct ActivityRequest {
    pub participant: ParticipantInfo,
    pub activity: String,
    pub extra_data: Value,
}

#[derive(Debug)]
pub struct ActivityResponse {
    pub participant: ParticipantInfo,
    pub activity: String,
    pub extra_data: Value,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PEPActivityResponse {
    pub participant: PEPParticipantInfo,
    pub domain_to: PseudonymizationDomain,
    pub activity: String,
    pub extra_data: Value,
}

impl HasParticipantInfo for ActivityRequest {
    fn participant_info(&self) -> &ParticipantInfo {
        &self.participant
    }
}

impl HasPEPParticipantInfo for PEPActivityRequest {
    fn pep_participant_info(&self) -> &PEPParticipantInfo {
        &self.participant
    }

    fn domain_to(&self) -> &PseudonymizationDomain {
        &self.domain_to
    }
}

impl HasParticipantInfo for ActivityResponse {
    fn participant_info(&self) -> &ParticipantInfo {
        &self.participant
    }
}

impl HasPEPParticipantInfo for PEPActivityResponse {
    fn pep_participant_info(&self) -> &PEPParticipantInfo {
        &self.participant
    }

    fn domain_to(&self) -> &PseudonymizationDomain {
        &self.domain_to
    }
}

#[derive(Debug)]
pub struct ActivityRequestMessageType;

impl PEPMessageType for ActivityRequestMessageType {
    type PEPMessage = PEPActivityRequest;
    type Message = ActivityRequest;
    async fn pack<R: Rng + CryptoRng>(
        request: Self::Message,
        domain_to: PseudonymizationDomain,
        ps: &mut PseudonymService,
        rng: &mut R,
    ) -> Result<Self::PEPMessage, ApiError> {
        Ok(Self::PEPMessage {
            participant: request.participant.encrypt(ps, rng).await?,
            domain_to,
            activity: request.activity,
            extra_data: request.extra_data,
        })
    }
    async fn unpack(
        request: Self::PEPMessage,
        ps: &mut PseudonymService,
    ) -> Result<Self::Message, ApiError> {
        Ok(Self::Message {
            participant: request.participant.decrypt(ps, request.domain_to).await?,
            activity: request.activity,
            extra_data: request.extra_data,
        })
    }
}

#[derive(Debug)]
pub struct ActivityResponseMessageType;

impl PEPMessageType for ActivityResponseMessageType {
    type PEPMessage = PEPActivityResponse;
    type Message = ActivityResponse;
    async fn pack<R: Rng + CryptoRng>(
        request: Self::Message,
        domain_to: PseudonymizationDomain,
        ps: &mut PseudonymService,
        rng: &mut R,
    ) -> Result<Self::PEPMessage, ApiError> {
        Ok(Self::PEPMessage {
            participant: request.participant.encrypt(ps, rng).await?,
            domain_to,
            activity: request.activity,
            extra_data: request.extra_data,
        })
    }
    async fn unpack(
        request: Self::PEPMessage,
        ps: &mut PseudonymService,
    ) -> Result<Self::Message, ApiError> {
        Ok(Self::Message {
            participant: request.participant.decrypt(ps, request.domain_to).await?,
            activity: request.activity,
            extra_data: request.extra_data,
        })
    }
}
