use clap::{arg, command, value_parser, Arg, ArgMatches};
use dialoguer::theme::ColorfulTheme;
use dialoguer::{Confirm, Input};
use tokio::io::AsyncWriteExt;
use tokio::sync::{mpsc, oneshot};
use tokio::time::sleep;
use crate::config::ConfigFile;
use crate::datastore::Command;
use crate::enums::SystemState;
use crate::zones::FileZone;
pub fn clap_parser() -> ArgMatches {
    command!()
        .arg(
            arg!(
                -c --config <FILE> "Sets a custom config file"
            )
            .required(false)
            .value_parser(value_parser!(String)),
        )
        .arg(
            Arg::new("configcheck")
                .short('t')
                .long("configcheck")
                .help("Check the config file, show it and then quit.")
                .action(clap::ArgAction::SetTrue),
        )
        .arg(
            Arg::new("export_config")
                .long("export-default-config")
                .help("Export a default config file.")
                .action(clap::ArgAction::SetTrue),
        )
        .arg(
            Arg::new("export_zone")
                .short('e')
                .long("export-zone")
                .help("Export a single zone.")
                .value_parser(value_parser!(String)),
        )
        .arg(
            Arg::new("import_zones")
                .short('i')
                .long("import-zones")
                .help("Import a single zone file.")
                .action(clap::ArgAction::SetTrue),
        )
        .arg(
            Arg::new("import_zone")
                .long("import-zone")
                .help("Import a single zone from a file.")
                .value_parser(value_parser!(String)),
        )
        .arg(
            Arg::new("filename")
                .short('f')
                .long("filename")
                .help("Filename to save to (used in other commands).")
                .value_parser(value_parser!(String)),
        )
        .arg(
            Arg::new("add_admin")
                .long("add-admin")
                .help("Add a new admin user.")
                .action(clap::ArgAction::SetTrue),
        )
        .arg(
            Arg::new("use_zonefile")
                .long("using-zonefile")
                .help("Load the zone file into the DB on startup, typically used for testing.")
                .action(clap::ArgAction::SetTrue),
        )
        .get_matches()
}
pub async fn cli_commands(
    tx: mpsc::Sender<Command>,
    clap_results: &ArgMatches,
    zone_file: &Option<String>,
) -> Result<SystemState, String> {
    if clap_results.get_flag("add_admin") {
        let _ = add_admin_user(tx).await;
        return Ok(SystemState::ShuttingDown);
    }
    if clap_results.get_flag("export_config") {
        default_config();
        return Ok(SystemState::ShuttingDown);
    }
    if clap_results.get_flag("use_zonefile") {
        if let Some(zone_file) = zone_file {
            if let Err(error) = import_zones(tx.clone(), zone_file.to_owned(), None).await {
                log::error!("Failed to import zone file! {error:?}");
                return Ok(SystemState::ShuttingDown);
            }
        }
    }
    if let Some(zone_name) = clap_results.get_one::<String>("export_zone") {
        if let Some(output_filename) = clap_results.get_one::<String>("filename") {
            log::info!("Exporting zone {zone_name} to {output_filename}");
            let res = export_zone_file(tx, zone_name, output_filename).await;
            if let Err(err) = res {
                log::error!("{err}");
            }
            return Ok(SystemState::Export);
        } else {
            log::error!("You need to specify a a filename to save to.");
            return Ok(SystemState::ShuttingDown);
        }
    };
    if clap_results.get_flag("import_zones") {
        if let Some(filename) = clap_results.get_one::<String>("filename") {
            log::info!("Importing zones from {filename}");
            import_zones(tx, filename.to_owned(), None)
                .await
                .map_err(|e| format!("Error importing {filename}: {e:?}"))?;
            return Ok(SystemState::Import);
        } else {
            log::error!("You need to specify a a filename to save to.");
            return Ok(SystemState::ShuttingDown);
        }
    };
    if let Some(zone_name) = clap_results.get_one::<String>("import_zone") {
        if let Some(filename) = clap_results.get_one::<String>("filename") {
            log::info!("Importing zones from {filename}");
            import_zones(tx, filename.to_owned(), Some(zone_name.to_owned()))
                .await
                .map_err(|e| format!("Error importing {filename}: {e:?}"))?;
            return Ok(SystemState::Import);
        } else {
            log::error!("You need to specify a a filename to save to.");
            return Ok(SystemState::ShuttingDown);
        }
    };
    Ok(SystemState::Server)
}
pub fn default_config() {
    let output = match serde_json::to_string_pretty(&ConfigFile::default()) {
        Ok(value) => value,
        Err(_) => {
            log::error!("I don't know how, but we couldn't parse our own config file def.");
            "".to_string()
        }
    };
    println!("{output}");
}
pub async fn export_zone_file(
    tx: mpsc::Sender<Command>,
    zone_name: &String,
    filename: &String,
) -> Result<(), String> {
    let (tx_oneshot, rx_oneshot) = oneshot::channel();
    let ds_req: Command = Command::GetZone {
        id: None,
        name: Some(zone_name.clone()),
        resp: tx_oneshot,
    };
    if let Err(error) = tx.send(ds_req).await {
        return Err(format!(
            "failed to send to datastore from export_zone_file {error:?}"
        ));
    };
    let zone: Option<FileZone> = match rx_oneshot.await {
        Ok(value) => value,
        Err(err) => return Err(format!("rx from ds failed {err:?}")),
    };
    eprintln!("Got filezone: {zone:?}");
    let zone_bytes = match zone {
        None => {
            log::warn!("Couldn't find the zone {zone_name}");
            return Ok(());
        }
        Some(zone) => serde_json::to_string_pretty(&zone).map_err(|err| {
            format!(
                "Failed to serialize zone {zone_name} to json: {err:?}",
                zone_name = zone_name,
                err = err
            )
        })?,
    };
    let mut file = tokio::fs::File::create(filename)
        .await
        .map_err(|e| format!("Failed to open file {e:?}"))?;
    file.write_all(zone_bytes.as_bytes())
        .await
        .map_err(|e| format!("Failed to write file: {e:?}"))?;
    Ok(())
}
pub async fn import_zones(
    tx: mpsc::Sender<Command>,
    filename: String,
    zone_name: Option<String>,
) -> Result<(), String> {
    let (tx_oneshot, mut rx_oneshot) = oneshot::channel();
    let msg = Command::ImportFile {
        filename,
        resp: tx_oneshot,
        zone_name,
    };
    if let Err(err) = tx.send(msg).await {
        log::error!("Failed to send message to datastore: {err:?}");
    }
    loop {
        let res = rx_oneshot.try_recv();
        match res {
            Err(error) => {
                if let oneshot::error::TryRecvError::Closed = error {
                    break;
                }
            }
            Ok(()) => break,
        };
        sleep(std::time::Duration::from_micros(500)).await;
    }
    Ok(())
    }
pub async fn add_admin_user(tx: mpsc::Sender<Command>) -> Result<(), ()> {
    println!("Creating admin user, please enter their username from the identity provider");
    let username: String = Input::with_theme(&ColorfulTheme::default())
        .with_prompt("Username")
        .interact_text()
        .map_err(|e| {
            log::error!("Failed to get username from user: {e:?}");
        })?;
    println!(
        "The authentication reference is the unique user identifier in the Identity Provider."
    );
    let authref: String = Input::with_theme(&ColorfulTheme::default())
        .with_prompt("Authentication Reference:")
        .interact_text()
        .map_err(|e| {
            log::error!("Failed to get auth reference from user: {e:?}");
        })?;
    println!(
        r#"
Creating the following user:
Username: {username}
Authref:  {authref}
"#
    );
    let confirm = Confirm::with_theme(&ColorfulTheme::default())
        .with_prompt("Do these details look correct?")
        .interact_opt();
    match confirm {
        Ok(Some(true)) => {}
        Ok(Some(false)) | Ok(None) | Err(_) => {
            log::warn!("Cancelled user creation");
            return Err(());
        }
    }
    let (tx_oneshot, rx_oneshot) = oneshot::channel();
    let new_user = Command::CreateUser {
        username: username.clone(),
        authref: authref.clone(),
        admin: true,
        disabled: false,
        resp: tx_oneshot,
    };
    if let Err(error) = tx.send(new_user).await {
        log::error!("Failed to send new user command for username {username:?}: {error:?}");
        return Err(());
    };
    match rx_oneshot.await {
        Ok(res) => match res {
            true => {
                log::info!("Successfully created user!");
                Ok(())
            }
            false => {
                log::error!("Failed to create user! Check datastore logs.");
                Err(())
            }
        },
        Err(error) => {
            log::debug!("Failed to rx result from datastore: {error:?}");
            Err(())
        }
    }
}