use std::collections::HashMap;
use warp::Filter;
use serde::{ Serialize, Deserialize };
use rust_util::XResult;

include!("lib.rs");

pub use qjs::*;
pub use sig::*;
pub use rpc::*;
pub use util::*;

#[derive(Serialize, Deserialize)]
struct CallJSBody {
    method: String,
    params: Option<String>,
}

#[tokio::main]
async fn main() {
    let get_public_key = warp::path("get_signing_public_key").map(|| {
        match get_signing_public_key() {
            None => "null".to_owned(),
            Some(public_key) => {
                let mut map = HashMap::new();
                map.insert("public_key", hex::encode(&public_key));
                serde_json::to_string_pretty(&map).unwrap_or_else(|e| format!("get public key error: {}", e)) + "\n"
            },
        }
    });

    let list_js = warp::path("list_js").map(|| {
        call_fn(|| -> XResult<String> {
            let mut jss = vec![];
            if let Ok(read_dir) = std::fs::read_dir("js") {
                for entry in read_dir {
                    if let Ok(dir_entry) = entry {
                        if let Ok(file_name) = dir_entry.file_name().into_string() {
                            if file_name.ends_with(".js") {
                                let chars = file_name.chars();
                                jss.push(chars.take(file_name.chars().count() - 3).collect::<String>());
                            }
                        }
                    }
                }
            }
            Ok(serde_json::to_string_pretty(&jss).unwrap() + "\n")
        })
    });

    let get_js = warp::path!("get_js" / String).map(|js_hex| {
        call_fn(|| -> XResult<String> {
            let js_fn = format!("js/{}.js", js_hex);
            let js_sig_fn = format!("js/{}.sig.json", js_hex);
            let js = read_file(&js_fn)?;
            let js_sig = read_file(&js_sig_fn)?;
    
            let signed_message: SignedMessage = serde_json::from_str(&js_sig)?;
    
            let mut result = HashMap::new();
            result.insert("js", js);
            result.insert("sig", js_sig);
            result.insert("verify", signed_message.verify(&load_signing_key_pair()?.public_key()).to_string());
    
            Ok(serde_json::to_string_pretty(&result)? + "\n")
        })
    });

    let call_js = warp::post()
                .and(warp::path!("call_js" / String))
                .and(warp::body::json())
                .map(|js_hex, call_js_body: CallJSBody| {
        call_fn(|| -> XResult<String> {
            let js_fn = format!("js/{}.js", js_hex);
            let js_sig_fn = format!("js/{}.sig.json", js_hex);
            let js = read_file(&js_fn)?;
            let js_sig = read_file(&js_sig_fn)?;

            let signed_message: SignedMessage = serde_json::from_str(&js_sig)?;
            if !signed_message.verify(&load_signing_key_pair()?.public_key()) {
                return Ok(HttpResult::new_400(
                    "Script verify failed!".to_owned(), 
                    Some(format!("{}", js_hex))
                ).to_string_pretty());
            }

            let context = QuickJSContext::new()?;
            context.init(&js)?;

            let r = context.call_fn(&call_js_body.method, &call_js_body.params.unwrap_or_else(|| "[]".to_owned()));
        
            let mut http_status = HttpResult::new();
            http_status.js_hash(format!("{}", js_hex));
            match r {
                Err(e) => {
                    http_status.status(500)
                               .message("Script call failed!".to_owned())
                               .result(format!("{}", e));
                },
                Ok(r) => {
                    http_status.status(200)
                               .message("Script call successed!".to_owned())
                               .result(r.into_string().unwrap_or_else(|| "null".to_owned()));
                },
            }
            Ok(http_status.to_string_pretty())
        })
    });

    println!("Listen at 127.0.0.1:8888 ...");
    warp::serve(
        get_public_key.or(list_js)
                             .or(get_js)
                             .or(call_js)
    )
    .run(([127, 0, 0, 1], 8888))
    .await;
}

fn call_fn<F>(f: F) -> String where F: FnOnce() -> XResult<String> {
    match f() {
        Err(e) => {
            HttpResult::new_500(
                "Call fn failed!".to_owned(), 
                Some(format!("{}", e))
            ).to_string_pretty()
        },
        Ok(r) => r,
    }
}