// Copyright © 2023 Kim Altintop <kim@eagain.io>
// SPDX-License-Identifier: GPL-2.0-only

use std::collections::{
    HashMap,
    HashSet,
};

use anyhow::Context as _;
use async_trait::async_trait;
use tracing::debug;
use url::Url;
use yapma_common::http::{
    self,
    Response as _,
};

use super::{
    gpg::{
        self,
        Deserializable as _,
        KeyTrait as _,
    },
    resolver::Resolver,
    ssh,
};

pub struct Gitlab<'a> {
    username: &'a str,
}

impl<'a> Gitlab<'a> {
    pub const fn new(username: &'a str) -> Self {
        Self { username }
    }
}

#[async_trait]
impl<'a> Resolver for Gitlab<'a> {
    type Error = anyhow::Error;

    #[tracing::instrument(skip_all, fields(platform = "gitlab"))]
    async fn resolve_ssh<C: http::Client>(
        &self,
        client: &C,
    ) -> Result<HashSet<ssh::PublicKey>, Self::Error> {
        let url = format!("https://gitlab.com/{}.keys", self.username);
        debug!("fetch: {url}");

        let keys = client
            .get(url)
            .await?
            .text()
            .await?
            .lines()
            .map(ssh::PublicKey::from_openssh)
            .collect::<Result<_, _>>()?;

        Ok(keys)
    }

    #[tracing::instrument(skip_all, fields(platform = "gitlab"))]
    async fn resolve_gpg<C: http::Client>(
        &self,
        client: &C,
    ) -> Result<HashMap<gpg::Fingerprint, gpg::SignedPublicKey>, Self::Error> {
        let mut url = Url::parse("https://gitlab.com/api/v4/users").unwrap();

        #[derive(serde::Deserialize)]
        struct User {
            id: u64,
        }

        url.query_pairs_mut().append_pair("username", self.username);
        debug!("resolve user: {url}");
        let user = {
            let mut user = client.get(url.as_str()).await?.json::<Vec<User>>().await?;
            user.pop()
                .with_context(|| format!("username `{}` not found at gitlab", self.username))?
        };

        #[derive(serde::Deserialize)]
        struct Key {
            key: String,
        }

        url.set_query(None);
        url.path_segments_mut()
            .expect("cannot be base cannot be")
            .push(&user.id.to_string())
            .push("gpg_keys");
        debug!("fetch: {url}");
        let keys = client
            .get(url)
            .await?
            .json::<Vec<Key>>()
            .await?
            .iter()
            .map(|Key { key }| {
                let (key, _) = gpg::SignedPublicKey::from_string(key.as_str())?;
                let fp = key.fingerprint();
                key.verify()
                    .with_context(|| format!("key {} failed to verify", hex::encode_upper(&fp)))?;

                Ok((fp, key))
            })
            .collect::<Result<HashMap<_, _>, anyhow::Error>>()?;

        Ok(keys)
    }
}