use super::*;
use rmux_proto::{
    encode_frame, FrameDecoder, RenameWindowResponse, ResizeWindowResponse, SelectLayoutResponse,
    SelectWindowResponse, WindowTarget,
};
use tokio::io::{AsyncReadExt, AsyncWriteExt};

fn alpha() -> rmux_proto::SessionName {
    rmux_proto::SessionName::new("alpha").expect("valid session")
}

fn target() -> WindowRef {
    WindowRef::new(alpha(), 2)
}

fn window(client: TransportClient) -> Window {
    Window::new(target(), RmuxEndpoint::Default, None, client)
}

async fn read_request(stream: &mut tokio::io::DuplexStream) -> Request {
    let mut decoder = FrameDecoder::new();
    let mut buffer = [0_u8; 256];
    loop {
        if let Some(request) = decoder
            .next_frame::<Request>()
            .expect("request frame decodes")
        {
            return request;
        }
        let read = stream.read(&mut buffer).await.expect("read request");
        assert_ne!(read, 0, "client closed before request");
        decoder.push_bytes(&buffer[..read]);
    }
}

async fn write_response(stream: &mut tokio::io::DuplexStream, response: Response) {
    let frame = encode_frame(&response).expect("response encodes");
    stream.write_all(&frame).await.expect("write response");
    stream.flush().await.expect("flush response");
}

#[tokio::test]
async fn window_mutation_methods_send_typed_requests() {
    let (client_stream, mut server_stream) = tokio::io::duplex(4096);
    let window = window(TransportClient::spawn(client_stream));
    let proto_target = WindowTarget::with_window(alpha(), 2);

    let rename = tokio::spawn({
        let window = window.clone();
        async move { window.rename("logs").await }
    });
    match read_request(&mut server_stream).await {
        Request::RenameWindow(request) => {
            assert_eq!(request.target, proto_target);
            assert_eq!(request.name, "logs");
        }
        request => panic!("expected rename-window, got {request:?}"),
    }
    write_response(
        &mut server_stream,
        Response::RenameWindow(RenameWindowResponse {
            target: proto_target.clone(),
        }),
    )
    .await;
    rename.await.expect("rename task").expect("rename succeeds");

    let select = tokio::spawn({
        let window = window.clone();
        async move { window.select().await }
    });
    assert_eq!(
        read_request(&mut server_stream).await,
        Request::SelectWindow(SelectWindowRequest {
            target: proto_target.clone()
        })
    );
    write_response(
        &mut server_stream,
        Response::SelectWindow(SelectWindowResponse {
            target: proto_target.clone(),
        }),
    )
    .await;
    select.await.expect("select task").expect("select succeeds");

    let resize = tokio::spawn({
        let window = window.clone();
        async move { window.resize(Some(120), Some(40)).await }
    });
    assert_eq!(
        read_request(&mut server_stream).await,
        Request::ResizeWindow(ResizeWindowRequest {
            target: proto_target.clone(),
            width: Some(120),
            height: Some(40),
            adjustment: None,
        })
    );
    write_response(
        &mut server_stream,
        Response::ResizeWindow(ResizeWindowResponse {
            target: proto_target.clone(),
        }),
    )
    .await;
    resize.await.expect("resize task").expect("resize succeeds");

    let layout = tokio::spawn({
        let window = window.clone();
        async move { window.select_layout(LayoutName::Tiled).await }
    });
    assert_eq!(
        read_request(&mut server_stream).await,
        Request::SelectLayout(SelectLayoutRequest {
            target: SelectLayoutTarget::Window(proto_target.clone()),
            layout: LayoutName::Tiled,
        })
    );
    write_response(
        &mut server_stream,
        Response::SelectLayout(SelectLayoutResponse {
            layout: LayoutName::Tiled,
        }),
    )
    .await;
    layout.await.expect("layout task").expect("layout succeeds");
}
