#[macro_use]
extern crate anyhow;

use std::fs::File;
use std::io::Read;
use std::time::Duration;

use aes_kw::Kek;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use reqwest::header::{HeaderMap, AUTHORIZATION, CONTENT_TYPE};
use reqwest::{Certificate, Identity};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::Sender;
use tokio::sync::oneshot::Receiver;
use tracing::{debug, error, info, span, trace, Instrument, Level};

use chirpstack_api::stream;

const PROTOCOL_VERSION: &str = "1.0";

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Role {
    FNS,
    HNS,
    SNS,
}

pub trait BasePayloadProvider {
    fn base_payload(&self) -> &BasePayload;
}

pub trait BasePayloadResultProvider {
    fn base_payload(&self) -> &BasePayloadResult;
}

pub struct ClientConfig {
    pub sender_id: Vec<u8>,
    pub receiver_id: Vec<u8>,
    pub server: String,
    pub ca_cert: String,
    pub tls_cert: String,
    pub tls_key: String,

    // Contains the value for the Authorization header. This may
    // include a prefix, like Bearer, Key or Basic.
    pub authorization: Option<String>,

    // AsyncTimeout defines the async timeout. This must be set when RedisClient
    // is set.
    pub async_timeout: Duration,

    // Use target-role URL suffix (e.g. /fns, /sns, ...).
    pub use_target_role_suffix: bool,

    // Request log function.
    pub request_log_sender: Option<Sender<stream::BackendInterfacesRequest>>,
}

impl Default for ClientConfig {
    fn default() -> Self {
        ClientConfig {
            sender_id: vec![],
            receiver_id: vec![],
            server: "".into(),
            ca_cert: "".into(),
            tls_cert: "".into(),
            tls_key: "".into(),
            authorization: None,
            async_timeout: Duration::from_secs(0),
            use_target_role_suffix: false,
            request_log_sender: None,
        }
    }
}

pub struct Client {
    client: reqwest::Client,
    config: ClientConfig,
    headers: HeaderMap,
}

impl Client {
    pub fn new(c: ClientConfig) -> Result<Self> {
        let mut headers = HeaderMap::new();
        headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
        if let Some(auth) = &c.authorization {
            headers.insert(AUTHORIZATION, auth.clone().parse()?);
        }

        let mut client = reqwest::Client::builder()
            .timeout(std::time::Duration::from_secs(5))
            .use_rustls_tls(); // this is important as else the client-certificate does not work!

        if !c.tls_cert.is_empty() && !c.tls_key.is_empty() {
            trace!(tls_cert = %c.tls_cert, tls_key = %c.tls_key, "Reading client certificate");

            let mut b: Vec<u8> = Vec::new();
            File::open(&c.tls_key)
                .context("Open tls_key")?
                .read_to_end(&mut b)
                .context("Read tls_key")?;
            File::open(&c.tls_cert)
                .context("Open tls_cert")?
                .read_to_end(&mut b)
                .context("Read tls_cert")?;

            trace!("Parsing client certificate");
            let id = Identity::from_pem(&b).context("Parse tls_cert and tls_key")?;

            trace!("Adding client certificate as identity");
            client = client.identity(id);
        } else {
            trace!("No client certificate configured");
        }

        if !c.ca_cert.is_empty() {
            trace!(ca_cert = %c.ca_cert, "Reading CA certificate");
            let mut b: Vec<u8> = Vec::new();
            File::open(&c.ca_cert)
                .context("Open ca_cert")?
                .read_to_end(&mut b)
                .context("Read ca_cert")?;

            trace!("Parsing CA certificate");
            let cert = Certificate::from_pem(&b).context("Parse ca_cert")?;

            trace!("Adding CA certificate to root certificate bundle");
            client = client.add_root_certificate(cert);
        } else {
            trace!("No CA certificate configured");
        }

        Ok(Client {
            config: c,
            client: client.build()?,
            headers,
        })
    }

    pub fn get_sender_id(&self) -> Vec<u8> {
        self.config.sender_id.clone()
    }

    pub fn get_receiver_id(&self) -> Vec<u8> {
        self.config.receiver_id.clone()
    }

    pub fn is_async(&self) -> bool {
        !self.config.async_timeout.is_zero()
    }

    pub fn get_async_timeout(&self) -> Duration {
        self.config.async_timeout
    }

    pub async fn join_req(
        &self,
        receiver_id: Vec<u8>,
        pl: &mut JoinReqPayload,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<JoinAnsPayload> {
        pl.base.sender_id.clone_from(&self.config.sender_id);
        pl.base.receiver_id = receiver_id;
        pl.base.message_type = MessageType::JoinReq;

        let mut ans: JoinAnsPayload = Default::default();
        self.request(None, &pl, &mut ans, async_resp).await?;
        Ok(ans)
    }

    pub async fn rejoin_req(
        &self,
        pl: &mut RejoinReqPayload,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<RejoinAnsPayload> {
        pl.base.sender_id.clone_from(&self.config.sender_id);
        pl.base.receiver_id.clone_from(&self.config.receiver_id);
        pl.base.message_type = MessageType::RejoinReq;

        let mut ans: RejoinAnsPayload = Default::default();
        self.request(None, &pl, &mut ans, async_resp).await?;
        Ok(ans)
    }

    pub async fn app_s_key_req(
        &self,
        pl: &mut AppSKeyReqPayload,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<AppSKeyAnsPayload> {
        pl.base.sender_id.clone_from(&self.config.sender_id);
        pl.base.receiver_id.clone_from(&self.config.receiver_id);
        pl.base.message_type = MessageType::AppSKeyReq;

        let mut ans: AppSKeyAnsPayload = Default::default();
        self.request(None, &pl, &mut ans, async_resp).await?;
        Ok(ans)
    }

    pub async fn pr_start_req(
        &self,
        target_role: Role,
        pl: &mut PRStartReqPayload,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<PRStartAnsPayload> {
        pl.base.sender_id.clone_from(&self.config.sender_id);
        pl.base.receiver_id.clone_from(&self.config.receiver_id);
        pl.base.message_type = MessageType::PRStartReq;

        let mut ans: PRStartAnsPayload = Default::default();
        self.request(Some(target_role), &pl, &mut ans, async_resp)
            .await?;
        Ok(ans)
    }

    pub async fn pr_start_ans(&self, target_role: Role, pl: &PRStartAnsPayload) -> Result<()> {
        self.response_request(Some(target_role), pl).await
    }

    pub async fn pr_stop_req(
        &self,
        target_role: Role,
        pl: &mut PRStopReqPayload,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<PRStopAnsPayload> {
        pl.base.sender_id.clone_from(&self.config.sender_id);
        pl.base.receiver_id.clone_from(&self.config.receiver_id);
        pl.base.message_type = MessageType::PRStopReq;

        let mut ans: PRStopAnsPayload = Default::default();
        self.request(Some(target_role), &pl, &mut ans, async_resp)
            .await?;
        Ok(ans)
    }

    pub async fn pr_stop_ans(&self, target_role: Role, pl: &PRStopAnsPayload) -> Result<()> {
        self.response_request(Some(target_role), pl).await
    }

    pub async fn home_ns_req(
        &self,
        receiver_id: Vec<u8>,
        pl: &mut HomeNSReqPayload,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<HomeNSAnsPayload> {
        pl.base.sender_id.clone_from(&self.config.sender_id);
        pl.base.receiver_id = receiver_id;
        pl.base.message_type = MessageType::HomeNSReq;

        let mut ans: HomeNSAnsPayload = Default::default();
        self.request(None, &pl, &mut ans, async_resp).await?;
        Ok(ans)
    }

    pub async fn home_ns_ans(&self, target_role: Role, pl: &HomeNSAnsPayload) -> Result<()> {
        self.response_request(Some(target_role), pl).await
    }

    pub async fn xmit_data_req(
        &self,
        target_role: Role,
        pl: &mut XmitDataReqPayload,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<XmitDataAnsPayload> {
        pl.base.sender_id.clone_from(&self.config.sender_id);
        pl.base.receiver_id.clone_from(&self.config.receiver_id);
        pl.base.message_type = MessageType::XmitDataReq;

        let mut ans: XmitDataAnsPayload = Default::default();
        self.request(Some(target_role), &pl, &mut ans, async_resp)
            .await?;
        Ok(ans)
    }

    pub async fn xmit_data_ans(&self, target_role: Role, pl: &XmitDataAnsPayload) -> Result<()> {
        self.response_request(Some(target_role), pl).await
    }

    async fn response_request<S>(&self, target_role: Option<Role>, pl: &S) -> Result<()>
    where
        S: ?Sized + serde::ser::Serialize + BasePayloadResultProvider,
    {
        let server = if self.config.use_target_role_suffix {
            match target_role {
                Some(Role::FNS) => format!("{}/fns", self.config.server),
                Some(Role::SNS) => format!("{}/sns", self.config.server),
                Some(Role::HNS) => format!("{}/hns", self.config.server),
                None => self.config.server.clone(),
            }
        } else {
            self.config.server.clone()
        };

        let bp = pl.base_payload();
        let body = serde_json::to_string(&pl)?;

        info!(receiver_id = %hex::encode(&bp.base.receiver_id), transaction_id = bp.base.transaction_id, message_type = ?bp.base.message_type, server = %server, "Making request");
        debug!("JSON: {}", body);

        self.client
            .post(&server)
            .headers(self.headers.clone())
            .body(body)
            .send()
            .await?
            .error_for_status()?;
        Ok(())
    }

    async fn request<S, D>(
        &self,
        target_role: Option<Role>,
        pl: &S,
        ans: &mut D,
        async_resp: Option<Receiver<Vec<u8>>>,
    ) -> Result<()>
    where
        S: ?Sized + serde::ser::Serialize + BasePayloadProvider,
        D: serde::de::DeserializeOwned + BasePayloadResultProvider,
    {
        let bp = pl.base_payload().clone();

        let mut be_req_log = stream::BackendInterfacesRequest {
            time: Some(Utc::now().into()),
            sender_id: hex::encode(&bp.sender_id),
            receiver_id: hex::encode(&bp.receiver_id),
            transaction_id: bp.transaction_id,
            message_type: format!("{:?}", bp.message_type),
            ..Default::default()
        };

        let span = span!(Level::INFO, "request", message_type = ?bp.message_type, sender_id = %be_req_log.sender_id, receiver_id = %be_req_log.receiver_id, transaction_id = bp.transaction_id);

        let res = self
            ._request(target_role, pl, ans, async_resp, &mut be_req_log)
            .instrument(span)
            .await;

        if let Err(e) = &res {
            be_req_log.request_error = format!("{:#}", e);
        }

        if let Some(tx) = &self.config.request_log_sender {
            // We use try_send here as we don't want to delay the response in case
            // there is no channel capacity. This would also log an error, proving
            // feedback that there is a channel capacity issue.
            if let Err(e) = tx.try_send(be_req_log) {
                error!(error = %e, "Sending request-log to stream error");
            }
        }

        res
    }

    async fn _request<S, D>(
        &self,
        target_role: Option<Role>,
        pl: &S,
        ans: &mut D,
        async_resp: Option<Receiver<Vec<u8>>>,
        be_req_log: &mut stream::BackendInterfacesRequest,
    ) -> Result<()>
    where
        S: ?Sized + serde::ser::Serialize + BasePayloadProvider,
        D: serde::de::DeserializeOwned + BasePayloadResultProvider,
    {
        let server = if self.config.use_target_role_suffix {
            match target_role {
                Some(Role::FNS) => format!("{}/fns", self.config.server),
                Some(Role::SNS) => format!("{}/sns", self.config.server),
                Some(Role::HNS) => format!("{}/hns", self.config.server),
                None => self.config.server.clone(),
            }
        } else {
            self.config.server.clone()
        };

        let body = serde_json::to_string(&pl)?;
        be_req_log.request_body.clone_from(&body);

        info!(server = %server, async_interface = %async_resp.is_some(), "Making request");

        let res = self
            .client
            .post(&server)
            .headers(self.headers.clone())
            .body(body)
            .send()
            .await?
            .error_for_status()?;

        let resp_json = match async_resp {
            Some(rx) => {
                let sleep = tokio::time::sleep(self.config.async_timeout);

                tokio::select! {
                    rx_ans = rx => {
                        info!("Async response received");
                        String::from_utf8(rx_ans?)?
                    }
                    _ = sleep => {
                        error!("Async response timeout");
                        return Err(anyhow!("Async timeout"));
                    }
                }
            }
            None => res.text().await?,
        };

        be_req_log.response_body.clone_from(&resp_json);

        let base: BasePayloadResult = serde_json::from_str(&resp_json)?;
        be_req_log.result_code = format!("{:?}", base.result.result_code);

        if base.result.result_code != ResultCode::Success {
            error!(result_code = ?base.result.result_code, description = %base.result.description, "Response error");
            return Err(anyhow!(
                "Response error, code: {:?}, description: {:?}",
                base.result.result_code,
                base.result.description
            ));
        }

        *ans = serde_json::from_str(&resp_json)?;

        Ok(())
    }
}

#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
pub enum MessageType {
    #[default]
    JoinReq,
    JoinAns,
    RejoinReq,
    RejoinAns,
    AppSKeyReq,
    AppSKeyAns,
    PRStartReq,
    PRStartAns,
    PRStopReq,
    PRStopAns,
    HomeNSReq,
    HomeNSAns,
    XmitDataReq,
    XmitDataAns,
}

#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
pub enum ResultCode {
    #[default]
    Success,
    MICFailed,
    JoinReqFailed,
    NoRoamingAgreement,
    DevRoamingDisallowed,
    RoamingActDisallowed,
    ActivationDisallowed,
    UnknownDevEUI,
    UnknownDevAddr,
    UnknownSender,
    UnknownReceiver,
    UnkownReceiver, //Value in Backend Interfaces Spec 1.0/1.1 is misspelled
    Deferred,
    XmitFailed,
    InvalidFPort,
    InvalidProtocolVersion,
    StaleDeviceProfile,
    MalformedRequest,
    FrameSizeError,
    Other,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
pub enum RatePolicy {
    Drop,
    Mark,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(default)]
pub struct BasePayload {
    #[serde(rename = "ProtocolVersion")]
    pub protocol_version: String,
    #[serde(rename = "SenderID", with = "hex_encode")]
    pub sender_id: Vec<u8>,
    #[serde(rename = "ReceiverID", with = "hex_encode")]
    pub receiver_id: Vec<u8>,
    #[serde(rename = "TransactionID")]
    pub transaction_id: u32,
    #[serde(rename = "MessageType")]
    pub message_type: MessageType,
    #[serde(
        default,
        rename = "SenderToken",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub sender_token: Vec<u8>,
    #[serde(
        default,
        rename = "ReceiverToken",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub receiver_token: Vec<u8>,
}

impl BasePayload {
    pub fn to_base_payload_result(
        &self,
        res_code: ResultCode,
        description: &str,
    ) -> BasePayloadResult {
        BasePayloadResult {
            base: BasePayload {
                protocol_version: self.protocol_version.clone(),
                sender_id: self.receiver_id.clone(),
                receiver_id: self.sender_id.clone(),
                transaction_id: self.transaction_id,
                message_type: match self.message_type {
                    MessageType::PRStartReq => MessageType::PRStartAns,
                    MessageType::PRStopReq => MessageType::PRStopAns,
                    MessageType::XmitDataReq => MessageType::XmitDataAns,
                    MessageType::HomeNSReq => MessageType::HomeNSAns,
                    _ => self.message_type,
                },
                sender_token: self.receiver_token.clone(),
                receiver_token: self.sender_token.clone(),
            },
            result: ResultPayload {
                result_code: res_code,
                description: description.to_string(),
            },
        }
    }

    pub fn is_answer(&self) -> bool {
        match self.message_type {
            MessageType::JoinAns
            | MessageType::RejoinAns
            | MessageType::AppSKeyAns
            | MessageType::PRStartAns
            | MessageType::PRStopAns
            | MessageType::HomeNSAns
            | MessageType::XmitDataAns => true,

            MessageType::JoinReq
            | MessageType::RejoinReq
            | MessageType::AppSKeyReq
            | MessageType::PRStartReq
            | MessageType::PRStopReq
            | MessageType::HomeNSReq
            | MessageType::XmitDataReq => false,
        }
    }
}

impl Default for BasePayload {
    fn default() -> Self {
        BasePayload {
            protocol_version: PROTOCOL_VERSION.into(),
            sender_id: "".into(),
            receiver_id: "".into(),
            transaction_id: rand::random(),
            message_type: MessageType::default(),
            sender_token: vec![],
            receiver_token: vec![],
        }
    }
}

#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
#[serde(default)]
pub struct BasePayloadResult {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(rename = "Result")]
    pub result: ResultPayload,
}

impl BasePayloadResultProvider for BasePayloadResult {
    fn base_payload(&self) -> &BasePayloadResult {
        self
    }
}

#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
pub struct ResultPayload {
    #[serde(rename = "ResultCode")]
    pub result_code: ResultCode,
    #[serde(
        default,
        rename = "Description",
        skip_serializing_if = "String::is_empty"
    )]
    pub description: String,
}

#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
#[serde(default)]
pub struct KeyEnvelope {
    #[serde(default, rename = "KEKLabel")]
    pub kek_label: String,
    #[serde(rename = "AESKey", with = "hex_encode")]
    pub aes_key: Vec<u8>,
}

impl KeyEnvelope {
    pub fn new(label: &str, kek: Option<&[u8; 16]>, key: &[u8; 16]) -> Result<Self> {
        if label.is_empty() || kek.is_none() {
            return Ok(KeyEnvelope {
                kek_label: "".into(),
                aes_key: key.to_vec(),
            });
        }

        let kek = Kek::from(*kek.unwrap());
        let mut cipher: Vec<u8> = vec![0; 16 + 8];
        kek.wrap(key, &mut cipher)
            .map_err(|e| anyhow!("KEK wrap failed: {}", e))?;

        Ok(KeyEnvelope {
            kek_label: label.to_string(),
            aes_key: cipher,
        })
    }

    pub fn unwrap(&self, kek: &[u8; 16]) -> Result<[u8; 16]> {
        let kek = Kek::from(*kek);
        let mut out: [u8; 16] = [0; 16];
        kek.unwrap(&self.aes_key, &mut out)
            .map_err(|e| anyhow!("KEK unwrap failed: {}", e))?;
        Ok(out)
    }
}

#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug, Clone)]
pub struct JoinReqPayload {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(rename = "MACVersion")]
    pub mac_version: String,
    #[serde(rename = "PHYPayload", with = "hex_encode")]
    pub phy_payload: Vec<u8>,
    #[serde(rename = "DevEUI", with = "hex_encode")]
    pub dev_eui: Vec<u8>,
    #[serde(rename = "DevAddr", with = "hex_encode")]
    pub dev_addr: Vec<u8>,
    #[serde(rename = "DLSettings", with = "hex_encode")]
    pub dl_settings: Vec<u8>,
    #[serde(rename = "RxDelay")]
    pub rx_delay: u8,
    #[serde(
        default,
        rename = "CFList",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub cf_list: Vec<u8>,
}

impl BasePayloadProvider for &mut JoinReqPayload {
    fn base_payload(&self) -> &BasePayload {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
#[serde(default)]
pub struct JoinAnsPayload {
    #[serde(flatten)]
    pub base: BasePayloadResult,
    #[serde(rename = "PHYPayload", with = "hex_encode")]
    pub phy_payload: Vec<u8>,
    #[serde(rename = "Lifetime", skip_serializing_if = "Option::is_none")]
    pub lifetime: Option<usize>,
    #[serde(rename = "SNwkSIntKey", skip_serializing_if = "Option::is_none")]
    pub s_nwk_s_int_key: Option<KeyEnvelope>,
    #[serde(rename = "FNwkSIntKey", skip_serializing_if = "Option::is_none")]
    pub f_nwk_s_int_key: Option<KeyEnvelope>,
    #[serde(rename = "NwkSEncKey", skip_serializing_if = "Option::is_none")]
    pub nwk_s_enc_key: Option<KeyEnvelope>,
    #[serde(rename = "NwkSKey", skip_serializing_if = "Option::is_none")]
    pub nwk_s_key: Option<KeyEnvelope>,
    #[serde(rename = "AppSKey", skip_serializing_if = "Option::is_none")]
    pub app_s_key: Option<KeyEnvelope>,
    #[serde(
        default,
        rename = "SessionKeyID",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub session_key_id: Vec<u8>,
}

impl BasePayloadResultProvider for JoinAnsPayload {
    fn base_payload(&self) -> &BasePayloadResult {
        &self.base
    }
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct RejoinReqPayload {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(rename = "MACVersion")]
    pub mac_version: String,
    #[serde(rename = "PHYPayload", with = "hex_encode")]
    pub phy_payload: Vec<u8>,
    #[serde(rename = "DevEUI", with = "hex_encode")]
    pub dev_eui: Vec<u8>,
    #[serde(rename = "DevAddr", with = "hex_encode")]
    pub dev_addr: Vec<u8>,
    #[serde(rename = "DLSettings", with = "hex_encode")]
    pub dl_settings: Vec<u8>,
    #[serde(rename = "RxDelay")]
    pub rx_delay: u8,
    #[serde(
        default,
        rename = "CFList",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub cf_list: Vec<u8>,
}

impl BasePayloadProvider for &mut RejoinReqPayload {
    fn base_payload(&self) -> &BasePayload {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default, Clone)]
pub struct RejoinAnsPayload {
    #[serde(flatten)]
    pub base: BasePayloadResult,
    #[serde(rename = "PHYPayload", with = "hex_encode")]
    pub phy_payload: Vec<u8>,
    #[serde(rename = "Lifetime", skip_serializing_if = "Option::is_none")]
    pub lifetime: Option<usize>,
    #[serde(rename = "SNwkSIntKey", skip_serializing_if = "Option::is_none")]
    pub s_nwk_s_int_key: Option<KeyEnvelope>,
    #[serde(rename = "FNwkSIntKey", skip_serializing_if = "Option::is_none")]
    pub f_nwk_s_int_key: Option<KeyEnvelope>,
    #[serde(rename = "NwkSEncKey", skip_serializing_if = "Option::is_none")]
    pub nwk_s_enc_key: Option<KeyEnvelope>,
    #[serde(rename = "NwkSKey", skip_serializing_if = "Option::is_none")]
    pub nwk_s_key: Option<KeyEnvelope>,
    #[serde(rename = "AppSKey", skip_serializing_if = "Option::is_none")]
    pub app_s_key: Option<KeyEnvelope>,
    #[serde(
        default,
        rename = "SessionKeyID",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub session_key_id: Vec<u8>,
}

impl BasePayloadResultProvider for RejoinAnsPayload {
    fn base_payload(&self) -> &BasePayloadResult {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct AppSKeyReqPayload {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(rename = "DevEUI", with = "hex_encode")]
    pub dev_eui: Vec<u8>,
    #[serde(rename = "SessionKeyID", with = "hex_encode")]
    pub session_key_id: Vec<u8>,
}

impl BasePayloadProvider for &mut AppSKeyReqPayload {
    fn base_payload(&self) -> &BasePayload {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default, Clone)]
pub struct AppSKeyAnsPayload {
    #[serde(flatten)]
    pub base: BasePayloadResult,
    #[serde(rename = "DevEUI", with = "hex_encode")]
    pub dev_eui: Vec<u8>,
    #[serde(rename = "AppSKey", skip_serializing_if = "Option::is_none")]
    pub app_s_key: Option<KeyEnvelope>,
    #[serde(rename = "SessionKeyID", with = "hex_encode")]
    pub session_key_id: Vec<u8>,
}

impl BasePayloadResultProvider for AppSKeyAnsPayload {
    fn base_payload(&self) -> &BasePayloadResult {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)]
pub struct PRStartReqPayload {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(rename = "PHYPayload", with = "hex_encode")]
    pub phy_payload: Vec<u8>,
    #[serde(rename = "ULMetaData")]
    pub ul_meta_data: ULMetaData,
}

impl BasePayloadProvider for &mut PRStartReqPayload {
    fn base_payload(&self) -> &BasePayload {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)]
pub struct PRStartAnsPayload {
    #[serde(flatten)]
    pub base: BasePayloadResult,
    #[serde(
        default,
        rename = "PHYPayload",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub phy_payload: Vec<u8>,
    #[serde(
        default,
        rename = "DevEUI",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub dev_eui: Vec<u8>,
    #[serde(rename = "Lifetime", skip_serializing_if = "Option::is_none")]
    pub lifetime: Option<usize>,
    #[serde(rename = "FNwkSIntKey", skip_serializing_if = "Option::is_none")]
    pub f_nwk_s_int_key: Option<KeyEnvelope>,
    #[serde(rename = "NwkSKey", skip_serializing_if = "Option::is_none")]
    pub nwk_s_key: Option<KeyEnvelope>,
    #[serde(rename = "FCntUp", skip_serializing_if = "Option::is_none")]
    pub f_cnt_up: Option<u32>,
    #[serde(rename = "ServiceProfile", skip_serializing_if = "Option::is_none")]
    pub service_profile: Option<ServiceProfile>,
    #[serde(rename = "DLMetaData", skip_serializing_if = "Option::is_none")]
    pub dl_meta_data: Option<DLMetaData>,
    #[serde(
        default,
        rename = "DevAddr",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub dev_addr: Vec<u8>,
}

impl BasePayloadResultProvider for PRStartAnsPayload {
    fn base_payload(&self) -> &BasePayloadResult {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct PRStopReqPayload {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(rename = "DevEUI", with = "hex_encode")]
    pub dev_eui: Vec<u8>,
    #[serde(rename = "Lifetime", skip_serializing_if = "Option::is_none")]
    pub lifetime: Option<usize>,
}

impl BasePayloadProvider for &mut PRStopReqPayload {
    fn base_payload(&self) -> &BasePayload {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default, Clone)]
pub struct PRStopAnsPayload {
    #[serde(flatten)]
    pub base: BasePayloadResult,
}

impl BasePayloadResultProvider for PRStopAnsPayload {
    fn base_payload(&self) -> &BasePayloadResult {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)]
pub struct XmitDataReqPayload {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(
        default,
        rename = "PHYPayload",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub phy_payload: Vec<u8>,
    #[serde(
        default,
        rename = "FRMPayload",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub frm_payload: Vec<u8>,
    #[serde(rename = "ULMetaData", skip_serializing_if = "Option::is_none")]
    pub ul_meta_data: Option<ULMetaData>,
    #[serde(rename = "DLMetaData", skip_serializing_if = "Option::is_none")]
    pub dl_meta_data: Option<DLMetaData>,
}

impl BasePayloadProvider for &mut XmitDataReqPayload {
    fn base_payload(&self) -> &BasePayload {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default, Clone)]
pub struct XmitDataAnsPayload {
    #[serde(flatten)]
    pub base: BasePayloadResult,
}

impl BasePayloadResultProvider for XmitDataAnsPayload {
    fn base_payload(&self) -> &BasePayloadResult {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
pub struct HomeNSReqPayload {
    #[serde(flatten)]
    pub base: BasePayload,
    #[serde(rename = "DevEUI", with = "hex_encode")]
    pub dev_eui: Vec<u8>,
}

impl BasePayloadProvider for &mut HomeNSReqPayload {
    fn base_payload(&self) -> &BasePayload {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
pub struct HomeNSAnsPayload {
    #[serde(flatten)]
    pub base: BasePayloadResult,
    #[serde(
        default,
        rename = "HNetID",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub h_net_id: Vec<u8>,
}

impl BasePayloadResultProvider for HomeNSAnsPayload {
    fn base_payload(&self) -> &BasePayloadResult {
        &self.base
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ULMetaData {
    #[serde(
        default,
        rename = "DevEUI",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub dev_eui: Vec<u8>,
    #[serde(
        default,
        rename = "DevAddr",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub dev_addr: Vec<u8>,
    #[serde(rename = "FPort", skip_serializing_if = "Option::is_none")]
    pub f_port: Option<u8>,
    #[serde(rename = "FCntDown", skip_serializing_if = "Option::is_none")]
    pub f_cnt_down: Option<u32>,
    #[serde(rename = "FCntUp", skip_serializing_if = "Option::is_none")]
    pub f_cnt_up: Option<u32>,
    #[serde(rename = "Confirmed", skip_serializing_if = "Option::is_none")]
    pub confirmed: Option<bool>,
    #[serde(rename = "DataRate", skip_serializing_if = "Option::is_none")]
    pub data_rate: Option<u8>,
    #[serde(rename = "ULFreq", skip_serializing_if = "Option::is_none")]
    pub ul_freq: Option<f64>,
    #[serde(rename = "Margin", skip_serializing_if = "Option::is_none")]
    pub margin: Option<isize>,
    #[serde(rename = "Battery", skip_serializing_if = "Option::is_none")]
    pub battery: Option<isize>,
    #[serde(
        default,
        rename = "FNSULToken",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub f_ns_ul_token: Vec<u8>,
    #[serde(rename = "RecvTime")]
    pub recv_time: DateTime<Utc>,
    #[serde(
        default,
        rename = "RFRegion",
        with = "rf_region_encode",
        skip_serializing_if = "String::is_empty"
    )]
    pub rf_region: String,
    #[serde(rename = "GWCnt", skip_serializing_if = "Option::is_none")]
    pub gw_cnt: Option<usize>,
    #[serde(rename = "GWInfo")]
    pub gw_info: Vec<GWInfoElement>,
}

impl Default for ULMetaData {
    fn default() -> Self {
        ULMetaData {
            dev_eui: Vec::new(),
            dev_addr: Vec::new(),
            f_port: None,
            f_cnt_down: None,
            f_cnt_up: None,
            confirmed: None,
            data_rate: None,
            ul_freq: None,
            margin: None,
            battery: None,
            f_ns_ul_token: Vec::new(),
            recv_time: Utc::now(),
            rf_region: "".to_string(),
            gw_cnt: None,
            gw_info: Vec::new(),
        }
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
pub struct GWInfoElement {
    #[serde(
        default,
        rename = "ID",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub id: Vec<u8>,
    #[serde(rename = "FineRecvTime", skip_serializing_if = "Option::is_none")]
    pub fine_recv_time: Option<usize>,
    #[serde(
        default,
        rename = "RFRegion",
        with = "rf_region_encode",
        skip_serializing_if = "String::is_empty"
    )]
    pub rf_region: String,
    #[serde(rename = "RSSI", skip_serializing_if = "Option::is_none")]
    pub rssi: Option<isize>,
    #[serde(rename = "SNR", skip_serializing_if = "Option::is_none")]
    pub snr: Option<f32>,
    #[serde(rename = "Lat", skip_serializing_if = "Option::is_none")]
    pub lat: Option<f64>,
    #[serde(rename = "Lon", skip_serializing_if = "Option::is_none")]
    pub lon: Option<f64>,
    #[serde(
        default,
        rename = "ULToken",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub ul_token: Vec<u8>,
    #[serde(rename = "DLAllowed", skip_serializing_if = "Option::is_none")]
    pub dl_allowed: Option<bool>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ServiceProfile {
    #[serde(rename = "ServiceProfile")]
    pub service_profile_id: String,
    #[serde(rename = "ULRate")]
    pub ul_rate: usize,
    #[serde(rename = "ULBucketSize")]
    pub ul_bucket_size: usize,
    #[serde(rename = "ULRatePolicy")]
    pub ul_rate_policy: RatePolicy,
    #[serde(rename = "DLRate")]
    pub dl_rate: usize,
    #[serde(rename = "DLBucketSize")]
    pub dl_bucket_size: usize,
    #[serde(rename = "DLRatePolicy")]
    pub dl_rate_policy: RatePolicy,
    #[serde(rename = "AddGWMetadata")]
    pub add_gw_metadata: bool,
    #[serde(rename = "DevStatusReqFreq")]
    pub dev_status_req_freq: usize,
    #[serde(rename = "ReportDevStatusBatery")]
    pub report_dev_status_battery: bool,
    #[serde(rename = "ReportDevStatusMargin")]
    pub report_dev_status_margin: bool,
    #[serde(rename = "DRMin")]
    pub dr_min: usize,
    #[serde(rename = "DRMax")]
    pub dr_mac: usize,
    #[serde(rename = "ChannelMask", with = "hex_encode")]
    pub channel_mask: Vec<u8>,
    #[serde(rename = "PRAllowed")]
    pub pr_allowed: bool,
    #[serde(rename = "HRAllowed")]
    pub hr_allowed: bool,
    #[serde(rename = "RAAllowed")]
    pub ra_allowed: bool,
    #[serde(rename = "NwkGeoLoc")]
    pub nwk_geo_loc: bool,
    #[serde(rename = "TargetPER")]
    pub target_per: f32,
    #[serde(rename = "MinGWDiversity")]
    pub min_gw_diversity: usize,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
pub struct DLMetaData {
    #[serde(
        default,
        rename = "DevEUI",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub dev_eui: Vec<u8>,
    #[serde(rename = "FPort", skip_serializing_if = "Option::is_none")]
    pub f_port: Option<u8>,
    #[serde(rename = "FCntDown", skip_serializing_if = "Option::is_none")]
    pub f_cnt_down: Option<u32>,
    #[serde(default, rename = "Confirmed")]
    pub confirmed: bool,
    #[serde(rename = "DLFreq1", skip_serializing_if = "Option::is_none")]
    pub dl_freq_1: Option<f64>,
    #[serde(rename = "DLFreq2", skip_serializing_if = "Option::is_none")]
    pub dl_freq_2: Option<f64>,
    #[serde(rename = "RXDelay1", skip_serializing_if = "Option::is_none")]
    pub rx_delay_1: Option<usize>,
    #[serde(rename = "ClassMode", skip_serializing_if = "Option::is_none")]
    pub class_mode: Option<String>,
    #[serde(rename = "DataRate1", skip_serializing_if = "Option::is_none")]
    pub data_rate_1: Option<u8>,
    #[serde(rename = "DataRate2", skip_serializing_if = "Option::is_none")]
    pub data_rate_2: Option<u8>,
    #[serde(
        default,
        rename = "FNSULToken",
        with = "hex_encode",
        skip_serializing_if = "Vec::is_empty"
    )]
    pub f_ns_ul_token: Vec<u8>,
    #[serde(rename = "GWInfo")]
    pub gw_info: Vec<GWInfoElement>,
    #[serde(default, rename = "HiPriorityFlag")]
    pub hi_priority_flag: bool,
}

mod rf_region_encode {
    use serde::{Deserializer, Serializer};

    pub fn serialize<S>(s: &str, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&s.replace('_', "-"))
    }

    pub fn deserialize<'a, D>(deserializer: D) -> Result<String, D::Error>
    where
        D: Deserializer<'a>,
    {
        let s: &str = serde::de::Deserialize::deserialize(deserializer)?;

        // Some implementations use lowercase.
        Ok(s.to_uppercase())
    }
}

mod hex_encode {
    use serde::{Deserializer, Serializer};

    pub fn serialize<S>(b: &[u8], serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex::encode(b))
    }

    pub fn deserialize<'a, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
    where
        D: Deserializer<'a>,
    {
        let s: &str = serde::de::Deserialize::deserialize(deserializer)?;

        // HEX encoded values may start with 0x prefix, we must strip this.
        let s = s.trim_start_matches("0x");

        hex::decode(s).map_err(serde::de::Error::custom)
    }
}

#[cfg(test)]
pub mod test {
    use super::*;
    use httpmock::prelude::*;
    use tokio::sync::{mpsc, oneshot};

    #[test]
    fn test_key_envelope() {
        let key: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8];
        let kek: [u8; 16] = [8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1];

        // no wrapping
        let ke = KeyEnvelope::new("", None, &key).unwrap();
        assert_eq!(key.to_vec(), ke.aes_key);
        assert_eq!("", ke.kek_label);

        // wrapping
        let ke = KeyEnvelope::new("test-kek", Some(&kek), &key).unwrap();
        assert_eq!(
            vec![
                0xe3, 0xd5, 0xa4, 0x7b, 0xa2, 0x5c, 0xbe, 0x6e, 0x5d, 0xa8, 0x20, 0x84, 0x6e, 0xc,
                0xb6, 0xa8, 0x2b, 0x75, 0xc, 0x59, 0xd8, 0x48, 0xec, 0x7a
            ],
            ke.aes_key
        );
        assert_eq!("test-kek", ke.kek_label);
        assert_eq!(key, ke.unwrap(&kek).unwrap());
    }

    #[tokio::test]
    async fn test_async_request() {
        let server = MockServer::start();

        let c = Client::new(ClientConfig {
            sender_id: vec![1, 2, 3],
            server: server.url("/"),
            async_timeout: Duration::from_secs(1),
            ..Default::default()
        })
        .unwrap();

        let mut req = HomeNSReqPayload {
            base: BasePayload {
                sender_id: vec![1, 2, 3],
                receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                message_type: MessageType::HomeNSReq,
                transaction_id: 1234,
                ..Default::default()
            },
            dev_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
        };

        let ans = HomeNSAnsPayload {
            base: BasePayloadResult {
                base: BasePayload {
                    sender_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                    receiver_id: vec![1, 2, 3],
                    message_type: MessageType::HomeNSAns,
                    transaction_id: 1234,
                    ..Default::default()
                },
                result: ResultPayload {
                    result_code: ResultCode::Success,
                    description: "".into(),
                },
            },
            h_net_id: vec![3, 2, 1],
        };

        let mut mock = server.mock(|when, then| {
            when.method(POST)
                .path("/")
                .body(serde_json::to_string(&req).unwrap());
            then.status(200);
        });

        // OK
        let (tx, rx) = oneshot::channel();
        tx.send(serde_json::to_vec(&ans).unwrap()).unwrap();
        let resp = c
            .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx))
            .await
            .unwrap();
        mock.assert();
        mock.delete();
        assert_eq!(resp, ans);

        // Timeout
        let (_tx, rx) = oneshot::channel();
        let resp = c
            .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx))
            .await;
        assert!(resp.is_err());
    }

    #[tokio::test]
    async fn test_async_request_204_status() {
        let server = MockServer::start();

        let c = Client::new(ClientConfig {
            sender_id: vec![1, 2, 3],
            server: server.url("/"),
            async_timeout: Duration::from_secs(1),
            ..Default::default()
        })
        .unwrap();

        let mut req = HomeNSReqPayload {
            base: BasePayload {
                sender_id: vec![1, 2, 3],
                receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                message_type: MessageType::HomeNSReq,
                transaction_id: 1234,
                ..Default::default()
            },
            dev_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
        };

        let ans = HomeNSAnsPayload {
            base: BasePayloadResult {
                base: BasePayload {
                    sender_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                    receiver_id: vec![1, 2, 3],
                    message_type: MessageType::HomeNSAns,
                    transaction_id: 1234,
                    ..Default::default()
                },
                result: ResultPayload {
                    result_code: ResultCode::Success,
                    description: "".into(),
                },
            },
            h_net_id: vec![3, 2, 1],
        };

        let mut mock = server.mock(|when, then| {
            when.method(POST)
                .path("/")
                .body(serde_json::to_string(&req).unwrap());
            then.status(204);
        });

        // OK
        let (tx, rx) = oneshot::channel();
        tx.send(serde_json::to_vec(&ans).unwrap()).unwrap();
        let resp = c
            .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx))
            .await
            .unwrap();
        mock.assert();
        mock.delete();
        assert_eq!(resp, ans);

        // Timeout
        let (_tx, rx) = oneshot::channel();
        let resp = c
            .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, Some(rx))
            .await;
        assert!(resp.is_err());
    }

    #[tokio::test]
    async fn test_sync_request() {
        let server = MockServer::start();

        let c = Client::new(ClientConfig {
            sender_id: vec![1, 2, 3],
            server: server.url("/"),
            ..Default::default()
        })
        .unwrap();

        let mut req = HomeNSReqPayload {
            base: BasePayload {
                sender_id: vec![1, 2, 3],
                receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                message_type: MessageType::HomeNSReq,
                transaction_id: 1234,
                ..Default::default()
            },
            dev_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
        };

        let ans = HomeNSAnsPayload {
            base: BasePayloadResult {
                base: BasePayload {
                    sender_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                    receiver_id: vec![1, 2, 3],
                    message_type: MessageType::HomeNSAns,
                    transaction_id: 1234,
                    ..Default::default()
                },
                result: ResultPayload {
                    result_code: ResultCode::Success,
                    description: "".into(),
                },
            },
            h_net_id: vec![3, 2, 1],
        };

        // OK
        let mut mock = server.mock(|when, then| {
            when.method(POST)
                .path("/")
                .body(serde_json::to_string(&req).unwrap());
            then.body(serde_json::to_vec(&ans).unwrap()).status(200);
        });
        let resp = c
            .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, None)
            .await
            .unwrap();
        mock.assert();
        mock.delete();
        assert_eq!(resp, ans);

        // Error status
        let mut mock = server.mock(|when, then| {
            when.method(POST)
                .path("/")
                .body(serde_json::to_string(&req).unwrap());
            then.status(500);
        });
        let resp = c
            .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, None)
            .await;
        mock.assert();
        mock.delete();
        assert!(resp.is_err());
    }

    #[tokio::test]
    async fn test_log_fn_ok() {
        let (tx, mut rx) = mpsc::channel(1);
        let server = MockServer::start();

        let c = Client::new(ClientConfig {
            sender_id: vec![1, 2, 3],
            server: server.url("/"),
            request_log_sender: Some(tx),
            ..Default::default()
        })
        .unwrap();

        let mut req = HomeNSReqPayload {
            base: BasePayload {
                sender_id: vec![1, 2, 3],
                receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                message_type: MessageType::HomeNSReq,
                transaction_id: 1234,
                ..Default::default()
            },
            dev_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
        };

        let ans = HomeNSAnsPayload {
            base: BasePayloadResult {
                base: BasePayload {
                    sender_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                    receiver_id: vec![1, 2, 3],
                    message_type: MessageType::HomeNSAns,
                    transaction_id: 1234,
                    ..Default::default()
                },
                result: ResultPayload {
                    result_code: ResultCode::Success,
                    description: "".into(),
                },
            },
            h_net_id: vec![3, 2, 1],
        };

        // OK
        let mut mock = server.mock(|when, then| {
            when.method(POST)
                .path("/")
                .body(serde_json::to_string(&req).unwrap());
            then.body(serde_json::to_vec(&ans).unwrap()).status(200);
        });

        c.home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, None)
            .await
            .unwrap();
        mock.assert();
        mock.delete();

        let be_req_log = rx.recv().await.unwrap();
        assert_eq!("010203", be_req_log.sender_id);
        assert_eq!("0102030405060708", be_req_log.receiver_id);
        assert_eq!(1234, be_req_log.transaction_id);
        assert!(be_req_log.request_error.is_empty());
    }

    #[tokio::test]
    async fn test_log_fn_error() {
        let (tx, mut rx) = mpsc::channel(1);
        let server = MockServer::start();

        let c = Client::new(ClientConfig {
            sender_id: vec![1, 2, 3],
            server: server.url("/"),
            request_log_sender: Some(tx),
            ..Default::default()
        })
        .unwrap();

        let mut req = HomeNSReqPayload {
            base: BasePayload {
                sender_id: vec![1, 2, 3],
                receiver_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
                message_type: MessageType::HomeNSReq,
                transaction_id: 1234,
                ..Default::default()
            },
            dev_eui: vec![8, 7, 6, 5, 4, 3, 2, 1],
        };

        // OK
        let mut mock = server.mock(|when, then| {
            when.method(POST)
                .path("/")
                .body(serde_json::to_string(&req).unwrap());
            then.status(500);
        });

        assert!(c
            .home_ns_req(vec![1, 2, 3, 4, 5, 6, 7, 8], &mut req, None)
            .await
            .is_err());
        mock.assert();
        mock.delete();

        let be_req_log = rx.recv().await.unwrap();
        assert_eq!("010203", be_req_log.sender_id);
        assert_eq!("0102030405060708", be_req_log.receiver_id);
        assert_eq!(1234, be_req_log.transaction_id);
        assert!(!be_req_log.request_error.is_empty());
    }
}
