use std::env::args;

use anyhow::{bail, Context};
use futures::StreamExt;
use reqwest::Client;
use serde_json::{json, Map, Value};

pub mod types;

macro_rules! get_json_value {
    ($key:expr, $json_value:ident, $type:ident, $get:ident) => {
        match $json_value.get($key) {
            Some(resp) => {
                let resp = resp.to_owned();
                if resp.$type() {
                    resp.$get().expect(
                        "The should have been checked in the if guard, so unpacking here is fine",
                    ).to_owned()
                } else {
                    bail!(
                        "Value {} => \n{}\n is not of type: {}",
                        $key,
                        resp,
                        stringify!($type)
                    );
                }
            }
            None => {
                bail!(
                    "There seems to be no '{}' in your json data (json value: '{}')\n Has the api changend?",
                    $key, serde_json::to_string_pretty(&$json_value).expect("Will always work")
                );
            }
        }
    };
}

use futures::stream::futures_unordered::FuturesUnordered;
use types::{Extension, InputExtension};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut extensions: Vec<InputExtension> = vec![];
    for input_extension in args()
        .skip(1)
        .map(|str| InputExtension::try_from(str))
        .collect::<Vec<anyhow::Result<InputExtension>>>()
    {
        extensions.push(input_extension?);
    }

    let resulting_extensions = process_extensions(extensions).await?;

    let mut output = Map::new();
    for extension in resulting_extensions {
        output.insert(extension.pname.clone(), json!(extension));
    }

    println!(
        "{}",
        serde_json::to_string_pretty(&serde_json::Value::Object(output)).expect(
            "This is constructed from json, it should also be possible to serialize it again"
        )
    );
    Ok(())
}

async fn process_extensions(extensions: Vec<InputExtension>) -> anyhow::Result<Vec<Extension>> {
    let mut output = Vec::with_capacity(extensions.len());

    let client = Client::new();
    for extension in extensions
        .iter()
        .map(|ext| {
            let local_client = &client;
            index_extension(ext, local_client)
        })
        .collect::<FuturesUnordered<_>>()
        .collect::<Vec<_>>()
        .await
    {
        output.push(extension?);
    }
    Ok(output)
}

async fn index_extension(extension: &InputExtension, client: &Client) -> anyhow::Result<Extension> {
    let response = client
        .get(format!(
            "https://addons.mozilla.org/api/v5/addons/addon/{}",
            extension,
        ))
        .send()
        .await
        .context("Accessing the mozzila extenios api failed with error: {e}")?;

    eprintln!("Indexing {} ({})...", extension, response.status());
    let response: Value = serde_json::from_str(
        &response
            .text()
            .await
            .context("Turning the response to text fail with error: {e}")?,
    )
    .context("Deserializing the response failed! Error: {e}")?;

    if let Some(detail) = response.get("detail") {
        if detail == "Not found." {
            bail!("Your extension ('{}') was not found!", extension);
        }
    };

    let release = { get_json_value!("current_version", response, is_object, as_object) };

    #[allow(non_snake_case)]
    let addonId = { get_json_value!("guid", response, is_string, as_str) };

    let version = { get_json_value!("version", release, is_string, as_str) };
    let file = { get_json_value!("file", release, is_object, as_object) };

    let url = { get_json_value!("url", file, is_string, as_str) };
    let sha256 = {
        let hash = get_json_value!("hash", file, is_string, as_str);
        if hash.starts_with("sha256:") {
            hash
        } else {
            bail!("This hash type is unhandled: {}", hash);
        }
    };

    Ok(Extension {
        pname: extension.moz_name.clone(),
        default_area: extension.default_area,
        version,
        addonId,
        url,
        sha256,
    })
}