use crate::{db::ChannelId, tests::TestServer};
use call::ActiveCall;
use editor::Editor;
use gpui::{BackgroundExecutor, TestAppContext};
use rpc::proto;

#[gpui::test]
async fn test_channel_guests(
    executor: BackgroundExecutor,
    cx_a: &mut TestAppContext,
    cx_b: &mut TestAppContext,
) {
    let mut server = TestServer::start(executor.clone()).await;
    let client_a = server.create_client(cx_a, "user_a").await;
    let client_b = server.create_client(cx_b, "user_b").await;
    let active_call_a = cx_a.read(ActiveCall::global);

    let channel_id = server
        .make_public_channel("the-channel", &client_a, cx_a)
        .await;

    // Client A shares a project in the channel
    let project_a = client_a.build_test_project(cx_a).await;
    active_call_a
        .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
        .await
        .unwrap();
    let project_id = active_call_a
        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
        .await
        .unwrap();
    cx_a.executor().run_until_parked();

    // Client B joins channel A as a guest
    cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
        .await
        .unwrap();

    // b should be following a in the shared project.
    // B is a guest,
    executor.run_until_parked();

    let active_call_b = cx_b.read(ActiveCall::global);
    let project_b =
        active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap());
    let room_b = active_call_b.update(cx_b, |call, _| call.room().unwrap().clone());

    assert_eq!(
        project_b.read_with(cx_b, |project, _| project.remote_id()),
        Some(project_id),
    );
    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
    assert!(project_b
        .update(cx_b, |project, cx| {
            let worktree_id = project.worktrees().next().unwrap().read(cx).id();
            project.create_entry((worktree_id, "b.txt"), false, cx)
        })
        .await
        .is_err());
    assert!(room_b.read_with(cx_b, |room, _| room.is_muted()));
}

#[gpui::test]
async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
    let mut server = TestServer::start(cx_a.executor()).await;
    let client_a = server.create_client(cx_a, "user_a").await;
    let client_b = server.create_client(cx_b, "user_b").await;
    let active_call_a = cx_a.read(ActiveCall::global);

    let channel_id = server
        .make_public_channel("the-channel", &client_a, cx_a)
        .await;

    let project_a = client_a.build_test_project(cx_a).await;
    cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx))
        .await
        .unwrap();

    // Client A shares a project in the channel
    active_call_a
        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
        .await
        .unwrap();
    cx_a.run_until_parked();

    // Client B joins channel A as a guest
    cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
        .await
        .unwrap();
    cx_a.run_until_parked();

    // client B opens 1.txt as a guest
    let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
    let room_b = cx_b
        .read(ActiveCall::global)
        .update(cx_b, |call, _| call.room().unwrap().clone());
    cx_b.simulate_keystrokes("cmd-p 1 enter");

    let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| {
        (
            workspace.project().clone(),
            workspace.active_item_as::<Editor>(cx).unwrap(),
        )
    });
    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
    assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
    assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
    assert!(room_b
        .update(cx_b, |room, cx| room.share_microphone(cx))
        .await
        .is_err());

    // B is promoted
    active_call_a
        .update(cx_a, |call, cx| {
            call.room().unwrap().update(cx, |room, cx| {
                room.set_participant_role(
                    client_b.user_id().unwrap(),
                    proto::ChannelRole::Member,
                    cx,
                )
            })
        })
        .await
        .unwrap();
    cx_a.run_until_parked();

    // project and buffers are now editable
    assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only()));
    assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));

    // B sees themselves as muted, and can unmute.
    assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
    room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));
    room_b.update(cx_b, |room, cx| room.toggle_mute(cx));
    cx_a.run_until_parked();
    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));

    // B is demoted
    active_call_a
        .update(cx_a, |call, cx| {
            call.room().unwrap().update(cx, |room, cx| {
                room.set_participant_role(
                    client_b.user_id().unwrap(),
                    proto::ChannelRole::Guest,
                    cx,
                )
            })
        })
        .await
        .unwrap();
    cx_a.run_until_parked();

    // project and buffers are no longer editable
    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
    assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx)));
    assert!(room_b
        .update(cx_b, |room, cx| room.share_microphone(cx))
        .await
        .is_err());
}

#[gpui::test]
async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
    let mut server = TestServer::start(cx_a.executor()).await;

    server
        .app_state
        .db
        .get_or_create_user_by_github_account("user_b", Some(100), None, None)
        .await
        .unwrap();

    let client_a = server.create_client(cx_a, "user_a").await;
    let client_b = server.create_client(cx_b, "user_b").await;
    let active_call_a = cx_a.read(ActiveCall::global);
    let active_call_b = cx_b.read(ActiveCall::global);

    // Create a parent channel that requires the Zed CLA
    let parent_channel_id = server
        .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
        .await;
    server
        .app_state
        .db
        .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true)
        .await
        .unwrap();

    // Create a public channel that is a child of the parent channel.
    let channel_id = client_a
        .channel_store()
        .update(cx_a, |store, cx| {
            store.create_channel("the-sub-channel", Some(parent_channel_id), cx)
        })
        .await
        .unwrap();
    client_a
        .channel_store()
        .update(cx_a, |store, cx| {
            store.set_channel_visibility(parent_channel_id, proto::ChannelVisibility::Public, cx)
        })
        .await
        .unwrap();
    client_a
        .channel_store()
        .update(cx_a, |store, cx| {
            store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx)
        })
        .await
        .unwrap();

    // Users A and B join the channel. B is a guest.
    active_call_a
        .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
        .await
        .unwrap();
    active_call_b
        .update(cx_b, |call, cx| call.join_channel(channel_id, cx))
        .await
        .unwrap();
    cx_a.run_until_parked();
    let room_b = cx_b
        .read(ActiveCall::global)
        .update(cx_b, |call, _| call.room().unwrap().clone());
    assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));

    // A tries to grant write access to B, but cannot because B has not
    // yet signed the zed CLA.
    active_call_a
        .update(cx_a, |call, cx| {
            call.room().unwrap().update(cx, |room, cx| {
                room.set_participant_role(
                    client_b.user_id().unwrap(),
                    proto::ChannelRole::Member,
                    cx,
                )
            })
        })
        .await
        .unwrap_err();
    cx_a.run_until_parked();
    assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
    assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));

    // A tries to grant write access to B, but cannot because B has not
    // yet signed the zed CLA.
    active_call_a
        .update(cx_a, |call, cx| {
            call.room().unwrap().update(cx, |room, cx| {
                room.set_participant_role(
                    client_b.user_id().unwrap(),
                    proto::ChannelRole::Talker,
                    cx,
                )
            })
        })
        .await
        .unwrap();
    cx_a.run_until_parked();
    assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects()));
    assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));

    // User B signs the zed CLA.
    server
        .app_state
        .db
        .add_contributor("user_b", Some(100), None, None)
        .await
        .unwrap();

    // A can now grant write access to B.
    active_call_a
        .update(cx_a, |call, cx| {
            call.room().unwrap().update(cx, |room, cx| {
                room.set_participant_role(
                    client_b.user_id().unwrap(),
                    proto::ChannelRole::Member,
                    cx,
                )
            })
        })
        .await
        .unwrap();
    cx_a.run_until_parked();
    assert!(room_b.read_with(cx_b, |room, _| room.can_share_projects()));
    assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
}
