#[macro_use]
extern crate clap;
use clap::ArgMatches;
use drk::cli::{Config, DrkConfig};
use drk::util::join_config_path;
use drk::{rpc::jsonrpc, rpc::jsonrpc::JsonResult, Error, Result};
use log::debug;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use simplelog::{
    CombinedLogger, Config as SimplelogConfig, ConfigBuilder, LevelFilter, TermLogger,
    TerminalMode, WriteLogger,
};
use std::path::PathBuf;

struct Drk {
    url: String,
}

impl Drk {
    pub fn new(url: String) -> Self {
        Self { url }
    }

    async fn request(&self, r: jsonrpc::JsonRequest) -> Result<Value> {
        let r = json!(r);
        // create http body from json
        let data = surf::Body::from_json(&r)?;
        debug!(target: "DRK", "--> {:?}", r);
        println!("--> {:?}", r);
        let d2 = surf::Body::from_json(&r)?;
        println!("{:?}", d2.into_string().await);
        // send the data to the server
        let mut req = surf::post(&self.url).body(data).await?;

        // get the response
        let resp = req.take_body();
        // read the response body as a string
        let json = resp.into_string().await?;

        // parse the response into a JsonResult
        println!("{}", json);
        let v: JsonResult = serde_json::from_str(&json)?;
        match v {
            JsonResult::Resp(r) => {
                debug!(target: "DRK", "<-- {:?}", r);
                return Ok(r.result);
            }

            JsonResult::Err(e) => {
                debug!(target: "DRK", "<-- {:?}", e);
                println!("here!");
                return Err(Error::JsonRpcError(e.error.message.to_string()));
            }

            JsonResult::Notif(n) => {
                debug!(target: "DRK", "<-- {:?}", n);
                return Err(Error::JsonRpcError(
                    "Unexpected reply from server".to_string(),
                ));
            }
        };
    }

    async fn deposit(&self, network: String, asset: String) -> Result<Value> {
        // create json request
        // method = deposit
        // params = network, asset
        // type = Value
        let req = jsonrpc::request(json!("deposit"), json!([network, asset]));
        // call request function
        Ok(self.request(req).await?)
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct XX {
    pub foo: Value,
}

async fn start(config: &DrkConfig, options: ArgMatches<'_>) -> Result<()> {
    //let req = json!(jsonrpc::request(
    //    json!("deposit"),
    //    json!(["solana", "USDC"])
    //));

    ////let x = json!(XX { foo: json!(2) });
    //let ss = req.to_string();
    //println!("{}", ss);
    //let v: jsonrpc::JsonRequest = serde_json::from_str(&ss)?;
    //return Ok(());
    //
    let client = Drk::new(config.rpc_url.clone());

    if let Some(matches) = options.subcommand_matches("deposit") {
        let network = matches.value_of("network").unwrap().to_lowercase();
        let token = matches.value_of("TOKEN").unwrap();

        let reply = client
            .deposit(network.to_string(), token.to_string())
            .await?;

        println!(
            "Deposit your coins to the following address: {}",
            &reply.to_string()
        );

        return Ok(());
    }

    println!("Please run 'drk help' to see usage.");
    Err(Error::MissingParams)
}

fn main() -> Result<()> {
    let args = clap_app!(drk =>
        (@subcommand deposit =>
            (about: "Deposit clear assets for Dark assets")
            (@arg network: +required +takes_value --network
                    "Which network to use (bitcoin/solana/...)")
            (@arg TOKEN: +required
                    "Which token to deposit (BTC/SOL/USDC/...)")
        )
    )
    .get_matches();

    let config_path: PathBuf;
    if args.is_present("CONFIG") {
        config_path = PathBuf::from(args.value_of("CONFIG").unwrap());
    } else {
        config_path = join_config_path(&PathBuf::from("drk.toml"))?;
    }

    let config = Config::<DrkConfig>::load(config_path)?;

    let logger_config = ConfigBuilder::new().set_time_format_str("%T%.6f").build();

    // TODO: --verbose
    let debug_level = if args.is_present("verbose") {
        LevelFilter::Debug
    } else {
        LevelFilter::Off
    };

    let log_path = config.log_path.clone();

    CombinedLogger::init(vec![
        TermLogger::new(debug_level, logger_config, TerminalMode::Mixed).unwrap(),
        WriteLogger::new(
            LevelFilter::Debug,
            SimplelogConfig::default(),
            std::fs::File::create(log_path).unwrap(),
        ),
    ])
    .unwrap();

    futures::executor::block_on(start(&config, args))
}
