//! Edge case tests for JSON-RPC layer
//!
//! Tests various edge cases and boundary conditions:
//! - Empty requests
//! - Null parameters
//! - Server shutdown scenarios
//! - Client disconnect handling

use futures::{AsyncRead, AsyncWrite};
use sacp::link::UntypedLink;
use sacp::{JrConnectionCx, JrMessage, JrRequest, JrRequestCx, JrResponse, JrResponsePayload};
use serde::{Deserialize, Serialize};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};

/// Test helper to block and wait for a JSON-RPC response.
async fn recv<T: JrResponsePayload + Send>(response: JrResponse<T>) -> Result<T, sacp::Error> {
    let (tx, rx) = tokio::sync::oneshot::channel();
    response.on_receiving_result(async move |result| {
        tx.send(result).map_err(|_| sacp::Error::internal_error())
    })?;
    rx.await.map_err(|_| sacp::Error::internal_error())?
}

/// Helper to set up test streams.
fn setup_test_streams() -> (
    impl AsyncRead,
    impl AsyncWrite,
    impl AsyncRead,
    impl AsyncWrite,
) {
    let (client_writer, server_reader) = tokio::io::duplex(1024);
    let (server_writer, client_reader) = tokio::io::duplex(1024);

    let server_reader = server_reader.compat();
    let server_writer = server_writer.compat_write();
    let client_reader = client_reader.compat();
    let client_writer = client_writer.compat_write();

    (server_reader, server_writer, client_reader, client_writer)
}

// ============================================================================
// Test types
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
struct EmptyRequest;

impl JrMessage for EmptyRequest {
    fn method(&self) -> &str {
        "empty_method"
    }

    fn to_untyped_message(&self) -> Result<sacp::UntypedMessage, sacp::Error> {
        sacp::UntypedMessage::new(self.method(), self)
    }

    fn parse_message(
        method: &str,
        _params: &impl serde::Serialize,
    ) -> Option<Result<Self, sacp::Error>> {
        if method != "empty_method" {
            return None;
        }
        Some(Ok(EmptyRequest))
    }
}

impl JrRequest for EmptyRequest {
    type Response = SimpleResponse;
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct OptionalParamsRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    value: Option<String>,
}

impl JrMessage for OptionalParamsRequest {
    fn method(&self) -> &str {
        "optional_params_method"
    }

    fn to_untyped_message(&self) -> Result<sacp::UntypedMessage, sacp::Error> {
        sacp::UntypedMessage::new(self.method(), self)
    }

    fn parse_message(
        method: &str,
        params: &impl serde::Serialize,
    ) -> Option<Result<Self, sacp::Error>> {
        if method != "optional_params_method" {
            return None;
        }
        Some(sacp::util::json_cast(params))
    }
}

impl JrRequest for OptionalParamsRequest {
    type Response = SimpleResponse;
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct SimpleResponse {
    result: String,
}

impl JrResponsePayload for SimpleResponse {
    fn into_json(self, _method: &str) -> Result<serde_json::Value, sacp::Error> {
        serde_json::to_value(self).map_err(sacp::Error::into_internal_error)
    }

    fn from_value(_method: &str, value: serde_json::Value) -> Result<Self, sacp::Error> {
        sacp::util::json_cast(&value)
    }
}

// ============================================================================
// Test 1: Empty request (no parameters)
// ============================================================================

#[tokio::test(flavor = "current_thread")]
async fn test_empty_request() {
    use tokio::task::LocalSet;

    let local = LocalSet::new();

    local
        .run_until(async {
            let (server_reader, server_writer, client_reader, client_writer) = setup_test_streams();

            let server_transport = sacp::ByteStreams::new(server_writer, server_reader);
            let server = UntypedLink::builder().on_receive_request(
                async |_request: EmptyRequest,
                       request_cx: JrRequestCx<SimpleResponse>,
                       _connection_cx: JrConnectionCx<UntypedLink>| {
                    request_cx.respond(SimpleResponse {
                        result: "Got empty request".to_string(),
                    })
                },
                sacp::on_receive_request!(),
            );

            let client_transport = sacp::ByteStreams::new(client_writer, client_reader);
            let client = UntypedLink::builder();

            tokio::task::spawn_local(async move {
                server.serve(server_transport).await.ok();
            });

            let result = client
                .run_until(client_transport, async |cx| -> Result<(), sacp::Error> {
                    let request = EmptyRequest;

                    let result: Result<SimpleResponse, _> = recv(cx.send_request(request)).await;

                    // Should succeed
                    assert!(result.is_ok());
                    if let Ok(response) = result {
                        assert_eq!(response.result, "Got empty request");
                    }
                    Ok(())
                })
                .await;

            assert!(result.is_ok(), "Test failed: {:?}", result);
        })
        .await;
}

// ============================================================================
// Test 2: Null parameters
// ============================================================================

#[tokio::test(flavor = "current_thread")]
async fn test_null_params() {
    use tokio::task::LocalSet;

    let local = LocalSet::new();

    local
        .run_until(async {
            let (server_reader, server_writer, client_reader, client_writer) = setup_test_streams();

            let server_transport = sacp::ByteStreams::new(server_writer, server_reader);
            let server = UntypedLink::builder().on_receive_request(
                async |_request: OptionalParamsRequest,
                       request_cx: JrRequestCx<SimpleResponse>,
                       _connection_cx: JrConnectionCx<UntypedLink>| {
                    request_cx.respond(SimpleResponse {
                        result: "Has params: true".to_string(),
                    })
                },
                sacp::on_receive_request!(),
            );

            let client_transport = sacp::ByteStreams::new(client_writer, client_reader);
            let client = UntypedLink::builder();

            tokio::task::spawn_local(async move {
                server.serve(server_transport).await.ok();
            });

            let result = client
                .run_until(client_transport, async |cx| -> Result<(), sacp::Error> {
                    let request = OptionalParamsRequest { value: None };

                    let result: Result<SimpleResponse, _> = recv(cx.send_request(request)).await;

                    // Should succeed - handler should handle null/missing params
                    assert!(result.is_ok());
                    Ok(())
                })
                .await;

            assert!(result.is_ok(), "Test failed: {:?}", result);
        })
        .await;
}

// ============================================================================
// Test 3: Server shutdown with pending requests
// ============================================================================

#[tokio::test(flavor = "current_thread")]
async fn test_server_shutdown() {
    use tokio::task::LocalSet;

    let local = LocalSet::new();

    local
        .run_until(async {
            let (server_reader, server_writer, client_reader, client_writer) = setup_test_streams();

            let server_transport = sacp::ByteStreams::new(server_writer, server_reader);
            let server = UntypedLink::builder().on_receive_request(
                async |_request: EmptyRequest,
                       request_cx: JrRequestCx<SimpleResponse>,
                       _connection_cx: JrConnectionCx<UntypedLink>| {
                    request_cx.respond(SimpleResponse {
                        result: "Got empty request".to_string(),
                    })
                },
                sacp::on_receive_request!(),
            );

            let client_transport = sacp::ByteStreams::new(client_writer, client_reader);
            let client = UntypedLink::builder();

            let server_handle = tokio::task::spawn_local(async move {
                server.serve(server_transport).await.ok();
            });

            let client_result = tokio::task::spawn_local(async move {
                client
                    .run_until(client_transport, async |cx| -> Result<(), sacp::Error> {
                        let request = EmptyRequest;

                        // Send request and get future for response
                        let response_future = recv(cx.send_request(request));

                        // Give the request time to be sent over the wire
                        tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;

                        // Try to get response (server should still be running briefly)
                        let _result: Result<SimpleResponse, _> = response_future.await;

                        // Could succeed or fail depending on timing
                        // The important thing is that it doesn't hang
                        Ok(())
                    })
                    .await
            });

            // Let the client send its request
            tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;

            // Abort the server
            server_handle.abort();

            // Wait for client to finish
            let result = client_result.await;
            assert!(result.is_ok(), "Test failed: {:?}", result);
        })
        .await;
}

// ============================================================================
// Test 4: Client disconnect mid-request
// ============================================================================

#[tokio::test(flavor = "current_thread")]
async fn test_client_disconnect() {
    use tokio::io::AsyncWriteExt;
    use tokio::task::LocalSet;

    let local = LocalSet::new();

    local
        .run_until(async {
            let (mut client_writer, server_reader) = tokio::io::duplex(1024);
            let (server_writer, _client_reader) = tokio::io::duplex(1024);

            let server_reader = server_reader.compat();
            let server_writer = server_writer.compat_write();

            let server_transport = sacp::ByteStreams::new(server_writer, server_reader);
            let server = UntypedLink::builder().on_receive_request(
                async |_request: EmptyRequest,
                       request_cx: JrRequestCx<SimpleResponse>,
                       _connection_cx: JrConnectionCx<UntypedLink>| {
                    request_cx.respond(SimpleResponse {
                        result: "Got empty request".to_string(),
                    })
                },
                sacp::on_receive_request!(),
            );

            tokio::task::spawn_local(async move {
                let _ = server.serve(server_transport).await;
            });

            // Send partial request and then disconnect
            let partial_request = b"{\"jsonrpc\":\"2.0\",\"method\":\"empty_method\",\"id\":1";
            client_writer.write_all(partial_request).await.unwrap();
            client_writer.flush().await.unwrap();

            // Drop the writer to disconnect
            drop(client_writer);

            // Give server time to process the disconnect
            tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

            // Server should handle this gracefully and terminate
            // (We can't really assert much here except that the test completes)
        })
        .await;
}
