#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tauri::State;

/// Strict allowlist of permitted Bitcoin Core RPC methods.
/// A compromised webview CANNOT call arbitrary RPC methods.
const ALLOWED_RPC_METHODS: &[&str] = &[
    "getblockcount",
    "getnetworkinfo",
    "getreceivedbyaddress",
    "listunspent",
    "estimatesmartfee",
    "listreceivedbyaddress",
    "sendrawtransaction",
    "getrawtransaction",
    "decoderawtransaction",
    "createpsbt",
    "signrawtransactionwithkey",
    "finalizepsbt",
    "getdescriptorinfo",
    "deriveaddresses",
    "createmultisig",
    "walletcreatefundedpsbt",
    "decodepsbt",
];

#[derive(Clone, Serialize, Deserialize)]
struct RpcRequest {
    method: String,
    params: Vec<Value>,
}

#[derive(Clone)]
struct BitcoinConfig {
    url: String,
    username: String,
    password: String,
}

struct AppState {
    bitcoin: BitcoinConfig,
    http: reqwest::Client,
}

async fn do_bitcoin_rpc(
    http: &reqwest::Client,
    config: &BitcoinConfig,
    request: &RpcRequest,
) -> Result<Value, String> {
    // Enforce allowlist — reject any method not explicitly permitted
    if !ALLOWED_RPC_METHODS.contains(&request.method.as_str()) {
        return Err(format!(
            "RPC method not allowed: {}. Only methods in the allowlist are permitted.",
            request.method
        ));
    }

    let payload = serde_json::json!({
        "jsonrpc": "1.0",
        "id": "btc-wallet",
        "method": request.method,
        "params": request.params,
    });

    let resp = http
        .post(&config.url)
        .basic_auth(&config.username, Some(&config.password))
        .json(&payload)
        .send()
        .await
        .map_err(|e| format!("RPC request failed: {e}"))?;

    let body: Value = resp
        .json()
        .await
        .map_err(|e| format!("Failed to parse RPC response: {e}"))?;

    if let Some(err) = body.get("error") {
        return Err(format!("RPC error: {err:?}"));
    }

    Ok(body.get("result").cloned().unwrap_or(Value::Null))
}

/// Call into the Python wallet engine for crypto operations.
/// Uses an allowlist of permitted commands instead of forwarding arbitrary strings.
const ALLOWED_PYTHON_COMMANDS: &[&str] = &[
    "generate_seed",
    "create_wallet",
    "derive_addresses",
    "get_balance",
    "get_block_count",
    "get_network_info",
    "validate_seed",
    "import_wallet",
    "get_utxos",
    "create_psbt",
    "sign_psbt",
    "finalize_psbt",
    "sign_and_finalize_psbt",
    "broadcast",
    "decode_psbt",
    "get_tx_history",
    "estimate_fee",
    "save_wallet",
    "load_wallet",
    "list_wallets",
    "set_label",
    "get_utxo_tree",
    "create_multisig_wallet",
    "get_multisig_balance",
    "get_multisig_utxos",
    "get_multisig_addresses",
    "create_multisig_psbt",
    "sign_multisig_psbt",
    "combine_psbt",
    "finalize_multisig_psbt",
];

#[tauri::command]
async fn python_proxy(
    state: State<'_, AppState>,
    command: String,
    args: Value,
) -> Result<Value, String> {
    // Enforce allowlist — reject any command not explicitly permitted
    if !ALLOWED_PYTHON_COMMANDS.contains(&command.as_str()) {
        return Err(format!(
            "Python command not allowed: {}. Only commands in the allowlist are permitted.",
            command
        ));
    }

    let python_url = std::env::var("BTC_PYTHON_URL")
        .unwrap_or_else(|_| "http://127.0.0.1:14195/api".to_string());
    let endpoint = format!("{}/{}", python_url, command);
    let api_key = std::env::var("WALLET_API_KEY").unwrap_or_default();

    let mut builder = state
        .http
        .post(&endpoint)
        .json(&args);

    if !api_key.is_empty() {
        builder = builder.header("X-API-Key", &api_key);
    }

    let resp = builder
        .send()
        .await
        .map_err(|e| format!("Python proxy failed: {e}"))?;

    resp.json::<Value>()
        .await
        .map_err(|e| format!("Failed to parse Python response: {e}"))
}

#[tauri::command]
async fn get_block_count(state: State<'_, AppState>) -> Result<i64, String> {
    let result = do_bitcoin_rpc(
        &state.http,
        &state.bitcoin,
        &RpcRequest {
            method: "getblockcount".to_string(),
            params: vec![],
        },
    )
    .await?;

    result.as_i64().ok_or("Invalid block count response".to_string())
}

#[tauri::command]
async fn get_network_info(state: State<'_, AppState>) -> Result<Value, String> {
    do_bitcoin_rpc(
        &state.http,
        &state.bitcoin,
        &RpcRequest {
            method: "getnetworkinfo".to_string(),
            params: vec![],
        },
    )
    .await
}

#[tauri::command]
async fn get_balance(
    state: State<'_, AppState>,
    addresses: Vec<String>,
) -> Result<Value, String> {
    let mut total = 0.0_f64;
    for addr in &addresses {
        let result = do_bitcoin_rpc(
            &state.http,
            &state.bitcoin,
            &RpcRequest {
                method: "getreceivedbyaddress".to_string(),
                params: vec![Value::String(addr.clone()), Value::Number(serde_json::Number::from(0))],
            },
        ).await?;
        if let Some(val) = result.as_f64() {
            total += val;
        }
    }
    Ok(json!({ "balance_btc": total, "balance_sats": (total * 100_000_000.0) as u64 }))
}

#[tauri::command]
async fn get_utxos(
    state: State<'_, AppState>,
    addresses: Vec<String>,
) -> Result<Value, String> {
    let result = do_bitcoin_rpc(
        &state.http,
        &state.bitcoin,
        &RpcRequest {
            method: "listunspent".to_string(),
            params: vec![Value::Array(vec![
                Value::Number(serde_json::Number::from(0)),
                Value::Number(serde_json::Number::from(999999)),
                Value::Array(addresses.into_iter().map(Value::String).collect()),
            ])],
        },
    )
    .await?;

    if let Some(arr) = result.as_array() {
        Ok(json!({ "utxos": arr }))
    } else {
        Ok(json!({ "utxos": [] }))
    }
}

#[tauri::command]
async fn estimate_fee(
    state: State<'_, AppState>,
    blocks: Option<u32>,
) -> Result<Value, String> {
    let blocks = blocks.unwrap_or(6);
    let result = do_bitcoin_rpc(
        &state.http,
        &state.bitcoin,
        &RpcRequest {
            method: "estimatesmartfee".to_string(),
            params: vec![Value::Number(serde_json::Number::from(blocks))],
        },
    ).await?;
    Ok(result)
}

#[tauri::command]
async fn get_tx_history(
    state: State<'_, AppState>,
    address: String,
    count: Option<u32>,
) -> Result<Value, String> {
    let count = count.unwrap_or(20);
    let result = do_bitcoin_rpc(
        &state.http,
        &state.bitcoin,
        &RpcRequest {
            method: "listreceivedbyaddress".to_string(),
            params: vec![
                Value::Number(serde_json::Number::from(0)),
                Value::Bool(true),
                Value::Array(vec![Value::String(address)]),
            ],
        },
    ).await?;
    // Filter to most recent `count`
    if let Some(arr) = result.as_array() {
        let filtered: Vec<&Value> = arr.iter().take(count as usize).collect();
        Ok(json!(filtered))
    } else {
        Ok(json!([]))
    }
}

fn main() {
    let rpc_url = std::env::var("BTC_RPC_URL")
        .unwrap_or_else(|_| "http://127.0.0.1:8332".to_string());
    let rpc_user = std::env::var("BTC_RPC_USER").unwrap_or_default();
    let rpc_pass = std::env::var("BTC_RPC_PASS").unwrap_or_default();

    if rpc_user.is_empty() || rpc_pass.is_empty() {
        eprintln!(
            "Error: BTC_RPC_USER and BTC_RPC_PASS environment variables are required.\n\
             Set them before running the application."
        );
        std::process::exit(1);
    }

    let config = BitcoinConfig {
        url: rpc_url,
        username: rpc_user,
        password: rpc_pass,
    };

    let http = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(30))
        .build()
        .expect("Failed to build HTTP client");

    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        // NOTE: tauri_plugin_shell is loaded but disabled via capabilities.
        // Do not grant shell access in capabilities unless specifically needed.
        .manage(AppState { bitcoin: config, http })
        .invoke_handler(tauri::generate_handler![
            python_proxy,
            get_block_count,
            get_network_info,
            get_balance,
            get_utxos,
            estimate_fee,
            get_tx_history,
        ])
        .run(tauri::generate_context!())
        .expect("failed to run BTC Wallet");
}