use config::{Config, OutputFormat};
use utils::console::*;
use utils::output;

use clap::{App, Arg, ArgMatches, SubCommand};
use hyper::Client;
use serde_json;
use std::io::Read;
use std::str;
use webbrowser;

pub const NAME: &'static str = "status";

error_chain! {
    errors {
       CenterDeviceStatusFailed {
            description("failed to get CenterDevice status")
            display("failed to get CenterDevice status")
        }
    }
}

#[derive(Deserialize, Debug)]
enum Status {
    Okay,
    Warning,
    Failed,
}

#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct Rest {
    Status: Status,
    Timestamp: String,
    NotificationQueueClientPool: bool,
    FolderHealthCheckSensor: bool,
    DocumentQueueClientPool: bool,
    MetadataStoreResource: bool,
    NotificationStoreResource: bool,
    SearchEngineResource: bool,
    SecurityDataStoreResource: bool,
    SendEmailQueueClientPool: bool,
    UserdataStoreResource: bool,
}

#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct Auth {
    Status: Status,
    Timestamp: String,
    AuthServer: bool,
}
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct WebClient {
    Status: Status,
    Timestamp: String,
    NotificationAlertingService: bool,
    RestServerSensor: bool,
}

#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct PublicLink {
    Status: Status,
    Timestamp: String,
    PublicLinkClient: bool,
}

#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct DistributorConsole {
    Status: Status,
    Timestamp: String,
    RestServerSensor: bool,
}

#[allow(non_camel_case_types)]
#[derive(Deserialize, Debug)]
enum PingDomStatus {
    up,
    down,
}

#[derive(Deserialize, Debug)]
struct Checks {
    checks: Vec<Vec<Check>>,
}


#[derive(Deserialize, Debug)]
struct Check {
    hostname: String,
    status: PingDomStatus,
    lasttesttime: String,
}

#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct PingDom {
    Status: Status,
    Timestamp: String,
    Checks: Checks,
}

#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct CenterDeviceStatus {
    Status: Status,
    Rest: Rest,
    Auth: Auth,
    WebClient: WebClient,
    PublicLink: PublicLink,
    DistributorConsole: DistributorConsole,
    PingDom: PingDom,
}

pub fn build_sub_cli() -> App<'static, 'static> {
    SubCommand::with_name(NAME)
        .about("Gets public centerdevice status from status server")
        .arg(Arg::with_name("details")
            .long("details")
            .help("Show detailed output"))
        .arg(Arg::with_name("browser")
            .long("browser")
            .help("Open status in web browser"))
}

pub fn call(args: Option<&ArgMatches>, config: &Config) -> Result<()> {
    let details = args.ok_or(false).unwrap().is_present("details");
    let browser = args.ok_or(false).unwrap().is_present("browser");

    if browser {
        info("Opening CenterDevice Status in default browser ...");
        browse(config, details).chain_err(|| ErrorKind::CenterDeviceStatusFailed)
    } else {
        info("Getting CenterDevice Status ...");
        status(config, details).chain_err(|| ErrorKind::CenterDeviceStatusFailed)
    }
}

fn browse(config: &Config, details: bool) -> Result<()> {
    if details {
        webbrowser::open("http://status.centerdevice.de/details.html")
    } else {
        webbrowser::open("http://status.centerdevice.de")
    }.chain_err(|| "Failed to open default browser")?;

    if config.general.output_format == OutputFormat::JSON { msgln("{}"); }

    Ok(())
}

fn status(config: &Config, details: bool) -> Result<()> {
    let json = get_centerdevice_status_json()?;
    output(&json, &config.general.output_format, details)
}

fn get_centerdevice_status_json() -> Result<String> {
    let url = "http://status.centerdevice.de/details.json";
    let mut response = Client::new()
        .get(url)
        .send()
        .chain_err(|| ErrorKind::CenterDeviceStatusFailed)?;

    let mut buffer = Vec::new();
    response.read_to_end(&mut buffer).chain_err(|| "Failed to read HTTP response")?;
    let json = str::from_utf8(&buffer).chain_err(|| "Failed to parse JSON")?;

    Ok(json.to_string())
}

fn output(json: &str, format: &OutputFormat, details: bool) -> Result<()> {
    match *format {
        OutputFormat::HUMAN => output_human(json, details),
        OutputFormat::JSON => output::as_json(json)
            .chain_err(|| ErrorKind::CenterDeviceStatusFailed),
    }
}

fn output_human(json: &str, details: bool) -> Result<()> {
    let status: CenterDeviceStatus = serde_json::from_str(json).chain_err(|| "JSON parsing failed")?;
    match (&status.Status, details) {
        (&Status::Okay, false) =>
            msgln(format!("CenterDevice status is {:?}.", status.Status)),
        (&Status::Okay, true) | (&Status::Warning, _) | (&Status::Failed, _) => {
            msgln(format!("CenterDevice status is {:?}.", status.Status));
            msgln(format!("+ Rest: {:?}", status.Rest.Status));
            msgln(format!("+ Auth: {:?}", status.Auth.Status));
            msgln(format!("+ WebClient: {:?}", status.WebClient.Status));
            msgln(format!("+ PublicLink: {:?}", status.PublicLink.Status));
            msgln(format!("+ DistributorConsole: {:?}", status.DistributorConsole.Status));
            msgln(format!("+ PingDom: {:?}", status.PingDom.Status));
        }
    }
    Ok(())
}