use bitflags::bitflags;
use diesel::{ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use tracing::*;
use uuid::Uuid;

bitflags! {
    #[derive(Debug, Clone, Copy)]
    pub struct Action: i64 {
        // Per-repository permissions.
        const NEW_DISCUSSION = 0x1;
        const ADD_PATCH = 0x2;
        const APPLY_PATCH = 0x4;
    }
}

#[derive(Debug)]
pub struct Hook {
    pub url: String,
    pub secret: String,
}

impl Hook {
    async fn run(&self, body: String) -> Result<(), crate::Error> {
        debug!("hook: {:?}", self);
        let client = reqwest::Client::new();
        let mut req = client.post(&self.url);
        if let Ok(url) = self.url.parse::<url::Url>() {
            if let (Some(host), Some(port)) = (url.host(), url.port()) {
                req = req.header("Host", format!("{}:{}", host, port).as_str());
            }
        }

        let signature = {
            use openssl::hash::MessageDigest;
            use openssl::pkey::PKey;
            use openssl::sign::Signer;
            let pkey = PKey::hmac(self.secret.as_bytes()).unwrap();
            let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap();
            signer.update(body.as_bytes()).unwrap();
            signer.sign_to_vec().unwrap()
        };
        let s = format!("sha256={}", data_encoding::HEXLOWER.encode(&signature));
        req.header("X-Nest-Event-Signature", s.as_str())
            .body(body)
            .send()
            .await?;
        Ok(())
    }
}

pub async fn run_hooks_by_repo_id(
    db: &mut diesel_async::AsyncPgConnection,
    id: Uuid,
    action: Action,
    contents: &pijul_hooks::HookContent,
) -> Result<(), crate::Error> {
    let body = serde_json::to_string(contents).unwrap();
    use crate::db::hooks::dsl as hooks;
    for (url, secret, a) in hooks::hooks
        .filter(hooks::repository.eq(id))
        .select((hooks::url, hooks::secret, hooks::action))
        .get_results::<(String, String, Option<i64>)>(db)
        .await?
    {
        if let Some(a) = a {
            if (action.bits() & a) != 0 {
                (Hook { url, secret }).run(body.clone()).await?
            }
        } else {
            (Hook { url, secret }).run(body.clone()).await?
        }
    }
    Ok(())
}