use std::fs;
use std::sync::mpsc::{Receiver, RecvError, TryRecvError};


use log::{error, info, warn};
use url::Url;
use dnapi_rs::client_blocking::Client;




use crate::config::{load_cdata, save_cdata, TFClientConfig};
use crate::daemon::ThreadMessageSender;
use crate::dirs::get_nebulaconfig_file;
use crate::nebulaworker::NebulaWorkerMessage;

pub enum APIWorkerMessage {
    Shutdown,
    Enroll { code: String },
    Update,
    Timer
}

pub fn apiworker_main(_config: TFClientConfig, instance: String, url: String, tx: ThreadMessageSender, rx: Receiver<APIWorkerMessage>) {
    let server = Url::parse(&url).unwrap();

    let client = Client::new(format!("tfclient/{}", env!("CARGO_PKG_VERSION")), server).unwrap();

    loop {
        match rx.recv() {
            Ok(msg) => {
                match msg {
                    APIWorkerMessage::Shutdown => {
                        info!("recv on command socket: shutdown, stopping");
                        break;
                    },
                    APIWorkerMessage::Timer | APIWorkerMessage::Update => {
                        info!("updating config");
                        let mut cdata = match load_cdata(&instance) {
                            Ok(c) => c,
                            Err(e) => {
                                error!("error in api worker thread: {}", e);
                                error!("APIWorker exiting with error");
                                return;
                            }
                        };
                        if cdata.creds.is_none() {
                            info!("not enrolled, cannot perform config update");
                            match save_cdata(&instance, cdata) {
                                Ok(_) => (),
                                Err(e) => {
                                    error!("Error saving cdata: {}", e);
                                    error!("APIWorker exiting with error");
                                    return;
                                }
                            }
                            continue;
                        }
                        let creds = cdata.clone().creds.unwrap_or_else(|| unreachable!());

                        info!("checking for update");
                        let update_available = match client.check_for_update(&creds) {
                            Ok(ua) => ua,
                            Err(e) => {
                                error!("error checking for config update: {}", e);
                                match save_cdata(&instance, cdata) {
                                    Ok(_) => (),
                                    Err(e) => {
                                        error!("Error saving cdata: {}", e);
                                        error!("APIWorker exiting with error");
                                        return;
                                    }
                                }
                                continue;
                            }
                        };

                        if !update_available {
                            match save_cdata(&instance, cdata) {
                                Ok(_) => (),
                                Err(e) => {
                                    error!("Error saving cdata: {}", e);
                                    error!("APIWorker exiting with error");
                                    return;
                                }
                            }
                            info!("no config update available");
                            continue;
                        }

                        info!("updated configuration is avaliable");
                        info!("updating configuration");

                        let (config, dh_privkey, creds) = match client.do_update(&creds) {
                            Ok(d) => d,
                            Err(e) => {
                                error!("error requesting updating config: {}", e);
                                match save_cdata(&instance, cdata) {
                                    Ok(_) => (),
                                    Err(e) => {
                                        error!("Error saving cdata: {}", e);
                                        error!("APIWorker exiting with error");
                                        return;
                                    }
                                }
                                continue;
                            }
                        };

                        cdata.creds = Some(creds);
                        cdata.dh_privkey = Some(dh_privkey.try_into().expect("32 != 32"));

                        match fs::write(get_nebulaconfig_file(&instance).expect("Unable to determine nebula config file location"), config) {
                            Ok(_) => (),
                            Err(e) => {
                                error!("unable to save nebula config: {}", e);
                                match save_cdata(&instance, cdata) {
                                    Ok(_) => (),
                                    Err(e) => {
                                        error!("Error saving cdata: {}", e);
                                        error!("APIWorker exiting with error");
                                        return;
                                    }
                                }
                                continue;
                            }
                        }

                        match save_cdata(&instance, cdata) {
                            Ok(_) => (),
                            Err(e) => {
                                error!("Error saving cdata: {}", e);
                                error!("APIWorker exiting with error");
                                return;
                            }
                        }

                        info!("configuration updated successfully!");
                        info!("sending signal to nebula thread to reload config");

                        match tx.nebula_thread.send(NebulaWorkerMessage::ConfigUpdated) {
                            Ok(_) => (),
                            Err(e) => {
                                error!("unable to tell nebula thread to update config: {}", e);
                                error!("APIWorker exiting with error");
                                return;
                            }
                        }
                    },
                    APIWorkerMessage::Enroll { code } => {
                        info!("recv on command socket: enroll {}", code);
                        let mut cdata = match load_cdata(&instance) {
                            Ok(c) => c,
                            Err(e) => {
                                error!("error in api worker thread: {}", e);
                                error!("APIWorker exiting with error");
                                return;
                            }
                        };
                        if cdata.creds.is_some() {
                            warn!("enrollment failed: already enrolled");
                            continue;
                        }

                        let (config, dh_privkey, creds, meta) = match client.enroll(&code) {
                            Ok(resp) => resp,
                            Err(e) => {
                                error!("error with enrollment request: {}", e);
                                continue;
                            }
                        };

                        match fs::write(get_nebulaconfig_file(&instance).expect("Unable to determine nebula config file location"), config) {
                            Ok(_) => (),
                            Err(e) => {
                                error!("unable to save nebula config: {}", e);
                                continue;
                            }
                        }

                        cdata.creds = Some(creds);
                        cdata.dh_privkey = Some(dh_privkey.try_into().expect("32 != 32"));
                        cdata.meta = Some(meta);

                        // Save vardata
                        match save_cdata(&instance, cdata) {
                            Ok(_) => (),
                            Err(e) => {
                                error!("Error saving cdata: {}", e);
                                error!("APIWorker exiting with error");
                                return;
                            }
                        }

                        info!("Configuration updated. Sending signal to Nebula worker thread");

                        match tx.nebula_thread.send(NebulaWorkerMessage::ConfigUpdated) {
                            Ok(_) => (),
                            Err(e) => {
                                error!("unable to tell nebula thread to update config: {}", e);
                                error!("APIWorker exiting with error");
                                return;
                            }
                        }
                    }
                }
            },
            Err(e) => {
                error!("error on command socket: {}", e);
                return;
            }
        }
    }
}