#[cfg(all(feature = "mainnet", feature = "testnet"))]
compile_error!("contractless-browser-core must be built for exactly one network");

#[cfg(not(any(feature = "mainnet", feature = "testnet")))]
compile_error!("contractless-browser-core requires either the mainnet or testnet feature");

mod address;
mod crypto;
mod error;
mod legacy_wallet;
mod message;
mod registry;
mod transfer;
mod wallet;

use wasm_bindgen::prelude::*;

use error::BrowserCoreError;

pub const PUBLIC_KEY_LENGTH: usize = 897;
pub const PRIVATE_KEY_LENGTH: usize = 1281;
pub const SIGNATURE_LENGTH: usize = 666;
pub const REQUIRED_PUBLIC_KEY_HASH_BYTE: u8 = 239;

fn decode_hex(value: &str) -> Result<Vec<u8>, BrowserCoreError> {
    hex::decode(value).map_err(|error| BrowserCoreError::InvalidHex(error.to_string()))
}

#[wasm_bindgen]
pub fn skein_128_hex(data: &[u8]) -> String {
    hex::encode(crypto::skein_128(data))
}

#[wasm_bindgen]
pub fn skein_256_hex(data: &[u8]) -> String {
    hex::encode(crypto::skein_256(data))
}

#[wasm_bindgen]
pub fn skein_512_hex(data: &[u8]) -> String {
    hex::encode(crypto::skein_512(data))
}

#[wasm_bindgen]
pub fn verify_bytes_hex(
    message: &[u8],
    signature_hex: &str,
    public_key_hex: &str,
) -> Result<bool, JsValue> {
    let signature = decode_hex(signature_hex)?;
    let public_key = decode_hex(public_key_hex)?;
    Ok(crypto::verify(message, &signature, &public_key))
}

#[wasm_bindgen]
pub fn verify_hash_hex(
    hash_hex: &str,
    signature_hex: &str,
    public_key_hex: &str,
) -> Result<bool, JsValue> {
    let hash = decode_hex(hash_hex)?;
    verify_bytes_hex(&hash, signature_hex, public_key_hex)
}

#[wasm_bindgen]
pub fn public_key_to_short_address(public_key_hex: &str) -> Result<String, JsValue> {
    let public_key = decode_hex(public_key_hex)?;
    Ok(address::public_key_to_short_address(&public_key)?)
}

#[wasm_bindgen]
pub fn short_address_to_bytes(address: &str) -> Result<Vec<u8>, JsValue> {
    Ok(address::short_address_to_bytes(address)?)
}

#[wasm_bindgen]
pub fn bytes_to_short_address(bytes: &[u8]) -> Result<String, JsValue> {
    Ok(address::bytes_to_short_address(bytes)?)
}

#[wasm_bindgen]
pub fn decode_transfer(transaction_hex: &str) -> Result<String, JsValue> {
    let bytes = decode_hex(transaction_hex)?;
    let transfer = transfer::SignedTransfer::from_bytes(&bytes)?;
    serde_json::to_string(&transfer.browser_output()?)
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn decode_transaction(transaction_hex: &str) -> Result<String, JsValue> {
    let bytes = decode_hex(transaction_hex)?;
    if bytes.is_empty() {
        return Err(BrowserCoreError::InvalidRequest("transaction bytes were empty".into()).into());
    }

    let decoded = match bytes[0] {
        0 => {
            if bytes.len() != 49 {
                return Err(BrowserCoreError::InvalidRequest(
                    "genesis transaction must contain exactly 49 bytes".into(),
                )
                .into());
            }
            let message = std::str::from_utf8(&bytes[1..])
                .map_err(|_| BrowserCoreError::InvalidRequest("invalid genesis message".into()))?
                .trim_end_matches(|character| character == '\0' || character == ' ');
            serde_json::json!({
                "txtype": 0,
                "transaction_name": "Genesis",
                "fields": {
                    "txtype": 0,
                    "message": message
                },
                "stored_hash": null,
                "signature": null,
                "signature1": null,
                "signature2": null
            })
        }
        1 => {
            if bytes.len() != 13 {
                return Err(BrowserCoreError::InvalidRequest(
                    "reward transaction must contain exactly 13 bytes".into(),
                )
                .into());
            }
            let timestamp = u32::from_le_bytes(bytes[1..5].try_into().unwrap());
            let value = u64::from_le_bytes(bytes[5..13].try_into().unwrap());
            serde_json::json!({
                "txtype": 1,
                "transaction_name": "Mining Reward",
                "fields": {
                    "txtype": 1,
                    "timestamp": timestamp,
                    "value": value.to_string()
                },
                "stored_hash": null,
                "signature": null,
                "signature1": null,
                "signature2": null
            })
        }
        _ => serde_json::to_value(registry::decode_transaction(&bytes)?)
            .map_err(|error| BrowserCoreError::Json(error.to_string()))?,
    };

    serde_json::to_string(&decoded)
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn inspect_transaction_request(request_json: &str) -> Result<String, JsValue> {
    let request: serde_json::Value = serde_json::from_str(request_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    serde_json::to_string(&registry::inspect(&request)?)
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn sign_transaction_request(
    request_json: &str,
    private_key_hex: &str,
    public_key_hex: &str,
    signer_slot: u8,
) -> Result<String, JsValue> {
    let request: serde_json::Value = serde_json::from_str(request_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    let private_key = decode_hex(private_key_hex)?;
    let public_key = decode_hex(public_key_hex)?;
    serde_json::to_string(&registry::sign_request(
        &request,
        &private_key,
        &public_key,
        signer_slot,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn complete_dual_transaction_request(
    request_json: &str,
    signature1_hex: &str,
    public_key1_hex: &str,
    signature2_hex: &str,
    public_key2_hex: &str,
) -> Result<String, JsValue> {
    let request: serde_json::Value = serde_json::from_str(request_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    let public_key1 = decode_hex(public_key1_hex)?;
    let public_key2 = decode_hex(public_key2_hex)?;
    serde_json::to_string(&registry::complete_dual_request(
        &request,
        signature1_hex,
        &public_key1,
        signature2_hex,
        &public_key2,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn transaction_type_registry() -> Result<String, JsValue> {
    serde_json::to_string(&registry::entries())
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn generate_browser_wallet(password: &str) -> Result<String, JsValue> {
    serde_json::to_string_pretty(&wallet::generate(password)?)
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn unlock_browser_wallet(wallet_json: &str, password: &str) -> Result<String, JsValue> {
    let wallet: wallet::BrowserWallet = serde_json::from_str(wallet_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    serde_json::to_string(&wallet::unlock(&wallet, password)?)
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn import_legacy_wallet(
    legacy_wallet_json: &str,
    legacy_password: &str,
    browser_password: &str,
) -> Result<String, JsValue> {
    let legacy: wallet::LegacySavedWallet = serde_json::from_str(legacy_wallet_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    serde_json::to_string_pretty(&wallet::import_legacy(
        &legacy,
        legacy_password,
        browser_password,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn import_private_key_image(
    image_base64: &str,
    image_password: &str,
    browser_password: &str,
) -> Result<String, JsValue> {
    serde_json::to_string_pretty(&wallet::import_private_key_image(
        image_base64,
        image_password,
        browser_password,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn import_private_key(
    private_key_hex: &str,
    browser_password: &str,
) -> Result<String, JsValue> {
    serde_json::to_string_pretty(&wallet::import_private_key(
        private_key_hex,
        browser_password,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn export_legacy_wallet(
    browser_wallet_json: &str,
    browser_password: &str,
    legacy_password: &str,
) -> Result<String, JsValue> {
    let wallet: wallet::BrowserWallet = serde_json::from_str(browser_wallet_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    serde_json::to_string_pretty(&wallet::export_legacy(
        &wallet,
        browser_password,
        legacy_password,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn create_wallet_registration(
    browser_wallet_json: &str,
    password: &str,
) -> Result<String, JsValue> {
    let wallet: wallet::BrowserWallet = serde_json::from_str(browser_wallet_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    serde_json::to_string(&wallet::registration(&wallet, password)?)
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn create_wallet_registration_from_keys(
    short_address: &str,
    public_key_hex: &str,
    private_key_hex: &str,
) -> Result<String, JsValue> {
    serde_json::to_string(&wallet::registration_from_keys(
        short_address,
        public_key_hex,
        private_key_hex,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn create_handshake_proof(
    public_key_hex: &str,
    private_key_hex: &str,
) -> Result<String, JsValue> {
    Ok(wallet::handshake_proof(public_key_hex, private_key_hex)?)
}

#[wasm_bindgen]
pub fn sign_message_request(
    request_json: &str,
    private_key_hex: &str,
    public_key_hex: &str,
) -> Result<String, JsValue> {
    let request: message::MessageRequest = serde_json::from_str(request_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    let private_key = decode_hex(private_key_hex)?;
    let public_key = decode_hex(public_key_hex)?;
    serde_json::to_string(&message::sign(request, &private_key, &public_key)?)
        .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}

#[wasm_bindgen]
pub fn verify_message_proof(
    proof_json: &str,
    expected_origin: &str,
    expected_nonce: &str,
    current_time: &str,
) -> Result<String, JsValue> {
    let proof: message::MessageProof = serde_json::from_str(proof_json)
        .map_err(|error| BrowserCoreError::Json(error.to_string()))?;
    let current_time = current_time.parse::<u64>().map_err(|_| {
        BrowserCoreError::InvalidRequest(
            "current_time must be an unsigned decimal timestamp".into(),
        )
    })?;
    serde_json::to_string(&message::verify(
        proof,
        expected_origin,
        expected_nonce,
        current_time,
    )?)
    .map_err(|error| BrowserCoreError::Json(error.to_string()).into())
}
