/// generated
use askama::Template;
use bytes::Bytes;
use cbwaw::AuthClient;
use notatype::{Kind, flexbuffer_reader_to_json, json_to_flexbuffer_vec};
use scramblr::*;
use serde::Serialize;
use std::error::Error;
use wasm_bindgen::prelude::*;

// todo: should be able to configure which filters a template uses, to decrease wasm size
mod filters {
    pub fn markdown(s: &str, _: &dyn askama::Values) -> askama::Result<String> {
        Ok(markdown::to_html(s))
    }

    pub fn timestamp_s_to_rfc_2822(
        timestamp_s: &i64,
        _: &dyn askama::Values,
    ) -> askama::Result<String> {
        if let Some(date_time) = chrono::DateTime::from_timestamp(*timestamp_s, 0) {
            Ok(date_time.to_rfc2822())
        } else {
            Err(askama::Error::Custom(
                "failed to get timestamp".to_string().into(),
            ))
        }
    }

    pub fn timestamp_s_to_formatted(
        timestamp_s: &i64,
        _: &dyn askama::Values,
        format: &str,
    ) -> askama::Result<String> {
        if let Some(date_time) = chrono::DateTime::from_timestamp(*timestamp_s, 0) {
            let formatted = date_time.format(format);
            let mut string = String::new();
            if formatted.write_to(&mut string).is_ok() {
                Ok(string)
            } else {
                Err(askama::Error::Custom(
                    "failed to write formatted to string".to_string().into(),
                ))
            }
        } else {
            Err(askama::Error::Custom(
                "failed to get timestamp".to_string().into(),
            ))
        }
    }

    pub fn uuid_str(bytes: &[u8; 16], _: &dyn askama::Values) -> askama::Result<String> {
        Ok(uuid::Uuid::from_bytes(*bytes).to_string())
    }

    pub fn uuid_to_formatted(
        bytes: &[u8; 16],
        av: &dyn askama::Values,
        format: &str,
    ) -> askama::Result<String> {
        let uuid = uuid::Uuid::from_bytes(*bytes);

        match uuid.get_timestamp() {
            Some(ts) => timestamp_s_to_formatted(&(ts.to_unix().0 as i64), av, format),
            None => Ok(uuid.to_string()),
        }
    }
}

#[wasm_bindgen]
pub struct RegistrationStartReq {
    client_state: Vec<u8>,
    request: Vec<u8>,
}

#[wasm_bindgen]
impl RegistrationStartReq {
    #[wasm_bindgen(getter)]
    pub fn client_state(&self) -> Vec<u8> {
        self.client_state.clone()
    }
    #[wasm_bindgen(getter)]
    pub fn request(&self) -> Vec<u8> {
        self.request.clone()
    }
}

#[wasm_bindgen]
pub fn registration_start_req(
    account: String,
    password: Vec<u8>,
    invite_token: Option<Vec<u8>>,
) -> Result<RegistrationStartReq, String> {
    match AuthClient::registration_start_req(
        account.as_bytes(),
        &password,
        invite_token.map(|t| Bytes::copy_from_slice(&t[..])),
    ) {
        Ok((client_state, request)) => Ok(RegistrationStartReq {
            client_state,
            request: request.to_vec(),
        }),
        Err(err) => Err(err.to_string()),
    }
}

#[wasm_bindgen]
pub struct RegistrationFinishReq {
    private_key: Vec<u8>,
    request: Vec<u8>,
}

#[wasm_bindgen]
impl RegistrationFinishReq {
    #[wasm_bindgen(getter)]
    pub fn private_key(&self) -> Vec<u8> {
        self.private_key.clone()
    }
    #[wasm_bindgen(getter)]
    pub fn request(&self) -> Vec<u8> {
        self.request.clone()
    }
}

#[wasm_bindgen]
pub fn registration_finish_req(
    account: String,
    password: Vec<u8>,
    client_state: Vec<u8>,
    server_message: Vec<u8>,
) -> Result<RegistrationFinishReq, String> {
    match AuthClient::registration_finish_req(
        account.as_bytes(),
        &password,
        &client_state,
        &server_message,
    ) {
        Ok((private_key, request)) => Ok(RegistrationFinishReq {
            private_key: private_key.to_vec(),
            request: request.to_vec(),
        }),
        Err(err) => Err(err.to_string()),
    }
}

#[wasm_bindgen]
pub fn decrypt_totp_mfa(
    response: Vec<u8>,
    private_key: Vec<u8>,
    app_name: String,
    account: String,
) -> Result<String, String> {
    let private_key: [u8; 32] = match private_key.try_into() {
        Ok(key) => key,
        Err(_) => return Err("private key is not 32 bytes".into()),
    };

    match AuthClient::decrypt_totp_mfa_to_qr_svg(
        Bytes::from(response),
        private_key,
        app_name,
        account,
    ) {
        Ok((qr_code, recovery_codes)) => Ok(format!("{recovery_codes}__{qr_code}")),
        Err(err) => Err(err.to_string()),
    }
}

#[wasm_bindgen]
pub struct LoginStartReq {
    client_state: Vec<u8>,
    request: Vec<u8>,
}

#[wasm_bindgen]
impl LoginStartReq {
    #[wasm_bindgen(getter)]
    pub fn client_state(&self) -> Vec<u8> {
        self.client_state.clone()
    }
    #[wasm_bindgen(getter)]
    pub fn request(&self) -> Vec<u8> {
        self.request.clone()
    }
}

#[wasm_bindgen]
pub fn login_start_req(account: String, password: Vec<u8>) -> Result<LoginStartReq, String> {
    match AuthClient::login_start_req(account.as_bytes(), &password) {
        Ok((client_state, request)) => Ok(LoginStartReq {
            client_state,
            request: request.to_vec(),
        }),
        Err(err) => Err(err.to_string()),
    }
}

#[wasm_bindgen]
pub struct LoginFinishReq {
    session_key: Vec<u8>,
    request: Vec<u8>,
}

#[wasm_bindgen]
impl LoginFinishReq {
    #[wasm_bindgen(getter)]
    pub fn session_key(&self) -> Vec<u8> {
        self.session_key.clone()
    }
    #[wasm_bindgen(getter)]
    pub fn request(&self) -> Vec<u8> {
        self.request.clone()
    }
}

#[wasm_bindgen]
pub fn login_finish_req(
    account: String,
    password: Vec<u8>,
    mfa_code: Vec<u8>,
    client_state: Vec<u8>,
    server_message: Vec<u8>,
    client_verifier: Vec<u8>,
) -> Result<LoginFinishReq, String> {
    match AuthClient::login_finish_req(
        account.as_bytes(),
        &password,
        &mfa_code,
        &client_state,
        &server_message,
        Some(&client_verifier),
    ) {
        Ok((request, session_key)) => Ok(LoginFinishReq {
            session_key,
            request: request.to_vec(),
        }),
        Err(err) => Err(err.to_string()),
    }
}

#[wasm_bindgen]
pub fn decrypt_token(response: Vec<u8>, session_key: Vec<u8>) -> Result<Vec<u8>, String> {
    match AuthClient::decrypt_token(Bytes::from(response), &session_key) {
        Ok(token) => Ok(token.to_vec()),
        Err(err) => Err(err.to_string()),
    }
}

#[wasm_bindgen]
pub fn access_get_req(refresh_token: Vec<u8>) -> Result<Vec<u8>, String> {
    Ok(AuthClient::access_get_req(&refresh_token).to_vec())
}

fn invoke_req_from_json_and_kind_str(
    idx: u8,
    json_str: &str,
    kind_str: &str,
    token: Vec<u8>,
) -> Result<Vec<u8>, Box<dyn Error>> {
    let kind: Kind = serde_json::from_str(kind_str)?;
    let value: serde_json::Value = serde_json::from_str(json_str)?;

    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut builder_vec = builder.start_vector();

    builder_vec.push(idx);

    json_to_flexbuffer_vec(&kind, &value, &mut builder_vec)?;

    if token.len() > 0 {
        builder_vec.push(&token[..]);
    }

    builder_vec.end_vector();

    Ok(builder.view().to_vec())
}

#[wasm_bindgen]
pub fn invoke_req(
    idx: u8,
    json_str: String,
    kind_str: String,
    token: Vec<u8>,
) -> Result<Vec<u8>, String> {
    match invoke_req_from_json_and_kind_str(idx, &json_str, &kind_str, token) {
        Ok(val) => Ok(val),
        Err(err) => Err(err.to_string()),
    }
}

fn invoke_res_from_kind_str(kind_str: &str, payload: Vec<u8>) -> Result<String, Box<dyn Error>> {
    let kind: Kind = serde_json::from_str(kind_str)?;
    let root = flexbuffers::Reader::get_root(&payload[..])?;

    let json_res = flexbuffer_reader_to_json(&kind, root)?;

    Ok(json_res.to_string())
}

#[wasm_bindgen]
pub fn invoke_res(kind_str: String, payload: Vec<u8>) -> Result<String, String> {
    match invoke_res_from_kind_str(&kind_str, payload) {
        Ok(val) => Ok(val),
        Err(err) => Err(err.to_string()),
    }
}
