pub struct Cli<T: CliOverride = ()> {
    client: sdk::Client,
    over: T,
}

impl Cli {
    pub fn new(client: sdk::Client) -> Self {
        Self { client, over: () }
    }

    pub fn get_command(cmd: CliCommand) -> clap::Command {
        match cmd {
            CliCommand::InstanceGet => Self::cli_instance_get(),
            CliCommand::InstanceEnsure => Self::cli_instance_ensure(),
            CliCommand::InstanceIssueCrucibleSnapshotRequest => {
                Self::cli_instance_issue_crucible_snapshot_request()
            }
            CliCommand::InstanceMigrateStatus => Self::cli_instance_migrate_status(),
            CliCommand::InstanceSerial => Self::cli_instance_serial(),
            CliCommand::InstanceStatePut => Self::cli_instance_state_put(),
            CliCommand::InstanceStateMonitor => Self::cli_instance_state_monitor(),
        }
    }

    pub fn cli_instance_get() -> clap::Command {
        clap::Command::new("")
    }

    pub fn cli_instance_ensure() -> clap::Command {
        clap::Command::new("")
            .arg(
                clap::Arg::new("cloud-init-bytes")
                    .long("cloud-init-bytes")
                    .value_parser(clap::value_parser!(String))
                    .required(false),
            )
            .arg(
                clap::Arg::new("json-body")
                    .long("json-body")
                    .value_name("JSON-FILE")
                    .required(true)
                    .value_parser(clap::value_parser!(std::path::PathBuf))
                    .help("Path to a file that contains the full json body."),
            )
            .arg(
                clap::Arg::new("json-body-template")
                    .long("json-body-template")
                    .action(clap::ArgAction::SetTrue)
                    .help("XXX"),
            )
    }

    pub fn cli_instance_issue_crucible_snapshot_request() -> clap::Command {
        clap::Command::new("")
            .arg(
                clap::Arg::new("id")
                    .long("id")
                    .value_parser(clap::value_parser!(uuid::Uuid))
                    .required(true),
            )
            .arg(
                clap::Arg::new("snapshot-id")
                    .long("snapshot-id")
                    .value_parser(clap::value_parser!(uuid::Uuid))
                    .required(true),
            )
            .about("Issue a snapshot request to a crucible backend")
    }

    pub fn cli_instance_migrate_status() -> clap::Command {
        clap::Command::new("")
            .arg(
                clap::Arg::new("migration-id")
                    .long("migration-id")
                    .value_parser(clap::value_parser!(uuid::Uuid))
                    .required_unless_present("json-body"),
            )
            .arg(
                clap::Arg::new("json-body")
                    .long("json-body")
                    .value_name("JSON-FILE")
                    .required(false)
                    .value_parser(clap::value_parser!(std::path::PathBuf))
                    .help("Path to a file that contains the full json body."),
            )
            .arg(
                clap::Arg::new("json-body-template")
                    .long("json-body-template")
                    .action(clap::ArgAction::SetTrue)
                    .help("XXX"),
            )
    }

    pub fn cli_instance_serial() -> clap::Command {
        clap::Command::new("")
    }

    pub fn cli_instance_state_put() -> clap::Command {
        clap::Command::new("")
            .arg(
                clap::Arg::new("json-body")
                    .long("json-body")
                    .value_name("JSON-FILE")
                    .required(true)
                    .value_parser(clap::value_parser!(std::path::PathBuf))
                    .help("Path to a file that contains the full json body."),
            )
            .arg(
                clap::Arg::new("json-body-template")
                    .long("json-body-template")
                    .action(clap::ArgAction::SetTrue)
                    .help("XXX"),
            )
    }

    pub fn cli_instance_state_monitor() -> clap::Command {
        clap::Command::new("")
            .arg(
                clap::Arg::new("gen")
                    .long("gen")
                    .value_parser(clap::value_parser!(u64))
                    .required_unless_present("json-body"),
            )
            .arg(
                clap::Arg::new("json-body")
                    .long("json-body")
                    .value_name("JSON-FILE")
                    .required(false)
                    .value_parser(clap::value_parser!(std::path::PathBuf))
                    .help("Path to a file that contains the full json body."),
            )
            .arg(
                clap::Arg::new("json-body-template")
                    .long("json-body-template")
                    .action(clap::ArgAction::SetTrue)
                    .help("XXX"),
            )
    }
}

impl<T: CliOverride> Cli<T> {
    pub fn new_with_override(client: sdk::Client, over: T) -> Self {
        Self { client, over }
    }

    pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) {
        match cmd {
            CliCommand::InstanceGet => {
                self.execute_instance_get(matches).await;
            }
            CliCommand::InstanceEnsure => {
                self.execute_instance_ensure(matches).await;
            }
            CliCommand::InstanceIssueCrucibleSnapshotRequest => {
                self.execute_instance_issue_crucible_snapshot_request(matches)
                    .await;
            }
            CliCommand::InstanceMigrateStatus => {
                self.execute_instance_migrate_status(matches).await;
            }
            CliCommand::InstanceSerial => {
                self.execute_instance_serial(matches).await;
            }
            CliCommand::InstanceStatePut => {
                self.execute_instance_state_put(matches).await;
            }
            CliCommand::InstanceStateMonitor => {
                self.execute_instance_state_monitor(matches).await;
            }
        }
    }

    pub async fn execute_instance_get(&self, matches: &clap::ArgMatches) {
        let mut request = self.client.instance_get();
        self.over
            .execute_instance_get(matches, &mut request)
            .unwrap();
        let result = request.send().await;
        match result {
            Ok(r) => {
                println!("success\n{:#?}", r)
            }
            Err(r) => {
                println!("error\n{:#?}", r)
            }
        }
    }

    pub async fn execute_instance_ensure(&self, matches: &clap::ArgMatches) {
        let mut request = self.client.instance_ensure();
        if let Some(value) = matches.get_one::<String>("cloud-init-bytes") {
            request = request.body_map(|body| body.cloud_init_bytes(value.clone()))
        }

        if let Some(value) = matches.get_one::<std::path::PathBuf>("json-body") {
            let body_txt = std::fs::read_to_string(value).unwrap();
            let body_value =
                serde_json::from_str::<types::InstanceEnsureRequest>(&body_txt).unwrap();
            request = request.body(body_value);
        }

        self.over
            .execute_instance_ensure(matches, &mut request)
            .unwrap();
        let result = request.send().await;
        match result {
            Ok(r) => {
                println!("success\n{:#?}", r)
            }
            Err(r) => {
                println!("error\n{:#?}", r)
            }
        }
    }

    pub async fn execute_instance_issue_crucible_snapshot_request(
        &self,
        matches: &clap::ArgMatches,
    ) {
        let mut request = self.client.instance_issue_crucible_snapshot_request();
        if let Some(value) = matches.get_one::<uuid::Uuid>("id") {
            request = request.id(value.clone());
        }

        if let Some(value) = matches.get_one::<uuid::Uuid>("snapshot-id") {
            request = request.snapshot_id(value.clone());
        }

        self.over
            .execute_instance_issue_crucible_snapshot_request(matches, &mut request)
            .unwrap();
        let result = request.send().await;
        match result {
            Ok(r) => {
                println!("success\n{:#?}", r)
            }
            Err(r) => {
                println!("error\n{:#?}", r)
            }
        }
    }

    pub async fn execute_instance_migrate_status(&self, matches: &clap::ArgMatches) {
        let mut request = self.client.instance_migrate_status();
        if let Some(value) = matches.get_one::<uuid::Uuid>("migration-id") {
            request = request.body_map(|body| body.migration_id(value.clone()))
        }

        if let Some(value) = matches.get_one::<std::path::PathBuf>("json-body") {
            let body_txt = std::fs::read_to_string(value).unwrap();
            let body_value =
                serde_json::from_str::<types::InstanceMigrateStatusRequest>(&body_txt).unwrap();
            request = request.body(body_value);
        }

        self.over
            .execute_instance_migrate_status(matches, &mut request)
            .unwrap();
        let result = request.send().await;
        match result {
            Ok(r) => {
                println!("success\n{:#?}", r)
            }
            Err(r) => {
                println!("error\n{:#?}", r)
            }
        }
    }

    pub async fn execute_instance_serial(&self, matches: &clap::ArgMatches) {
        let mut request = self.client.instance_serial();
        self.over
            .execute_instance_serial(matches, &mut request)
            .unwrap();
        let result = request.send().await;
        match result {
            Ok(r) => {
                todo!()
            }
            Err(r) => {
                todo!()
            }
        }
    }

    pub async fn execute_instance_state_put(&self, matches: &clap::ArgMatches) {
        let mut request = self.client.instance_state_put();
        if let Some(value) = matches.get_one::<std::path::PathBuf>("json-body") {
            let body_txt = std::fs::read_to_string(value).unwrap();
            let body_value =
                serde_json::from_str::<types::InstanceStateRequested>(&body_txt).unwrap();
            request = request.body(body_value);
        }

        self.over
            .execute_instance_state_put(matches, &mut request)
            .unwrap();
        let result = request.send().await;
        match result {
            Ok(r) => {
                println!("success\n{:#?}", r)
            }
            Err(r) => {
                println!("error\n{:#?}", r)
            }
        }
    }

    pub async fn execute_instance_state_monitor(&self, matches: &clap::ArgMatches) {
        let mut request = self.client.instance_state_monitor();
        if let Some(value) = matches.get_one::<u64>("gen") {
            request = request.body_map(|body| body.gen(value.clone()))
        }

        if let Some(value) = matches.get_one::<std::path::PathBuf>("json-body") {
            let body_txt = std::fs::read_to_string(value).unwrap();
            let body_value =
                serde_json::from_str::<types::InstanceStateMonitorRequest>(&body_txt).unwrap();
            request = request.body(body_value);
        }

        self.over
            .execute_instance_state_monitor(matches, &mut request)
            .unwrap();
        let result = request.send().await;
        match result {
            Ok(r) => {
                println!("success\n{:#?}", r)
            }
            Err(r) => {
                println!("error\n{:#?}", r)
            }
        }
    }
}

pub trait CliOverride {
    fn execute_instance_get(
        &self,
        matches: &clap::ArgMatches,
        request: &mut builder::InstanceGet,
    ) -> Result<(), String> {
        Ok(())
    }

    fn execute_instance_ensure(
        &self,
        matches: &clap::ArgMatches,
        request: &mut builder::InstanceEnsure,
    ) -> Result<(), String> {
        Ok(())
    }

    fn execute_instance_issue_crucible_snapshot_request(
        &self,
        matches: &clap::ArgMatches,
        request: &mut builder::InstanceIssueCrucibleSnapshotRequest,
    ) -> Result<(), String> {
        Ok(())
    }

    fn execute_instance_migrate_status(
        &self,
        matches: &clap::ArgMatches,
        request: &mut builder::InstanceMigrateStatus,
    ) -> Result<(), String> {
        Ok(())
    }

    fn execute_instance_serial(
        &self,
        matches: &clap::ArgMatches,
        request: &mut builder::InstanceSerial,
    ) -> Result<(), String> {
        Ok(())
    }

    fn execute_instance_state_put(
        &self,
        matches: &clap::ArgMatches,
        request: &mut builder::InstanceStatePut,
    ) -> Result<(), String> {
        Ok(())
    }

    fn execute_instance_state_monitor(
        &self,
        matches: &clap::ArgMatches,
        request: &mut builder::InstanceStateMonitor,
    ) -> Result<(), String> {
        Ok(())
    }
}

impl CliOverride for () {}

#[derive(Copy, Clone, Debug)]
pub enum CliCommand {
    InstanceGet,
    InstanceEnsure,
    InstanceIssueCrucibleSnapshotRequest,
    InstanceMigrateStatus,
    InstanceSerial,
    InstanceStatePut,
    InstanceStateMonitor,
}

impl CliCommand {
    pub fn iter() -> impl Iterator<Item = CliCommand> {
        vec![
            CliCommand::InstanceGet,
            CliCommand::InstanceEnsure,
            CliCommand::InstanceIssueCrucibleSnapshotRequest,
            CliCommand::InstanceMigrateStatus,
            CliCommand::InstanceSerial,
            CliCommand::InstanceStatePut,
            CliCommand::InstanceStateMonitor,
        ]
        .into_iter()
    }
}