use oci_client::{annotations, secrets::RegistryAuth, Client, Reference};

use docker_credential::{CredentialRetrievalError, DockerCredential};
use std::collections::BTreeMap;
use tracing::{debug, warn};
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};

mod cli;
use clap::Parser;
use cli::Cli;

mod pull;
use pull::pull_wasm;

mod push;
use push::push_wasm;

fn build_auth(reference: &Reference, cli: &Cli) -> RegistryAuth {
    let server = reference
        .resolve_registry()
        .strip_suffix('/')
        .unwrap_or_else(|| reference.resolve_registry());

    if cli.anonymous {
        return RegistryAuth::Anonymous;
    }

    match docker_credential::get_credential(server) {
        Err(CredentialRetrievalError::ConfigNotFound) => RegistryAuth::Anonymous,
        Err(CredentialRetrievalError::NoCredentialConfigured) => RegistryAuth::Anonymous,
        Err(e) => panic!("Error handling docker configuration file: {e}"),
        Ok(DockerCredential::UsernamePassword(username, password)) => {
            debug!("Found docker credentials");
            RegistryAuth::Basic(username, password)
        }
        Ok(DockerCredential::IdentityToken(_)) => {
            warn!("Cannot use contents of docker config, identity token not supported. Using anonymous auth");
            RegistryAuth::Anonymous
        }
    }
}

fn build_client_config(cli: &Cli) -> oci_client::client::ClientConfig {
    let protocol = if cli.insecure {
        oci_client::client::ClientProtocol::Http
    } else {
        oci_client::client::ClientProtocol::Https
    };

    oci_client::client::ClientConfig {
        protocol,
        ..Default::default()
    }
}

#[tokio::main]
pub async fn main() {
    let cli = Cli::parse();

    // setup logging
    let level_filter = if cli.verbose { "debug" } else { "info" };
    let filter_layer = EnvFilter::new(level_filter);
    tracing_subscriber::registry()
        .with(filter_layer)
        .with(fmt::layer().with_writer(std::io::stderr))
        .init();

    let client_config = build_client_config(&cli);
    let mut client = Client::new(client_config);

    match &cli.command {
        crate::cli::Commands::Pull { output, image } => {
            let reference: Reference = image.parse().expect("Not a valid image reference");
            let auth = build_auth(&reference, &cli);
            pull_wasm(&mut client, &auth, &reference, output).await;
        }
        crate::cli::Commands::Push {
            module,
            image,
            annotations,
        } => {
            let reference: Reference = image.parse().expect("Not a valid image reference");
            let auth = build_auth(&reference, &cli);

            let annotations = if annotations.is_empty() {
                let mut values: BTreeMap<String, String> = BTreeMap::new();
                values.insert(
                    annotations::ORG_OPENCONTAINERS_IMAGE_TITLE.to_string(),
                    module.clone(),
                );
                Some(values)
            } else {
                let mut values: BTreeMap<String, String> = BTreeMap::new();
                for annotation in annotations {
                    let tmp: Vec<_> = annotation.splitn(2, '=').collect();
                    if tmp.len() == 2 {
                        values.insert(String::from(tmp[0]), String::from(tmp[1]));
                    }
                }
                values
                    .entry(annotations::ORG_OPENCONTAINERS_IMAGE_TITLE.to_string())
                    .or_insert_with(|| module.clone());

                Some(values)
            };

            push_wasm(&mut client, &auth, &reference, module, annotations).await;
        }
    }
}
