use crate::message_processor::ConnectionSessionState;
use crate::outgoing_message::ConnectionId;
use crate::transport::AppServerTransport;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCRequest;
use codex_otel::set_parent_from_context;
use codex_otel::set_parent_from_w3c_trace_context;
use codex_otel::traceparent_context_from_env;
use codex_protocol::protocol::W3cTraceContext;
use tracing::Span;
use tracing::field;
use tracing::info_span;

pub(crate) fn request_span(
    request: &JSONRPCRequest,
    transport: AppServerTransport,
    connection_id: ConnectionId,
    session: &ConnectionSessionState,
) -> Span {
    let span = info_span!(
        "app_server.request",
        otel.kind = "server",
        otel.name = request.method.as_str(),
        rpc.system = "jsonrpc",
        rpc.method = request.method.as_str(),
        rpc.transport = transport_name(transport),
        rpc.request_id = ?request.id,
        app_server.connection_id = ?connection_id,
        app_server.api_version = "v2",
        app_server.client_name = field::Empty,
        app_server.client_version = field::Empty,
    );

    let initialize_client_info = initialize_client_info(request);
    if let Some(client_name) = client_name(initialize_client_info.as_ref(), session) {
        span.record("app_server.client_name", client_name);
    }
    if let Some(client_version) = client_version(initialize_client_info.as_ref(), session) {
        span.record("app_server.client_version", client_version);
    }

    if let Some(traceparent) = request
        .trace
        .as_ref()
        .and_then(|trace| trace.traceparent.as_deref())
    {
        let trace = W3cTraceContext {
            traceparent: Some(traceparent.to_string()),
            tracestate: request
                .trace
                .as_ref()
                .and_then(|value| value.tracestate.clone()),
        };
        if !set_parent_from_w3c_trace_context(&span, &trace) {
            tracing::warn!(
                rpc_method = request.method.as_str(),
                rpc_request_id = ?request.id,
                "ignoring invalid inbound request trace carrier"
            );
        }
    } else if let Some(context) = traceparent_context_from_env() {
        set_parent_from_context(&span, context);
    }

    span
}

fn transport_name(transport: AppServerTransport) -> &'static str {
    match transport {
        AppServerTransport::Stdio => "stdio",
        AppServerTransport::WebSocket { .. } => "websocket",
    }
}

fn client_name<'a>(
    initialize_client_info: Option<&'a InitializeParams>,
    session: &'a ConnectionSessionState,
) -> Option<&'a str> {
    if let Some(params) = initialize_client_info {
        return Some(params.client_info.name.as_str());
    }
    session.app_server_client_name.as_deref()
}

fn client_version<'a>(
    initialize_client_info: Option<&'a InitializeParams>,
    session: &'a ConnectionSessionState,
) -> Option<&'a str> {
    if let Some(params) = initialize_client_info {
        return Some(params.client_info.version.as_str());
    }
    session.client_version.as_deref()
}

fn initialize_client_info(request: &JSONRPCRequest) -> Option<InitializeParams> {
    if request.method != "initialize" {
        return None;
    }
    let params = request.params.clone()?;
    serde_json::from_value(params).ok()
}
