use crate::error::JsError;
use crate::{Requester, Result};
use async_trait::async_trait;
use std::collections::HashMap;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::Request;
use web_sys::RequestInit;
use web_sys::RequestMode;
use web_sys::Response;
//use js_sys::encode_uri_component;
#[cfg(feature = "wasm-bindings")]
use crate::client::BoxedRequester;
#[cfg(feature = "wasm-bindings")]
use wasm_bindgen::prelude::wasm_bindgen;

pub struct JsFetchRequester {
    proxy_url: String,
    cors: bool,
}

async fn request(
    requester: &JsFetchRequester,
    url: String,
    body: String,
    headers: HashMap<&str, &str>,
) -> std::result::Result<Vec<u8>, JsError> {
    let url = format!("{}{}", requester.proxy_url, url);
    let opts = {
        let mut opts = RequestInit::new();
        opts.method("POST");
        let body_value = JsValue::from_str(&body);
        opts.body(Some(&body_value));
        if requester.cors {
            opts.mode(RequestMode::Cors);
        }
        opts
    };
    let request = Request::new_with_str_and_init(&url, &opts)?;

    for (k, v) in headers {
        request.headers().set(k, v)?;
    }

    let window = web_sys::window().unwrap();
    let resp: Response = {
        let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
        // `resp_value` is a `Response` object.
        assert!(resp_value.is_instance_of::<Response>());
        resp_value.dyn_into()?
    };

    // Convert this other `Promise` into a rust `Future`.
    let json = JsFuture::from(resp.text()?).await?.as_string().unwrap();
    Ok(json.into_bytes())
}

#[async_trait(?Send)]
impl Requester for JsFetchRequester {
    async fn request(
        &self,
        url: String,
        body: String,
        headers: HashMap<&str, &str>,
    ) -> Result<Vec<u8>> {
        Ok(request(self, url, body, headers).await?)
    }
}

impl JsFetchRequester {
    pub fn new(proxy_url: &str, cors: bool) -> Self {
        Self {
            cors,
            proxy_url: proxy_url.to_string(),
        }
    }
}

#[cfg(feature = "wasm-bindings")]
#[wasm_bindgen]
impl JsFetchRequester {
    #[wasm_bindgen(constructor)]
    pub fn wasm_new(proxy_url: &str, cors: bool) -> BoxedRequester {
        Self::new(proxy_url, cors).into()
    }
}
