use reqwest::Client;
use serde_json::Value;

use crate::rpc::{JsonRpcRequest, JsonRpcResponse};

pub async fn forward_to_upstream(
    client: &Client,
    rpc_url: &str,
    request: &JsonRpcRequest,
) -> JsonRpcResponse {
    let resp = match client.post(rpc_url).json(request).send().await {
        Ok(resp) => resp,
        Err(e) => {
            return JsonRpcResponse::error(
                request.id.clone(),
                -32603,
                format!("Failed to reach upstream RPC: {e}"),
            );
        }
    };

    let body = match resp.text().await {
        Ok(body) => body,
        Err(e) => {
            return JsonRpcResponse::error(
                request.id.clone(),
                -32603,
                format!("Failed to read upstream response body: {e}"),
            );
        }
    };

    // Try parsing as a typed JsonRpcResponse first, fall back to raw Value passthrough
    match serde_json::from_str::<JsonRpcResponse>(&body) {
        Ok(mut rpc_resp) => {
            // Ensure valid JSON-RPC: if neither result nor error is present,
            // set result to null. Some RPCs omit `result` for pending receipts.
            if rpc_resp.result.is_none() && rpc_resp.error.is_none() {
                rpc_resp.result = Some(Value::Null);
            }
            rpc_resp
        }
        Err(_) => {
            // If it's valid JSON at all, wrap it as a raw result so the caller gets something useful
            match serde_json::from_str::<Value>(&body) {
                Ok(raw) => JsonRpcResponse {
                    jsonrpc: "2.0".into(),
                    result: Some(raw),
                    error: None,
                    id: request.id.clone(),
                },
                Err(e) => {
                    log::warn!(
                        "Upstream returned non-JSON for {}: {}",
                        request.method,
                        &body[..body.len().min(200)]
                    );
                    JsonRpcResponse::error(
                        request.id.clone(),
                        -32603,
                        format!("Upstream returned invalid JSON: {e}"),
                    )
                }
            }
        }
    }
}

/// Forward a batch of requests to upstream, preserving order.
pub async fn forward_batch_to_upstream(
    client: &Client,
    rpc_url: &str,
    requests: &[JsonRpcRequest],
) -> Vec<JsonRpcResponse> {
    match client
        .post(rpc_url)
        .json(requests)
        .send()
        .await
    {
        Ok(resp) => match resp.json::<Vec<JsonRpcResponse>>().await {
            Ok(rpc_resps) => rpc_resps,
            Err(e) => requests
                .iter()
                .map(|r| {
                    JsonRpcResponse::error(
                        r.id.clone(),
                        -32603,
                        format!("Failed to parse upstream batch response: {e}"),
                    )
                })
                .collect(),
        },
        Err(e) => requests
            .iter()
            .map(|r| {
                JsonRpcResponse::error(
                    r.id.clone(),
                    -32603,
                    format!("Failed to reach upstream RPC: {e}"),
                )
            })
            .collect(),
    }
}

/// Fetch the chain ID from upstream. Returns the hex string (e.g. "0x1").
pub async fn fetch_chain_id(client: &Client, rpc_url: &str) -> Option<Value> {
    let req = JsonRpcRequest {
        jsonrpc: "2.0".into(),
        method: "eth_chainId".into(),
        params: Value::Array(vec![]),
        id: Value::Number(1.into()),
    };
    let resp = forward_to_upstream(client, rpc_url, &req).await;
    resp.result
}
