use crate::app::AppState;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::routes::stream_support::{
    build_stream_push_delta_hint, load_stream_snapshot, StreamSnapshotQuery,
};
use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
use axum::extract::{Extension, Query, State};
use axum::response::Response;
use futures_util::StreamExt;
use serde_json::{json, Value};
use tokio::sync::broadcast::error::RecvError;

pub async fn socket(
    ws: WebSocketUpgrade,
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Query(query): Query<StreamSnapshotQuery>,
) -> Result<Response, WebError> {
    let snapshot = load_stream_snapshot(state.config(), &context, &query).await?;
    let state = state.clone();
    let context = context.clone();
    let query = query.clone();

    Ok(ws.on_upgrade(move |socket| handle_socket(socket, state, context, query, snapshot)))
}

async fn handle_socket(
    mut socket: WebSocket,
    state: AppState,
    context: RequestContext,
    query: StreamSnapshotQuery,
    payload: Value,
) {
    let mut stream_delta_rx = state.stream_delta_tx.subscribe();
    let _ = socket.send(serialize_snapshot_message(&payload)).await;

    loop {
        tokio::select! {
            biased;

            // 优先处理 broadcast 推送的变更通知
            delta_result = stream_delta_rx.recv() => {
                match delta_result {
                    Ok(delta) => {
                        if socket
                            .send(serialize_delta_message(
                                &delta,
                                &context.trace.request_id,
                                &context.trace.trace_id,
                            ))
                            .await
                            .is_err()
                        {
                            break;
                        }
                    }
                    Err(RecvError::Lagged(n)) => {
                        // Lagged: 发送 resync 提示让客户端重新加载
                        let lagged_hint = json!({
                            "kind": "resync_hint",
                            "reason": "stream lagged",
                            "dropped": n,
                            "requestId": context.trace.request_id,
                            "traceId": context.trace.trace_id,
                        });
                        let _ = socket.send(Message::Text(lagged_hint.to_string().into())).await;
                    }
                    Err(RecvError::Closed) => {
                        // Broadcast channel closed, WS stays open for client-initiated resync
                    }
                }
            }

            // 处理客户端消息（resync 请求等）
            message = socket.next() => {
                match message {
                    Some(Ok(Message::Text(text))) => {
                        if is_resync_request(&text) {
                            match load_stream_snapshot(state.config(), &context, &query).await {
                                Ok(snapshot) => {
                                    if socket
                                        .send(serialize_resync_message(&snapshot))
                                        .await
                                        .is_err()
                                    {
                                        break;
                                    }
                                    // 重订阅 broadcast（可能丢掉了中间的变更）
                                    stream_delta_rx = state.stream_delta_tx.subscribe();
                                }
                                Err(error) => {
                                    let err_payload = json!({
                                        "kind": "error",
                                        "code": "snapshot_reload_failed",
                                        "message": format!("{error:?}"),
                                        "requestId": context.trace.request_id,
                                        "traceId": context.trace.trace_id,
                                    });
                                    if socket
                                        .send(Message::Text(err_payload.to_string().into()))
                                        .await
                                        .is_err()
                                    {
                                        break;
                                    }
                                }
                            }
                        } else {
                            let ack = json!({
                                "kind": "ack",
                                "requestId": context.trace.request_id,
                                "traceId": context.trace.trace_id,
                                "accepted": false,
                                "reason": "unsupported_message",
                            });
                            if socket.send(Message::Text(ack.to_string().into())).await.is_err() {
                                break;
                            }
                        }
                    }
                    Some(Ok(Message::Close(_))) => break,
                    Some(Ok(_)) => {}  // ignore binary/ping/pong
                    Some(Err(_)) => break,
                    None => break,
                }
            }
        }
    }
}

fn serialize_snapshot_message(payload: &Value) -> Message {
    Message::Text(payload.to_string().into())
}

fn serialize_delta_message(payload: &Value, request_id: &str, trace_id: &str) -> Message {
    Message::Text(
        build_stream_push_delta_hint(payload, request_id, trace_id)
            .to_string()
            .into(),
    )
}

fn serialize_resync_message(payload: &Value) -> Message {
    Message::Text(
        json!({
            "kind": "resync",
            "snapshot": payload,
        })
        .to_string()
        .into(),
    )
}

fn is_resync_request(text: &str) -> bool {
    let trimmed = text.trim();
    if trimmed.eq_ignore_ascii_case("resync") {
        return true;
    }

    serde_json::from_str::<Value>(trimmed)
        .ok()
        .and_then(|value| value.get("type").and_then(Value::as_str).map(str::to_owned))
        .map(|value| value.eq_ignore_ascii_case("resync"))
        .unwrap_or(false)
}

#[cfg(test)]
mod tests {
    use super::{
        is_resync_request, serialize_delta_message, serialize_resync_message,
        serialize_snapshot_message,
    };
    use axum::extract::ws::Message;
    use serde_json::json;

    #[test]
    fn ws_resync_detection_accepts_plain_text_and_json() {
        assert!(is_resync_request("resync"));
        assert!(is_resync_request(r#"{"type":"resync"}"#));
        assert!(!is_resync_request("hello"));
    }

    #[test]
    fn ws_snapshot_serializers_emit_text_frames() {
        let snapshot = json!({
            "kind": "snapshot",
            "scope": "workspace",
        });
        let Message::Text(snapshot_text) = serialize_snapshot_message(&snapshot) else {
            panic!("snapshot message 应该是文本帧");
        };
        assert!(snapshot_text.contains("\"kind\":\"snapshot\""));

        let Message::Text(resync_text) = serialize_resync_message(&snapshot) else {
            panic!("resync message 应该是文本帧");
        };
        assert!(resync_text.contains("\"kind\":\"resync\""));
    }

    #[test]
    fn ws_delta_serializer_uses_stream_hint_contract() {
        let delta = json!({
            "kind": "command_committed",
            "commandName": "tree.rename",
            "commandId": "cmd_1",
            "workspaceId": "ws_demo",
            "requestId": "req_command",
            "traceId": "trace_command"
        });
        let Message::Text(delta_text) = serialize_delta_message(&delta, "req_ws", "trace_ws")
        else {
            panic!("delta message 应该是文本帧");
        };
        let payload: serde_json::Value = serde_json::from_str(&delta_text).expect("delta json");
        assert_eq!(payload["kind"], "delta");
        assert_eq!(payload["hint"], "command_committed");
        assert_eq!(payload["commandName"], "tree.rename");
        assert_eq!(payload["commandId"], "cmd_1");
        assert_eq!(payload["workspaceId"], "ws_demo");
        assert_eq!(payload["requestId"], "req_ws");
        assert_eq!(payload["traceId"], "trace_ws");
        assert!(payload.get("data").is_none());
    }
}
