use format_table::format_table;

use garage_util::error::*;

use garage_api_admin::api::*;

use crate::cli::structs::*;
use crate::cli_v2::*;

impl Cli {
	pub async fn cmd_key(&self, cmd: KeyOperation) -> Result<(), Error> {
		match cmd {
			KeyOperation::List => self.cmd_list_keys().await,
			KeyOperation::Info(query) => self.cmd_key_info(query).await,
			KeyOperation::Create(query) => self.cmd_create_key(query).await,
			KeyOperation::Rename(query) => self.cmd_rename_key(query).await,
			KeyOperation::Delete(query) => self.cmd_delete_key(query).await,
			KeyOperation::Allow(query) => self.cmd_allow_key(query).await,
			KeyOperation::Deny(query) => self.cmd_deny_key(query).await,
			KeyOperation::Import(query) => self.cmd_import_key(query).await,
		}
	}

	pub async fn cmd_list_keys(&self) -> Result<(), Error> {
		let keys = self.api_request(ListKeysRequest).await?;

		println!("List of keys:");
		let mut table = vec![];
		for key in keys.0.iter() {
			table.push(format!("\t{}\t{}", key.id, key.name));
		}
		format_table(table);

		Ok(())
	}

	pub async fn cmd_key_info(&self, opt: KeyInfoOpt) -> Result<(), Error> {
		let key = self
			.api_request(GetKeyInfoRequest {
				id: None,
				search: Some(opt.key_pattern),
				show_secret_key: opt.show_secret,
			})
			.await?;

		print_key_info(&key);

		Ok(())
	}

	pub async fn cmd_create_key(&self, opt: KeyNewOpt) -> Result<(), Error> {
		let key = self
			.api_request(CreateKeyRequest {
				name: Some(opt.name),
			})
			.await?;

		print_key_info(&key.0);

		Ok(())
	}

	pub async fn cmd_rename_key(&self, opt: KeyRenameOpt) -> Result<(), Error> {
		let key = self
			.api_request(GetKeyInfoRequest {
				id: None,
				search: Some(opt.key_pattern),
				show_secret_key: false,
			})
			.await?;

		let new_key = self
			.api_request(UpdateKeyRequest {
				id: key.access_key_id,
				body: UpdateKeyRequestBody {
					name: Some(opt.new_name),
					allow: None,
					deny: None,
				},
			})
			.await?;

		print_key_info(&new_key.0);

		Ok(())
	}

	pub async fn cmd_delete_key(&self, opt: KeyDeleteOpt) -> Result<(), Error> {
		let key = self
			.api_request(GetKeyInfoRequest {
				id: None,
				search: Some(opt.key_pattern),
				show_secret_key: false,
			})
			.await?;

		if !opt.yes {
			println!("About to delete key {}...", key.access_key_id);
			return Err(Error::Message(
				"Add --yes flag to really perform this operation".to_string(),
			));
		}

		self.api_request(DeleteKeyRequest {
			id: key.access_key_id.clone(),
		})
		.await?;

		println!("Access key {} has been deleted.", key.access_key_id);

		Ok(())
	}

	pub async fn cmd_allow_key(&self, opt: KeyPermOpt) -> Result<(), Error> {
		let key = self
			.api_request(GetKeyInfoRequest {
				id: None,
				search: Some(opt.key_pattern),
				show_secret_key: false,
			})
			.await?;

		let new_key = self
			.api_request(UpdateKeyRequest {
				id: key.access_key_id,
				body: UpdateKeyRequestBody {
					name: None,
					allow: Some(KeyPerm {
						create_bucket: opt.create_bucket,
					}),
					deny: None,
				},
			})
			.await?;

		print_key_info(&new_key.0);

		Ok(())
	}

	pub async fn cmd_deny_key(&self, opt: KeyPermOpt) -> Result<(), Error> {
		let key = self
			.api_request(GetKeyInfoRequest {
				id: None,
				search: Some(opt.key_pattern),
				show_secret_key: false,
			})
			.await?;

		let new_key = self
			.api_request(UpdateKeyRequest {
				id: key.access_key_id,
				body: UpdateKeyRequestBody {
					name: None,
					allow: None,
					deny: Some(KeyPerm {
						create_bucket: opt.create_bucket,
					}),
				},
			})
			.await?;

		print_key_info(&new_key.0);

		Ok(())
	}

	pub async fn cmd_import_key(&self, opt: KeyImportOpt) -> Result<(), Error> {
		if !opt.yes {
			return Err(Error::Message("This command is intended to re-import keys that were previously generated by Garage. If you want to create a new key, use `garage key new` instead. Add the --yes flag if you really want to re-import a key.".to_string()));
		}

		let new_key = self
			.api_request(ImportKeyRequest {
				name: Some(opt.name),
				access_key_id: opt.key_id,
				secret_access_key: opt.secret_key,
			})
			.await?;

		print_key_info(&new_key.0);

		Ok(())
	}
}

fn print_key_info(key: &GetKeyInfoResponse) {
	println!("Key name: {}", key.name);
	println!("Key ID: {}", key.access_key_id);
	println!(
		"Secret key: {}",
		key.secret_access_key.as_deref().unwrap_or("(redacted)")
	);
	println!("Can create buckets: {}", key.permissions.create_bucket);

	println!("\nKey-specific bucket aliases:");
	let mut table = vec![];
	for bucket in key.buckets.iter() {
		for la in bucket.local_aliases.iter() {
			table.push(format!(
				"\t{}\t{}\t{}",
				la,
				bucket.global_aliases.join(","),
				bucket.id
			));
		}
	}
	format_table(table);

	println!("\nAuthorized buckets:");
	let mut table = vec![];
	for bucket in key.buckets.iter() {
		let rflag = if bucket.permissions.read { "R" } else { " " };
		let wflag = if bucket.permissions.write { "W" } else { " " };
		let oflag = if bucket.permissions.owner { "O" } else { " " };
		table.push(format!(
			"\t{}{}{}\t{}\t{}\t{:.16}",
			rflag,
			wflag,
			oflag,
			bucket.global_aliases.join(","),
			bucket.local_aliases.join(","),
			bucket.id
		));
	}
	format_table(table);
}
