use crate::zabbix::api::get_zabbix_authtoken;
use crate::zabbix::problems::DataMatrix;
use clap::{Arg, ArgMatches, Command};
use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify};
use serde::{Deserialize, Serialize};
use serde_json::Error as JsonError;
use serde_json::Value as JsonValue;
use std::fs::File;
use std::io::Read;
use std::string::String;
use std::thread::sleep;
use std::time::Duration;

pub enum ReloadFrequency {
    High = 50,
    Medium = 20,
    Low = 10,
}

#[derive(Debug, Clone)]
pub enum Mode {
    Draw,
    Test,
    Input,
    Effect,
}

#[derive(Debug)]
pub struct Context {
    pub cfg: Config,
    configfile: String,
    inotify: Inotify,
    pub datamatrix: DataMatrix,
    pub mode: Option<Mode>,
}

impl Context {
    pub fn new() -> Self {
        let argp: ArgMatches = Context::argparse();

        let configfile = argp
            .get_one::<String>("config")
            .unwrap_or(&"config.json".to_string())
            .to_string();

        let mut ctx = Context {
            cfg: Config::new(),
            configfile: configfile,
            inotify: Inotify::init(InitFlags::IN_NONBLOCK).unwrap(),
            datamatrix: DataMatrix::new(),
            mode: match argp.get_one::<String>("mode").unwrap().as_str() {
                "draw" => Some(Mode::Draw),
                "test" => Some(Mode::Test),
                "input" => Some(Mode::Input),
                "effect" => Some(Mode::Effect),
                _ => {
                    println!("No valid mode choosen");
                    None
                }
            },
        };

        println!("Loading {configfile} file ...", configfile = ctx.configfile);
        ctx.cfg.load(&ctx.configfile);

        println!(
            "Adding inotify watch on {configfile} file ...",
            configfile = ctx.configfile
        );
        ctx.inotify
            .add_watch(ctx.configfile.as_str(), AddWatchFlags::IN_MODIFY)
            .unwrap();
        get_zabbix_authtoken(&mut ctx.cfg);

        ctx
    }

    fn argparse() -> ArgMatches {
        Command::new(env!("CARGO_PKG_NAME"))
            .version(env!("CARGO_PKG_VERSION"))
            .author(env!("CARGO_PKG_AUTHORS"))
            .about(env!("CARGO_PKG_DESCRIPTION"))
            .arg(
                Arg::new("config")
                    .short('c')
                    .long("config")
                    .value_name("FILE")
                    .default_value("/etc/zabbixlaunch/config.json")
                    .help("Sets a custom config file"),
            )
            .arg(
                Arg::new("mode")
                    .short('m')
                    .long("mode")
                    .value_name("mode")
                    .default_value("draw")
                    .value_parser(["draw", "test", "input", "effect"])
                    .help("Sets a when running"),
            )
            .arg(Arg::new("debug").short('d').help("Enable debugging"))
            .get_matches()
    }

    pub fn hotreload(&mut self) {
        let mut i = 0;
        let waitmilli = self.cfg.refresh.unwrap_or(5) * 1000;

        while i < waitmilli {
            let waitinc = waitmilli / ReloadFrequency::Medium as u64;
            let events = match self.inotify.read_events() {
                Ok(ev) => ev,
                Err(_) => vec![],
            };
            if events.len() > 0 {
                println!("Reloading {cfg}", cfg = self.configfile);
                self.cfg.load(self.configfile.as_str());
                get_zabbix_authtoken(&mut self.cfg);
                break;
            } else {
                sleep(Duration::from_millis(waitinc));
            }
            i += waitinc;
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Config {
    pub server: String,
    pub username: String,
    pub password: String,
    #[serde(skip_serializing)]
    pub authtoken: Option<String>,
    pub sloweffect: Option<bool>,
    pub refresh: Option<u64>,
    pub limit: Option<u64>,
}

impl<'a> Config {
    pub fn new() -> Self {
        Self {
            server: "https://zabbix.acme.com/api_jsonrpc.php".to_string(),
            username: "bob".to_string(),
            password: "password".to_string(),
            authtoken: Some("token".to_string()),
            sloweffect: Some(true),
            refresh: Some(5u64),
            limit: Some(20u64),
        }
    }

    fn load(&'a mut self, configfile: &str) {
        let fileopen: Result<File, std::io::Error>;
        let filemeta = std::fs::metadata(configfile);
        let mut error_reading = false;
        let fileexists = match filemeta {
            Ok(_) => true,
            Err(_) => false,
        };

        if !fileexists {
            File::create(configfile).unwrap();
        }
        fileopen = File::open(configfile);

        let mut file = match fileopen {
            Ok(f) => f,
            Err(err) => {
                panic!("{err}", err = err);
            }
        };
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        let parse: Result<JsonValue, JsonError> = serde_json::from_str(contents.as_str());
        let ncfg = match parse {
            Ok(ncfg) => {
                let tmpcfg = Config::new();
                Config {
                    server: ncfg["server"]
                        .as_str()
                        .unwrap_or(tmpcfg.server.as_str())
                        .to_string(),
                    username: ncfg["username"]
                        .as_str()
                        .unwrap_or(tmpcfg.username.as_str())
                        .to_string(),
                    password: ncfg["password"]
                        .as_str()
                        .unwrap_or(tmpcfg.password.as_str())
                        .to_string(),
                    authtoken: Some(
                        ncfg["authtoken"]
                            .as_str()
                            .unwrap_or(self.authtoken.clone().unwrap().as_str())
                            .to_string(),
                    ),
                    sloweffect: Some(
                        ncfg["sloweffect"]
                            .as_bool()
                            .unwrap_or(tmpcfg.sloweffect.unwrap()),
                    ),
                    refresh: Some(ncfg["refresh"].as_u64().unwrap_or(tmpcfg.refresh.unwrap())),
                    limit: Some(ncfg["limit"].as_u64().unwrap_or(tmpcfg.limit.unwrap())),
                }
            }
            Err(err) => {
                error_reading = true;
                println!("error occured: '{}'", err);
                Config::new()
            }
        };
        *self = ncfg;
        if !fileexists || error_reading {
            self.save(&configfile);
        }
    }

    fn save(&self, configfile: &str) {
        let file = File::create(configfile).unwrap();
        serde_json::to_writer_pretty(file, &self).unwrap();
    }
}