use structopt::{clap::Shell, StructOpt};

use garage_util::version::garage_version;

use crate::cli::local::convert_db;

#[derive(StructOpt, Debug)]
pub enum Command {
	/// Run Garage server
	#[structopt(name = "server", version = garage_version())]
	Server(ServerOpt),

	/// Check the cluster health and set the exit code to 1 if it is unavailable
	#[structopt(name = "health", version = garage_version())]
	Health(HealthOpt),

	/// Get network status
	#[structopt(name = "status", version = garage_version())]
	Status,

	/// Operations on individual Garage nodes
	#[structopt(name = "node", version = garage_version())]
	Node(NodeOperation),

	/// Operations on the assignment of node roles in the cluster layout
	#[structopt(name = "layout", version = garage_version())]
	Layout(LayoutOperation),

	/// Operations on buckets
	#[structopt(name = "bucket", version = garage_version())]
	Bucket(BucketOperation),

	/// Operations on S3 access keys
	#[structopt(name = "key", version = garage_version())]
	Key(KeyOperation),

	/// Operations on admin API tokens
	#[structopt(name = "admin-token", version = garage_version())]
	AdminToken(AdminTokenOperation),

	/// Start repair of node data on remote node
	#[structopt(name = "repair", version = garage_version())]
	Repair(RepairOpt),

	/// Offline reparation of node data (these repairs must be run offline
	/// directly on the server node)
	#[structopt(name = "offline-repair", version = garage_version())]
	OfflineRepair(OfflineRepairOpt),

	/// Gather node statistics
	#[structopt(name = "stats", version = garage_version())]
	Stats(StatsOpt),

	/// Manage background workers
	#[structopt(name = "worker", version = garage_version())]
	Worker(WorkerOperation),

	/// Low-level node-local debug operations on data blocks
	#[structopt(name = "block", version = garage_version())]
	Block(BlockOperation),

	/// Operations on the metadata db
	#[structopt(name = "meta", version = garage_version())]
	Meta(MetaOperation),

	/// Convert metadata db between database engine formats
	#[structopt(name = "convert-db", version = garage_version())]
	ConvertDb(convert_db::ConvertDbOpt),

	/// Output openapi JSON schema for admin api
	#[structopt(name = "admin-api-schema", version = garage_version(), setting(structopt::clap::AppSettings::Hidden))]
	AdminApiSchema,

	/// Directly invoke the admin API using a JSON payload.
	/// The result is printed to `stdout` in JSON format.
	#[structopt(name = "json-api", version = garage_version())]
	JsonApi {
		/// The admin API endpoint to invoke, e.g. `GetClusterStatus`
		endpoint: String,
		/// The JSON payload, or `-` to read from `stdin`
		#[structopt(default_value = "null")]
		payload: String,
	},

	/// Generate completions for a shell
	#[structopt(name = "completions", version = garage_version())]
	Completions { shell: Shell },
}

// ---------------------------
// ---- garage server ... ----
// ---------------------------

#[derive(StructOpt, Debug)]
pub struct ServerOpt {
	/// Automatically configure a single-node layout in the cluster.
	/// Garage will refuse to run if the cluster already has other nodes.
	#[structopt(long = "single-node")]
	pub(crate) single_node: bool,
	/// Configure a default S3 API key using environment variables `GARAGE_DEFAULT_ACCESS_KEY` and
	/// `GARAGE_DEFAULT_SECRET_KEY`. Requires `--single-node`.
	#[structopt(long = "default-access-key")]
	pub(crate) default_access_key: bool,
	/// Configure a default bucket using environment variable `GARAGE_DEFAULT_BUCKET`.
	/// Implies `--default-access-key`. Requires `--single-node`.
	#[structopt(long = "default-bucket")]
	pub(crate) default_bucket: bool,
}

// -------------------------
// ---- garage health ----
// -------------------------

#[derive(StructOpt, Debug)]
pub struct HealthOpt {
	/// Do not print healthyness to stdout
	#[structopt(short = "q", long = "quiet")]
	pub(crate) quiet: bool,
}

// -------------------------
// ---- garage node ... ----
// -------------------------

#[derive(StructOpt, Debug)]
pub enum NodeOperation {
	/// Print the full node ID (public key) of this Garage node, and its publicly reachable IP
	/// address and port if they are specified in config file under `rpc_public_addr`
	#[structopt(name = "id", version = garage_version())]
	NodeId(NodeIdOpt),

	/// Connect to Garage node that is currently isolated from the system
	#[structopt(name = "connect", version = garage_version())]
	Connect(ConnectNodeOpt),
}

#[derive(StructOpt, Debug)]
pub struct NodeIdOpt {
	/// Do not print usage instructions to stderr
	#[structopt(short = "q", long = "quiet")]
	pub(crate) quiet: bool,
}

#[derive(StructOpt, Debug)]
pub struct ConnectNodeOpt {
	/// Full node ID (public key) and IP address and port, in the format:
	/// `<full node ID>@<ip or hostname>:<port>`.
	/// You can retrieve this information on the target node using `garage node id`.
	pub(crate) node: String,
}

// ---------------------------
// ---- garage layout ... ----
// ---------------------------

#[derive(StructOpt, Debug)]
pub enum LayoutOperation {
	/// Assign role to Garage node
	#[structopt(name = "assign", version = garage_version())]
	Assign(AssignRoleOpt),

	/// Remove role from Garage cluster node
	#[structopt(name = "remove", version = garage_version())]
	Remove(RemoveRoleOpt),

	/// Configure parameters value for the layout computation
	#[structopt(name = "config", version = garage_version())]
	Config(ConfigLayoutOpt),

	/// Show roles currently assigned to nodes and changes staged for commit
	#[structopt(name = "show", version = garage_version())]
	Show,

	/// Apply staged changes to cluster layout
	#[structopt(name = "apply", version = garage_version())]
	Apply(ApplyLayoutOpt),

	/// Revert staged changes to cluster layout
	#[structopt(name = "revert", version = garage_version())]
	Revert(RevertLayoutOpt),

	/// View the history of layouts in the cluster
	#[structopt(name = "history", version = garage_version())]
	History,

	/// Skip dead nodes when awaiting for a new layout version to be synchronized
	#[structopt(name = "skip-dead-nodes", version = garage_version())]
	SkipDeadNodes(SkipDeadNodesOpt),
}

#[derive(StructOpt, Debug)]
pub struct AssignRoleOpt {
	/// Node(s) to which to assign role (prefix of hexadecimal node id)
	#[structopt(required = true)]
	pub(crate) node_ids: Vec<String>,

	/// Location (zone or datacenter) of the node
	#[structopt(short = "z", long = "zone")]
	pub(crate) zone: Option<String>,

	/// Storage capacity, in bytes (supported suffixes: B, KB, MB, GB, TB, PB)
	#[structopt(short = "c", long = "capacity")]
	pub(crate) capacity: Option<bytesize::ByteSize>,

	/// Gateway-only node
	#[structopt(short = "g", long = "gateway")]
	pub(crate) gateway: bool,

	/// Optional tags to add to node
	#[structopt(short = "t", long = "tag")]
	pub(crate) tags: Vec<String>,

	/// Replaced node(s): list of node IDs that will be removed from the current cluster
	#[structopt(long = "replace")]
	pub(crate) replace: Vec<String>,
}

#[derive(StructOpt, Debug)]
pub struct RemoveRoleOpt {
	/// Node whose role to remove (prefix of hexadecimal node id)
	pub(crate) node_id: String,
}

#[derive(StructOpt, Debug)]
pub struct ConfigLayoutOpt {
	/// Zone redundancy parameter ('none'/'max' or integer)
	#[structopt(short = "r", long = "redundancy")]
	pub(crate) redundancy: Option<String>,
}

#[derive(StructOpt, Debug)]
pub struct ApplyLayoutOpt {
	/// Version number of new configuration: this command will fail if
	/// it is not exactly 1 + the previous configuration's version
	#[structopt(long = "version")]
	pub(crate) version: Option<u64>,
}

#[derive(StructOpt, Debug)]
pub struct RevertLayoutOpt {
	/// The revert operation will not be ran unless this flag is added
	#[structopt(long = "yes")]
	pub(crate) yes: bool,
}

#[derive(StructOpt, Debug)]
pub struct SkipDeadNodesOpt {
	/// Version number of the layout to assume is currently up-to-date.
	/// This will generally be the current layout version.
	#[structopt(long = "version")]
	pub(crate) version: u64,
	/// Allow the skip even if a quorum of nodes could not be found for
	/// the data among the remaining nodes
	#[structopt(long = "allow-missing-data")]
	pub(crate) allow_missing_data: bool,
}

// ---------------------------
// ---- garage bucket ... ----
// ---------------------------

#[derive(StructOpt, Debug)]
pub enum BucketOperation {
	/// List buckets
	#[structopt(name = "list", version = garage_version())]
	List,

	/// Get bucket info
	#[structopt(name = "info", version = garage_version())]
	Info(BucketOpt),

	/// Create bucket
	#[structopt(name = "create", version = garage_version())]
	Create(BucketOpt),

	/// Delete bucket
	#[structopt(name = "delete", version = garage_version())]
	Delete(DeleteBucketOpt),

	/// Alias bucket under new name
	#[structopt(name = "alias", version = garage_version())]
	Alias(AliasBucketOpt),

	/// Remove bucket alias
	#[structopt(name = "unalias", version = garage_version())]
	Unalias(UnaliasBucketOpt),

	/// Allow key to read or write to bucket
	#[structopt(name = "allow", version = garage_version())]
	Allow(PermBucketOpt),

	/// Deny key from reading or writing to bucket
	#[structopt(name = "deny", version = garage_version())]
	Deny(PermBucketOpt),

	/// Expose as website or not
	#[structopt(name = "website", version = garage_version())]
	Website(WebsiteOpt),

	/// Set the quotas for this bucket
	#[structopt(name = "set-quotas", version = garage_version())]
	SetQuotas(SetQuotasOpt),

	/// Clean up (abort) old incomplete multipart uploads
	#[structopt(name = "cleanup-incomplete-uploads", version = garage_version())]
	CleanupIncompleteUploads(CleanupIncompleteUploadsOpt),

	/// Inspect an object in a bucket
	#[structopt(name = "inspect-object", version = garage_version())]
	InspectObject(InspectObjectOpt),
}

#[derive(StructOpt, Debug)]
pub struct WebsiteOpt {
	/// Create
	#[structopt(long = "allow")]
	pub allow: bool,

	/// Delete
	#[structopt(long = "deny")]
	pub deny: bool,

	/// Bucket name
	pub bucket: String,

	/// Index document: the suffix appended to request paths ending by /
	#[structopt(short = "i", long = "index-document", default_value = "index.html")]
	pub index_document: String,

	/// Error document: the optional document returned when an error occurs
	#[structopt(short = "e", long = "error-document")]
	pub error_document: Option<String>,
}

#[derive(StructOpt, Debug)]
pub struct BucketOpt {
	/// Bucket name
	pub name: String,
}

#[derive(StructOpt, Debug)]
pub struct DeleteBucketOpt {
	/// Bucket name
	pub name: String,

	/// If this flag is not given, the bucket won't be deleted
	#[structopt(long = "yes")]
	pub yes: bool,
}

#[derive(StructOpt, Debug)]
pub struct AliasBucketOpt {
	/// Existing bucket name (its alias in global namespace or its full hex uuid)
	pub existing_bucket: String,

	/// New bucket name
	pub new_name: String,

	/// Make this alias local to the specified access key
	#[structopt(long = "local")]
	pub local: Option<String>,
}

#[derive(StructOpt, Debug)]
pub struct UnaliasBucketOpt {
	/// Bucket name
	pub name: String,

	/// Unalias in bucket namespace local to this access key
	#[structopt(long = "local")]
	pub local: Option<String>,
}

#[derive(StructOpt, Debug)]
pub struct PermBucketOpt {
	/// Access key name or ID
	#[structopt(long = "key")]
	pub key_pattern: String,

	/// Allow/deny read operations
	#[structopt(long = "read")]
	pub read: bool,

	/// Allow/deny write operations
	#[structopt(long = "write")]
	pub write: bool,

	/// Allow/deny administrative operations operations
	/// (such as deleting bucket or changing bucket website configuration)
	#[structopt(long = "owner")]
	pub owner: bool,

	/// Bucket name
	pub bucket: String,
}

#[derive(StructOpt, Debug)]
pub struct SetQuotasOpt {
	/// Bucket name
	pub bucket: String,

	/// Set a maximum size for the bucket (specify a size e.g. in MiB or GiB,
	/// or `none` for no size restriction)
	#[structopt(long = "max-size")]
	pub max_size: Option<String>,

	/// Set a maximum number of objects for the bucket (or `none` for no restriction)
	#[structopt(long = "max-objects")]
	pub max_objects: Option<String>,
}

#[derive(StructOpt, Debug)]
pub struct CleanupIncompleteUploadsOpt {
	/// Abort multipart uploads older than this value
	#[structopt(long = "older-than", default_value = "1d")]
	pub older_than: String,

	/// Name of bucket(s) to clean up
	#[structopt(required = true)]
	pub buckets: Vec<String>,
}

#[derive(StructOpt, Debug)]
pub struct InspectObjectOpt {
	/// Name or ID of bucket
	pub bucket: String,
	/// Key of object to inspect
	pub key: String,
}

// ------------------------
// ---- garage key ... ----
// ------------------------

#[derive(StructOpt, Debug)]
pub enum KeyOperation {
	/// List keys
	#[structopt(name = "list", version = garage_version())]
	List,

	/// Get key info
	#[structopt(name = "info", version = garage_version())]
	Info(KeyInfoOpt),

	/// Create new key
	#[structopt(name = "create", version = garage_version())]
	Create(KeyNewOpt),

	/// Rename key
	#[structopt(name = "rename", version = garage_version())]
	Rename(KeyRenameOpt),

	/// Delete key
	#[structopt(name = "delete", version = garage_version())]
	Delete(KeyDeleteOpt),

	/// Set permission flags for key
	#[structopt(name = "allow", version = garage_version())]
	Allow(KeyPermOpt),

	/// Unset permission flags for key
	#[structopt(name = "deny", version = garage_version())]
	Deny(KeyPermOpt),

	/// Import key
	#[structopt(name = "import", version = garage_version())]
	Import(KeyImportOpt),

	/// Set parameters for an access key
	#[structopt(name = "set", version = garage_version())]
	Set(KeySetOpt),

	/// Delete all expired access keys
	#[structopt(name = "delete-expired", version = garage_version())]
	DeleteExpired {
		/// Confirm deletion
		#[structopt(long = "yes")]
		yes: bool,
	},
}

#[derive(StructOpt, Debug)]
pub struct KeyInfoOpt {
	/// ID or name of the key
	pub key_pattern: String,
	/// Whether to display the secret key
	#[structopt(long = "show-secret")]
	pub show_secret: bool,
}

#[derive(StructOpt, Debug)]
pub struct KeyNewOpt {
	/// Name of the key
	#[structopt(default_value = "Unnamed key")]
	pub name: String,
	/// Set an expiration time for the access key
	/// (see `docs.rs/fundu-systemd` for date format)
	#[structopt(long = "expires-in")]
	pub expires_in: Option<String>,
}

#[derive(StructOpt, Debug)]
pub struct KeySetOpt {
	/// ID or name of the key
	pub key_pattern: String,

	/// Set an expiration time for the access key
	/// (see `docs.rs/fundu-systemd` for date format)
	#[structopt(long = "expires-in")]
	pub expires_in: Option<String>,
	/// Set the access key to never expire
	#[structopt(long = "never-expires")]
	pub never_expires: bool,
}

#[derive(StructOpt, Debug)]
pub struct KeyRenameOpt {
	/// ID or name of the key
	pub key_pattern: String,

	/// New name of the key
	pub new_name: String,
}

#[derive(StructOpt, Debug)]
pub struct KeyDeleteOpt {
	/// ID or name of the key
	pub key_pattern: String,

	/// Confirm deletion
	#[structopt(long = "yes")]
	pub yes: bool,
}

#[derive(StructOpt, Debug)]
pub struct KeyPermOpt {
	/// ID or name of the key
	pub key_pattern: String,

	/// Flag that allows key to create buckets using S3's `CreateBucket` call
	#[structopt(long = "create-bucket")]
	pub create_bucket: bool,
}

#[derive(StructOpt, Debug)]
pub struct KeyImportOpt {
	/// Access key ID
	pub key_id: String,

	/// Secret access key
	pub secret_key: String,

	/// Key name
	#[structopt(short = "n", default_value = "Imported key")]
	pub name: String,

	/// Confirm key import
	#[structopt(long = "yes")]
	pub yes: bool,
}

// --------------------------------
// ---- garage admin-token ... ----
// --------------------------------

#[derive(StructOpt, Debug)]
pub enum AdminTokenOperation {
	/// List all admin API tokens
	#[structopt(name = "list", version = garage_version())]
	List,

	/// Fetch info about a specific admin API token
	#[structopt(name = "info", version = garage_version())]
	Info {
		/// Name or prefix of the ID of the token to look up
		api_token: String,
	},

	/// Create new admin API token
	#[structopt(name = "create", version = garage_version())]
	Create(AdminTokenCreateOp),

	/// Rename an admin API token
	#[structopt(name = "rename", version = garage_version())]
	Rename {
		/// Name or prefix of the ID of the token to rename
		api_token: String,
		/// New name of the admintoken
		new_name: String,
	},

	/// Set parameters for an admin API token
	#[structopt(name = "set", version = garage_version())]
	Set(AdminTokenSetOp),

	/// Delete an admin API token
	#[structopt(name = "delete", version = garage_version())]
	Delete {
		/// Name or prefix of the ID of the token to delete
		api_token: String,
		/// Confirm deletion
		#[structopt(long = "yes")]
		yes: bool,
	},

	/// Delete all expired admin API tokens
	#[structopt(name = "delete-expired", version = garage_version())]
	DeleteExpired {
		/// Confirm deletion
		#[structopt(long = "yes")]
		yes: bool,
	},
}

#[derive(StructOpt, Debug, Clone)]
pub struct AdminTokenCreateOp {
	/// Set a name for the token
	pub name: Option<String>,
	/// Set an expiration time for the token (see `docs.rs/fundu-systemd` for date
	/// format)
	#[structopt(long = "expires-in")]
	pub expires_in: Option<String>,
	/// Set a limited scope for the token, as a comma-separated list of
	/// admin API functions (e.g. `GetClusterStatus`, etc.). The default scope
	/// is `*`, which allows access to all admin API functions.
	/// Note that granting a scope that allows `CreateAdminToken` or
	/// `UpdateAdminToken` allows for privilege escalation, and is therefore
	/// equivalent to `*`.
	#[structopt(long = "scope")]
	pub scope: Option<String>,
	/// Print only the newly generated API token to stdout
	#[structopt(short = "q", long = "quiet")]
	pub quiet: bool,
}

#[derive(StructOpt, Debug, Clone)]
pub struct AdminTokenSetOp {
	/// Name or prefix of the ID of the token to modify
	pub api_token: String,

	/// Set an expiration time for the token (see `docs.rs/fundu-systemd` for date
	/// format)
	#[structopt(long = "expires-in")]
	pub expires_in: Option<String>,
	/// Set the token to never expire
	#[structopt(long = "never-expires")]
	pub never_expires: bool,

	/// Set a limited scope for the token, as a comma-separated list of
	/// admin API functions (e.g. `GetClusterStatus`, etc.), or `*` to allow
	/// all admin API functions.
	/// Use `--scope=+Scope1,Scope2` to add scopes to the existing list,
	/// and `--scope=-Scope1,Scope2` to remove scopes from the existing list.
	/// Note that granting a scope that allows `CreateAdminToken` or
	/// `UpdateAdminToken` allows for privilege escalation, and is therefore
	/// equivalent to `*`.
	#[structopt(long = "scope")]
	pub scope: Option<String>,
}

// ---------------------------
// ---- garage repair ... ----
// ---------------------------

#[derive(StructOpt, Debug, Clone)]
pub struct RepairOpt {
	/// Launch repair operation on all nodes
	#[structopt(short = "a", long = "all-nodes")]
	pub all_nodes: bool,

	/// Confirm the launch of the repair operation
	#[structopt(long = "yes")]
	pub yes: bool,

	#[structopt(subcommand)]
	pub what: RepairWhat,
}

#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
pub enum RepairWhat {
	/// Do a full sync of metadata tables
	#[structopt(name = "tables", version = garage_version())]
	Tables,
	/// Repair (resync/rebalance) the set of stored blocks in the cluster
	#[structopt(name = "blocks", version = garage_version())]
	Blocks,
	/// Clear the block resync queue. The list of blocks in errored state
	/// is cleared as well. You MUST run `garage repair blocks` after invoking this.
	#[structopt(name = "clear-resync-queue", version = garage_version())]
	ClearResyncQueue,
	/// Repropagate object deletions to the version table
	#[structopt(name = "versions", version = garage_version())]
	Versions,
	/// Repropagate object deletions to the multipart upload table
	#[structopt(name = "mpu", version = garage_version())]
	MultipartUploads,
	/// Repropagate version deletions to the block ref table
	#[structopt(name = "block-refs", version = garage_version())]
	BlockRefs,
	/// Recalculate block reference counters
	#[structopt(name = "block-rc", version = garage_version())]
	BlockRc,
	/// Fix inconsistency in bucket aliases (WARNING: EXPERIMENTAL)
	#[structopt(name = "aliases", version = garage_version())]
	Aliases,
	/// Verify integrity of all blocks on disc
	#[structopt(name = "scrub", version = garage_version())]
	Scrub {
		#[structopt(subcommand)]
		cmd: ScrubCmd,
	},
	/// Rebalance data blocks among HDDs on individual nodes
	#[structopt(name = "rebalance", version = garage_version())]
	Rebalance,
}

#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
pub enum ScrubCmd {
	/// Start scrub
	#[structopt(name = "start", version = garage_version())]
	Start,
	/// Pause scrub (it will resume automatically after 24 hours)
	#[structopt(name = "pause", version = garage_version())]
	Pause,
	/// Resume paused scrub
	#[structopt(name = "resume", version = garage_version())]
	Resume,
	/// Cancel scrub in progress
	#[structopt(name = "cancel", version = garage_version())]
	Cancel,
}

// -----------------------------------
// ---- garage offline-repair ... ----
// -----------------------------------

#[derive(StructOpt, Debug, Clone)]
pub struct OfflineRepairOpt {
	/// Confirm the launch of the repair operation
	#[structopt(long = "yes")]
	pub yes: bool,

	#[structopt(subcommand)]
	pub what: OfflineRepairWhat,
}

#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
pub enum OfflineRepairWhat {
	/// Repair K2V item counters
	#[cfg(feature = "k2v")]
	#[structopt(name = "k2v_item_counters", version = garage_version())]
	K2VItemCounters,
	/// Repair object counters
	#[structopt(name = "object_counters", version = garage_version())]
	ObjectCounters,
}

// --------------------------
// ---- garage stats ... ----
// --------------------------

#[derive(StructOpt, Debug, Clone)]
pub struct StatsOpt {
	/// Gather statistics from all nodes
	#[structopt(short = "a", long = "all-nodes")]
	pub all_nodes: bool,
}

// ---------------------------
// ---- garage worker ... ----
// ---------------------------

#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
pub enum WorkerOperation {
	/// List all workers on Garage node
	#[structopt(name = "list", version = garage_version())]
	List {
		#[structopt(flatten)]
		opt: WorkerListOpt,
	},
	/// Get detailed information about a worker
	#[structopt(name = "info", version = garage_version())]
	Info { tid: usize },
	/// Get worker parameter
	#[structopt(name = "get", version = garage_version())]
	Get {
		/// Gather variable values from all nodes
		#[structopt(short = "a", long = "all-nodes")]
		all_nodes: bool,
		/// Variable name to get, or none to get all variables
		variable: Option<String>,
	},
	/// Set worker parameter
	#[structopt(name = "set", version = garage_version())]
	Set {
		/// Set variable values on all nodes
		#[structopt(short = "a", long = "all-nodes")]
		all_nodes: bool,
		/// Variable node to set
		variable: String,
		/// Value to set the variable to
		value: String,
	},
}

#[derive(StructOpt, Debug, Eq, PartialEq, Clone, Copy)]
pub struct WorkerListOpt {
	/// Show only busy workers
	#[structopt(short = "b", long = "busy")]
	pub busy: bool,
	/// Show only workers with errors
	#[structopt(short = "e", long = "errors")]
	pub errors: bool,
}

// --------------------------
// ---- garage block ... ----
// --------------------------

#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
pub enum BlockOperation {
	/// List all blocks that currently have a resync error
	#[structopt(name = "list-errors", version = garage_version())]
	ListErrors,
	/// Get detailed information about a single block
	#[structopt(name = "info", version = garage_version())]
	Info {
		/// Hash of the block for which to retrieve information
		hash: String,
	},
	/// Retry now the resync of one or many blocks
	#[structopt(name = "retry-now", version = garage_version())]
	RetryNow {
		/// Retry all blocks that have a resync error
		#[structopt(long = "all")]
		all: bool,
		/// Hashes of the block to retry to resync now
		blocks: Vec<String>,
	},
	/// Delete all objects referencing a missing block
	#[structopt(name = "purge", version = garage_version())]
	Purge {
		/// Mandatory to confirm this operation
		#[structopt(long = "yes")]
		yes: bool,
		/// Hashes of the block to purge
		#[structopt(required = true)]
		blocks: Vec<String>,
	},
}

// -------------------------
// ---- garage meta ... ----
// -------------------------

#[derive(StructOpt, Debug, Eq, PartialEq, Clone, Copy)]
pub enum MetaOperation {
	/// Save a snapshot of the metadata db file
	#[structopt(name = "snapshot", version = garage_version())]
	Snapshot {
		/// Run on all nodes instead of only local node
		#[structopt(long = "all")]
		all: bool,
	},
}
