use reqwest::Request;
use sha1::{Digest, Sha1};

use crate::timestamp::Timestamp;

pub(crate) struct SignatureParams<'a> {
    pub(crate) request: &'a Request,
    pub(crate) app_key: &'a str,
    pub(crate) access_token: Option<&'a str>,
    pub(crate) app_secret: &'a str,
    pub(crate) timestamp: Timestamp,
}

pub(crate) fn signature(params: SignatureParams<'_>) -> String {
    let method = params.request.method().as_str();

    let (signed_headers, signed_values) = match params.access_token {
        Some(access_token) => (
            "authorization;x-api-key;x-timestamp",
            format!(
                "authorization:{}\nx-api-key:{}\nx-timestamp:{}\n",
                access_token, params.app_key, params.timestamp
            ),
        ),
        None => (
            "x-api-key;x-timestamp",
            format!(
                "x-api-key:{}\nx-timestamp:{}\n",
                params.app_key, params.timestamp
            ),
        ),
    };

    let url = params.request.url();
    let path = url.path();
    let query = url.query().unwrap_or_default();

    let mut str_to_sign = format!("{method}|{path}|{query}|{signed_values}|{signed_headers}|",);

    if let Some(body) = params.request.body().and_then(|b| b.as_bytes()) {
        str_to_sign.push_str(&sha1(body));
    }

    let str_to_sign = format!("HMAC-SHA256|{}", sha1(str_to_sign.as_bytes()));
    let signature = hmac_sha256(&str_to_sign, params.app_secret);

    format!("HMAC-SHA256 SignedHeaders={signed_headers}, Signature={signature}")
}

fn sha1(data: &[u8]) -> String {
    format!("{:x}", Sha1::digest(data))
}

fn hmac_sha256(str_to_sign: &str, key: &str) -> String {
    use hmac::Mac;
    let result = hmac::Hmac::<sha2::Sha256>::new_from_slice(key.as_bytes())
        .expect("invalid app secret length")
        .chain_update(str_to_sign)
        .finalize();
    format!("{:x}", result.into_bytes())
}
