//! AWS support

use std::collections::HashMap;

use reqwest::{Client, Response};
use serde_json::Value;

#[derive(Debug, Clone)]
pub struct AwsMetadata {
    client: Client,
    host: String,
}

impl AwsMetadata {
    pub const METADATA_ENDPOINT: &'static str = "169.254.169.254";

    pub fn new(config: &HashMap<String, Value>) -> Self {
        AwsMetadata {
            client: Client::new(),
            host: config
                .get("host")
                .map(|host| host.as_str().unwrap().to_string())
                .unwrap_or_else(|| String::from("169.254.169.254")),
        }
    }

    fn fetch_metadata(&self) -> HashMap<&'static str, String> {
        let mut results = HashMap::with_capacity(11);
        results.insert("ami-id", self.lookup_metadata_key("ami-id"));
        results.insert("instance-id", self.lookup_metadata_key("instance-id"));
        results.insert("instance-type", self.lookup_metadata_key("instance-type"));
        results.insert("local-ipv4", self.lookup_metadata_key("local-ipv4"));
        results.insert("local-hostname", self.lookup_metadata_key("local-hostname"));
        results.insert(
            "availability-zone",
            self.lookup_metadata_key("placement/availability-zone"),
        );
        results.insert(
            "public-hostname",
            self.lookup_metadata_key("public-hostname"),
        );
        results.insert("public-ipv4", self.lookup_metadata_key("public-ipv4"));
        results.insert("mac", self.lookup_metadata_key("mac"));
        results.insert(
            "accountId",
            self.lookup_instance_identity()
                .and_then(|i| i["accountId"].as_str().map(|id| id.to_owned())),
        );
        let mac = results["mac"].clone().unwrap();
        results.insert(
            "vpc-id",
            self.lookup_metadata_key(&format!("network/interfaces/macs/{}/vpc-id", mac)),
        );
        debug!("Found Instance AWS Metadata: {:?}", results);
        results
            .into_iter()
            .fold(HashMap::new(), |mut filtered, (prop, value)| {
                if let Some(value) = value {
                    filtered.insert(prop, value);
                }
                filtered
            })
    }

    fn lookup_metadata_key(&self, key: &str) -> Option<String> {
        let mut response = self
            .client
            .get(&format!("http://{}/latest/meta-data/{}", self.host, key))
            .send()
            .and_then(Response::error_for_status)
            .map_err(|e| {
                error!("Error requesting metadata key: {}", e);
                e
            })
            .ok()?;
        response.text().ok()
    }

    fn lookup_instance_identity(&self) -> Option<HashMap<String, Value>> {
        let mut response = self
            .client
            .get(&format!(
                "http://{}/latest/dynamic/instance-identity/document",
                self.host
            ))
            .send()
            .and_then(Response::error_for_status)
            .map_err(|e| {
                error!("Error requesting instance identity document: {}", e);
                e
            })
            .ok()?;
        response.json().ok()
    }
}
