use std::path::PathBuf;

use clap::{Args, Parser, Subcommand};

pub const DEFAULT_PAYLOAD_BYTES: usize = 1024 * 1024 * 1024;
pub const DEFAULT_CHUNK_BYTES: usize = 8 * 1024 * 1024;
pub const DEFAULT_MEMFD_BYTES: u64 = 5 * 1024 * 1024 * 1024;
pub const DEFAULT_MARKER: &str = "TRACER_BULLET_SUCCESS";
pub const DEFAULT_RECORDS: u64 = 1_000_000;
pub const DEFAULT_MODULE_D_TARGET_PATH: &str = "/dev/tracer-block";
pub const DEFAULT_MODULE_D_GROUP_BYTES: usize = 16 * 1024 * 1024;
pub const DEFAULT_MODULE_D_REQUEST_BYTES: usize = 256 * 1024;
pub const DEFAULT_MODULE_D_PRODUCERS: usize = 4;
pub const DEFAULT_MODULE_D_SEED: u64 = 0xD00D_FEED_CAFE_BABE;
pub const DEFAULT_MODULE_E_INDEX_PATH: &str = "/tmp/tracer-bullet-module-e.fst";
pub const DEFAULT_MODULE_F_LISTEN_ADDR: &str = "0.0.0.0:8080";
pub const DEFAULT_MODULE_F_TARGET_PATH: &str = "/dev/tracer-block";
pub const DEFAULT_MODULE_F_WORKERS: usize = 8;
pub const DEFAULT_MODULE_F_GROUP_BYTES: usize = 16 * 1024 * 1024;
pub const DEFAULT_MODULE_F_COMPACTION_THRESHOLD: usize = 250_000;
pub const DEFAULT_MODULE_F_FLUSH_INTERVAL_MS: u64 = 8;
pub const DEFAULT_MODULE_F_BUCKET: &str = "nexus";
pub const DEFAULT_MODULE_F_ACCESS_KEY: &str = "AKIAtracerbullet";
pub const DEFAULT_MODULE_F_SECRET_KEY: &str = "SECRETtracerbullet";

#[cfg(feature = "dev-mode")]
pub const DEFAULT_MODULE_D_TOTAL_BYTES: u64 = 2 * 1024 * 1024 * 1024;
#[cfg(not(feature = "dev-mode"))]
pub const DEFAULT_MODULE_D_TOTAL_BYTES: u64 = 50 * 1024 * 1024 * 1024;

#[cfg(feature = "dev-mode")]
pub const DEFAULT_MODULE_E_KEYS: u64 = 2_000_000;
#[cfg(not(feature = "dev-mode"))]
pub const DEFAULT_MODULE_E_KEYS: u64 = 50_000_000;

#[cfg(feature = "dev-mode")]
pub const DEFAULT_MODULE_E_LOOKUPS: u64 = 20_000;
#[cfg(not(feature = "dev-mode"))]
pub const DEFAULT_MODULE_E_LOOKUPS: u64 = 100_000;

#[cfg(feature = "dev-mode")]
pub const DEFAULT_MODULE_E_LIST_QUERIES: u64 = 20_000;
#[cfg(not(feature = "dev-mode"))]
pub const DEFAULT_MODULE_E_LIST_QUERIES: u64 = 100_000;

#[derive(Debug, Parser)]
#[command(
    name = "tracer-bullet",
    version,
    about = "Tracer Bullet prototype runner"
)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Command,
}

#[derive(Debug, Subcommand)]
pub enum Command {
    ModuleA(ModuleAArgs),
    ModuleB(ModuleBArgs),
    ModuleCWrite(ModuleCWriteArgs),
    ModuleCRead(ModuleCReadArgs),
    ModuleDAppend(ModuleDAppendArgs),
    ModuleEFst(ModuleEFstArgs),
    ModuleETrie(ModuleETrieArgs),
    ModuleECompare(ModuleECompareArgs),
    ModuleFServe(ModuleFServeArgs),
    #[command(name = "module-b-receiver-internal", hide = true)]
    ModuleBReceiverInternal(ModuleBReceiverInternalArgs),
}

#[derive(Debug, Clone, Args)]
pub struct ModuleAArgs {
    #[arg(long, default_value_t = DEFAULT_PAYLOAD_BYTES)]
    pub payload_bytes: usize,
    #[arg(long, default_value_t = DEFAULT_CHUNK_BYTES)]
    pub chunk_bytes: usize,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleBArgs {
    #[arg(long, default_value_t = DEFAULT_MEMFD_BYTES)]
    pub memfd_bytes: u64,
    #[arg(long)]
    pub socket_path: Option<PathBuf>,
    #[arg(long, default_value_t = String::from(DEFAULT_MARKER))]
    pub marker: String,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleBReceiverInternalArgs {
    #[arg(long)]
    pub socket_path: PathBuf,
    #[arg(long)]
    pub marker: String,
    #[arg(long)]
    pub memfd_bytes: u64,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleCWriteArgs {
    #[arg(long, default_value_t = DEFAULT_RECORDS)]
    pub records: u64,
    #[arg(long, default_value = "wal.bin")]
    pub wal_path: PathBuf,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleCReadArgs {
    #[arg(long, default_value = "wal.bin")]
    pub wal_path: PathBuf,
    #[arg(long)]
    pub expect_records: Option<u64>,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleDAppendArgs {
    #[arg(long, default_value = DEFAULT_MODULE_D_TARGET_PATH)]
    pub target_path: PathBuf,
    #[arg(long, default_value_t = DEFAULT_MODULE_D_TOTAL_BYTES)]
    pub total_bytes: u64,
    #[arg(long, default_value_t = DEFAULT_MODULE_D_GROUP_BYTES)]
    pub group_bytes: usize,
    #[arg(long, default_value_t = DEFAULT_MODULE_D_REQUEST_BYTES)]
    pub request_bytes: usize,
    #[arg(long, default_value_t = DEFAULT_MODULE_D_PRODUCERS)]
    pub producers: usize,
    #[arg(long, default_value_t = DEFAULT_MODULE_D_SEED)]
    pub seed: u64,
    #[arg(long)]
    pub allow_file_fallback: bool,
    #[arg(long)]
    pub require_io_uring: bool,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleEFstArgs {
    #[arg(long, default_value_t = DEFAULT_MODULE_E_KEYS)]
    pub keys: u64,
    #[arg(long, default_value_t = DEFAULT_MODULE_E_LOOKUPS)]
    pub lookups: u64,
    #[arg(long, default_value_t = DEFAULT_MODULE_E_LIST_QUERIES)]
    pub list_queries: u64,
    #[arg(long, default_value = DEFAULT_MODULE_E_INDEX_PATH)]
    pub index_path: PathBuf,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleETrieArgs {
    #[arg(long, default_value_t = DEFAULT_MODULE_E_KEYS)]
    pub keys: u64,
    #[arg(long, default_value_t = DEFAULT_MODULE_E_LOOKUPS)]
    pub lookups: u64,
    #[arg(long, default_value_t = DEFAULT_MODULE_E_LIST_QUERIES)]
    pub list_queries: u64,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleECompareArgs {
    #[arg(long, default_value_t = DEFAULT_MODULE_E_KEYS)]
    pub keys: u64,
    #[arg(long, default_value_t = DEFAULT_MODULE_E_LOOKUPS)]
    pub lookups: u64,
    #[arg(long, default_value_t = DEFAULT_MODULE_E_LIST_QUERIES)]
    pub list_queries: u64,
    #[arg(long, default_value = DEFAULT_MODULE_E_INDEX_PATH)]
    pub index_path: PathBuf,
    #[arg(long)]
    pub json: bool,
}

#[derive(Debug, Clone, Args)]
pub struct ModuleFServeArgs {
    #[arg(long, default_value = DEFAULT_MODULE_F_LISTEN_ADDR)]
    pub listen_addr: String,
    #[arg(long, default_value_t = DEFAULT_MODULE_F_WORKERS)]
    pub workers: usize,
    #[arg(long, default_value = DEFAULT_MODULE_F_TARGET_PATH)]
    pub target_path: PathBuf,
    #[arg(long, default_value_t = DEFAULT_MODULE_F_GROUP_BYTES)]
    pub group_bytes: usize,
    #[arg(long, default_value_t = DEFAULT_MODULE_F_COMPACTION_THRESHOLD)]
    pub compaction_threshold: usize,
    #[arg(long, default_value_t = DEFAULT_MODULE_F_FLUSH_INTERVAL_MS)]
    pub flush_interval_ms: u64,
    #[arg(long, default_value_t = String::from(DEFAULT_MODULE_F_BUCKET))]
    pub bucket: String,
    #[arg(long, default_value_t = String::from(DEFAULT_MODULE_F_ACCESS_KEY))]
    pub access_key: String,
    #[arg(long, default_value_t = String::from(DEFAULT_MODULE_F_SECRET_KEY))]
    pub secret_key: String,
    #[arg(long)]
    pub allow_file_fallback: bool,
    #[arg(long)]
    pub require_io_uring: bool,
}
