/// This module contains utilities for populating userdata for the admin-container with user SSH keys from IMDS.
use argh::FromArgs;
use imdsclient::ImdsClient;
use serde::Serialize;
use snafu::ResultExt;

use crate::error::{self, Result};

#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "generate-admin-userdata")]
/// Fetch and populate the admin container's user-data with authorized ssh keys.
pub(crate) struct GenerateAdminUserdata {}

impl GenerateAdminUserdata {
    pub(crate) async fn run(self) -> Result<()> {
        let public_keys = fetch_public_keys_from_imds().await?;

        let user_data = UserData::new(public_keys);

        log::info!("Generating user-data");
        // Serialize user_data to a JSON string that can be read by the admin container.
        let user_data_json =
            serde_json::to_string(&user_data).context(error::SerializeJsonSnafu)?;
        log::debug!("{}", &user_data_json);

        log::info!("Encoding user-data");
        // admin container user-data must be base64-encoded to be passed through to the admin container
        // using a setting, rather than another arbitrary storage mechanism. This approach allows the
        // user to bypass shibaken and use their own user-data if desired.
        let user_data_base64 = base64::encode(&user_data_json);

        log::info!("Outputting base64-encoded user-data");
        // sundog expects JSON-serialized output so that many types can be represented, allowing the
        // API model to use more accurate types.
        let output = serde_json::to_string(&user_data_base64).context(error::SerializeJsonSnafu)?;

        println!("{}", output);

        Ok(())
    }
}

#[derive(Serialize)]
struct UserData {
    ssh: Ssh,
}

#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
struct Ssh {
    authorized_keys: Vec<String>,
}
impl UserData {
    fn new(public_keys: Vec<String>) -> Self {
        UserData {
            ssh: Ssh {
                authorized_keys: public_keys,
            },
        }
    }
}

/// Returns a list of public keys.
async fn fetch_public_keys_from_imds() -> Result<Vec<String>> {
    log::info!("Connecting to IMDS");
    let mut client = ImdsClient::new();
    let public_keys = client
        .fetch_public_ssh_keys()
        .await
        .context(error::ImdsClientSnafu)?
        .unwrap_or_default();
    Ok(public_keys)
}