// 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 yapma_common::http::{
    self,
    Response as _,
};

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

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

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

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

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

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

        let keys = client
            .get(url)
            .await?
            .json::<Vec<Key>>()
            .await?
            .iter()
            .map(|Key { raw_key }| {
                let (key, _) = gpg::SignedPublicKey::from_string(raw_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)
    }
}