// file: kb_demo_app/src/demo_http.rs

//! Tauri commands for the HTTP demo window.
//!
//! This module exposes a small manual test surface over the HTTP endpoint pool.

use tauri::Manager;

/// Request payload for one demo HTTP execution.
#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoHttpRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoHttpRequest {
    /// Logical role used to select one endpoint from the pool.
    pub role: std::string::String,
    /// JSON-RPC HTTP method name.
    pub method: std::string::String,
    /// Optional first string argument, used by methods such as
    /// `getBalance`, `getAccountInfo`, `getProgramAccounts`,
    /// `getSignaturesForAddress`, `getTransaction`, `sendTransaction`.
    pub first_arg: std::option::Option<std::string::String>,
    /// Optional JSON config payload encoded as a string.
    pub config_json: std::option::Option<std::string::String>,
}

/// Response payload for one demo HTTP execution.
#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoHttpExecutionPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoHttpExecutionPayload {
    /// Selected endpoint name.
    pub endpoint_name: std::string::String,
    /// Selected endpoint provider.
    pub provider: std::string::String,
    /// Selected endpoint URL.
    pub endpoint_url: std::string::String,
    /// Requested role.
    pub role: std::string::String,
    /// Executed method name.
    pub method: std::string::String,
    /// Classified method family.
    pub method_class: std::string::String,
    /// Pretty-printed JSON response payload.
    pub response_json: std::string::String,
}

/// Opens the dedicated HTTP demo window.
#[tauri::command]
pub(crate) fn open_demo_http_window(
    app_handle: tauri::AppHandle,
) -> Result<(), std::string::String> {
    let existing_window_option = app_handle.get_webview_window("demo_http");

    let demo_window = match existing_window_option {
        Some(demo_window) => demo_window,
        None => {
            let builder = tauri::WebviewWindowBuilder::new(
                &app_handle,
                "demo_http",
                tauri::WebviewUrl::App("demo_http.html".into()),
            )
            .title("Demo Http")
            .inner_size(1400.0, 768.0)
            .min_inner_size(800.0, 600.0)
            .center()
            .visible(true)
            .transparent(false)
            .decorations(true);
            let build_result = builder.build();
            match build_result {
                Ok(window) => window,
                Err(error) => {
                    return Err(format!("cannot create demo_http window: {error:?}"));
                },
            }
        },
    };
    let show_result = demo_window.show();
    if let Err(error) = show_result {
        return Err(format!("cannot show demo_http window: {error:?}"));
    }
    let focus_result = demo_window.set_focus();
    if let Err(error) = focus_result {
        return Err(format!("cannot focus demo_http window: {error:?}"));
    }
    Ok(())
}

/// Returns a fresh snapshot of the HTTP endpoint pool.
#[tauri::command]
pub(crate) async fn demo_http_list_pool_clients(
    state: tauri::State<'_, crate::AppState>,
) -> Result<std::vec::Vec<kb_lib::HttpPoolClientSnapshot>, std::string::String> {
    Ok(state.http_pool.snapshot().await)
}

/// Executes one manual HTTP request through the endpoint pool.
#[tauri::command]
pub(crate) async fn demo_http_execute_request(
    state: tauri::State<'_, crate::AppState>,
    request: DemoHttpRequest,
) -> Result<DemoHttpExecutionPayload, std::string::String> {
    let role = request.role.trim().to_string();
    if role.is_empty() {
        return Err("demo http role must not be empty".to_string());
    }
    let method = request.method.trim().to_string();
    if method.is_empty() {
        return Err("demo http method must not be empty".to_string());
    }
    let config_json_value_result = parse_optional_demo_http_json(request.config_json);
    let config_json_value = match config_json_value_result {
        Ok(config_json_value) => config_json_value,
        Err(error) => return Err(error),
    };
    let params_result =
        build_demo_http_params(&method, request.first_arg.as_deref(), config_json_value);
    let params = match params_result {
        Ok(params) => params,
        Err(error) => return Err(error),
    };
    let selected_client_result =
        state.http_pool.select_client_for_role_and_method(&role, &method).await;
    let selected_client = match selected_client_result {
        Ok(selected_client) => selected_client,
        Err(error) => return Err(error.to_string()),
    };
    let method_class = kb_lib::HttpClient::classify_method(&method);
    let method_class_text = demo_http_method_class_to_string(method_class);
    tracing::trace!(
        endpoint_name = %selected_client.endpoint_name(),
        endpoint_url = %selected_client.endpoint_url(),
        role = %role,
        method = %method,
        method_class = %method_class_text,
        "executing demo http request"
    );
    let response_value_result =
        selected_client.execute_json_rpc_result_raw(method.clone(), params).await;
    let response_value = match response_value_result {
        Ok(response_value) => response_value,
        Err(error) => return Err(error.to_string()),
    };
    let response_json_result = serde_json::to_string_pretty(&response_value);
    let response_json = match response_json_result {
        Ok(response_json) => response_json,
        Err(error) => {
            return Err(format!(
                "cannot pretty-print demo http response for method '{}': {}",
                method, error
            ));
        },
    };
    Ok(DemoHttpExecutionPayload {
        endpoint_name: selected_client.endpoint_name().to_string(),
        provider: selected_client.endpoint_config().provider.clone(),
        endpoint_url: selected_client.endpoint_url().to_string(),
        role,
        method,
        method_class: method_class_text.to_string(),
        response_json,
    })
}

fn parse_optional_demo_http_json(
    config_json: std::option::Option<std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, std::string::String> {
    let config_json = match config_json {
        Some(config_json) => config_json.trim().to_string(),
        None => {
            return Ok(None);
        },
    };
    if config_json.is_empty() {
        return Ok(None);
    }
    let value_result = serde_json::from_str::<serde_json::Value>(&config_json);
    match value_result {
        Ok(value) => Ok(Some(value)),
        Err(error) => Err(format!("invalid configJson: {}", error)),
    }
}

fn build_demo_http_params(
    method: &str,
    first_arg: std::option::Option<&str>,
    config_json: std::option::Option<serde_json::Value>,
) -> Result<std::vec::Vec<serde_json::Value>, std::string::String> {
    let needs_first_arg = matches!(
        method,
        "getBalance"
            | "getAccountInfo"
            | "getProgramAccounts"
            | "getSignaturesForAddress"
            | "getTransaction"
            | "sendTransaction"
    );
    if needs_first_arg {
        let first_arg = match first_arg {
            Some(first_arg) => first_arg.trim(),
            None => "",
        };
        if first_arg.is_empty() {
            return Err(format!("method '{}' requires firstArg", method));
        }
        let mut params = vec![serde_json::Value::String(first_arg.to_string())];
        if let Some(config_json) = config_json {
            params.push(config_json);
        }
        return Ok(params);
    }
    let mut params = std::vec::Vec::new();
    if let Some(config_json) = config_json {
        params.push(config_json);
    }
    Ok(params)
}

fn demo_http_method_class_to_string(method_class: kb_lib::HttpMethodClass) -> &'static str {
    match method_class {
        kb_lib::HttpMethodClass::GeneralRpc => "GeneralRpc",
        kb_lib::HttpMethodClass::SendTransaction => "SendTransaction",
        kb_lib::HttpMethodClass::HeavyRead => "HeavyRead",
    }
}
