extern crate core;

use std::time::SystemTime;

use anyhow::Result;
use serde_json::Value;
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};

use crate::fn_common::FnResult;

mod fn_common;
mod fn_fetch;

wit_bindgen_wasmtime::export!("../container.wit");

#[derive(Default)]
pub struct MyContainer {
    fetch_invoked_count: u32,
}

impl container::Container for MyContainer {
    fn fetch(&mut self, params: &str) -> String {
        let t1 = SystemTime::now();
        if self.fetch_invoked_count > 10 {
            return FnResult::fail("Fetch invoke count cannot more than 10").to_json();
        }
        self.fetch_invoked_count += 1;
        let result = fn_fetch::do_fetch(params).to_json();
        let t2 = SystemTime::now();
        let e = t2.duration_since(t1).expect("get duration error");
        println!("+ fetch: {}, time cost: {}ms", params, e.as_millis());
        result
    }

    fn log(&mut self, level: &str, message: &str) {
        println!("[{}] {}", level, message);
    }
}

wit_bindgen_wasmtime::import!("../exports.wit");

fn main() -> Result<()> {
    let main_t1 = SystemTime::now();
    use exports::*;
    let wasm = "../engine/target/wasm32-unknown-unknown/debug/engine.wasm";
    let (exports, mut store) = instantiate(
        wasm,
        |linker| container::add_to_linker(linker, |cx| -> &mut MyContainer { &mut cx.imports }),
        |store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports),
    )?;
    let eval_t1 = SystemTime::now();
    let a = exports.eval_javascript(&mut store, r##"
        function hi(name) { return "hi: " + name; }
        console.log('hello', 1, true);
        let a = [];
        // a.push(console);
        console.log('query: ' + 'print_request.action');
        a.push(fetch('https://hatter.ink/util/print_request.action', {
          headers: {
            'Test-Header': 'this is a test header',
          }
        }));
        // a.push(fetch('https://hatter.ink/ip.action'));
        console.log('query: ' + 'ip.jsonp');
        a.push(fetch('https://hatter.ink/ip/ip.jsonp'));
        // a.push(fetch('https://hatter.ink/ip2.action'));
        a.push({
            userId: 'id001',
            name: 'Test',
        });

        JSON.stringify(a)
        // a
    "##);
    let eval_e = SystemTime::now().duration_since(eval_t1).expect("get duration error");
    println!("+ eval cost: {}ms", eval_e.as_millis());
    let a = match a {
        Ok(a) => a,
        Err(a) => {
            let e = format!("{}", a);
            if e.contains("all fuel consumed by WebAssembly") {
                println!("all fuel consumed by WebAssembly");
            } else {
                println!("{}", a);
            }
            return Ok(());
        }
    };
    // let a = a.expect("error");
    let a: Value = match serde_json::from_str(&a) {
        Ok(a) => a,
        Err(e) => panic!("ERROR: {}", e),
    };
    let a = match a {
        Value::String(a) => a,
        _ => panic!("error: {}", a),
    };

    let v: Value = match serde_json::from_str(&a) {
        Ok(v) => v,
        Err(e) => panic!("Parse json1 failed: {}, raw json:\n - {}", e, a),
    };
    let p = serde_json::to_string_pretty(&v).expect("error");
    // println!("------- {}", v);
    println!("{}", p);

    let main_e = SystemTime::now().duration_since(main_t1).expect("get duration error");
    println!("+ total cost (-eval): {}ms", main_e.as_millis() - eval_e.as_millis());
    println!("+ total cost: {}ms", main_e.as_millis());
    Ok(())
}

struct Context<I, E> {
    imports: I,
    exports: E,
}

fn instantiate<I: Default, E: Default, T>(
    wasm: &str,
    add_imports: impl FnOnce(&mut Linker<Context<I, E>>) -> Result<()>,
    mk_exports: impl FnOnce(
        &mut Store<Context<I, E>>,
        &Module,
        &mut Linker<Context<I, E>>,
    ) -> Result<(T, Instance)>,
) -> Result<(T, Store<Context<I, E>>)> {
    let engine = Engine::new(&default_config()?)?;
    let module = Module::from_file(&engine, wasm)?;

    let mut linker = Linker::new(&engine);
    add_imports(&mut linker)?;

    let mut store = Store::new(
        &engine,
        Context {
            imports: I::default(),
            exports: E::default(),
        },
    );
    store.add_fuel(1_000_000_000_u64).ok(); // FUEL
    let (exports, _instance) = mk_exports(&mut store, &module, &mut linker)?;
    Ok((exports, store))
}

fn default_config() -> Result<Config> {
    // Create an engine with caching enabled to assist with iteration in this project.
    let mut config = Config::new();
    config.cache_config_load_default()?;
    config.consume_fuel(true); // FUEL
    // config.debug_info(true);
    config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
    Ok(config)
}