// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/video_conference/video_conference_ash_feature_client.h"

#include <cstdlib>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
#include "ash/system/video_conference/video_conference_common.h"
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/unguessable_token.h"
#include "chrome/browser/ash/borealis/borealis_prefs.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/video_conference/video_conference_manager_ash.h"
#include "chrome/browser/chromeos/video_conference/video_conference_manager_client_common.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace ash {

constexpr char kCrostiniVmId[] = "Linux";
constexpr char kBorealisId[] = "Borealis";

VideoConferenceMediaAppInfo MakeMediaAppInfo(const base::UnguessableToken& id,
                                             base::Time last_activity_time,
                                             bool is_capturing_camera,
                                             bool is_capturing_microphone,
                                             const std::u16string& title,
                                             VideoConferenceAppType app_type) {
  VideoConferenceMediaAppInfo app;
  app.id = id;
  app.last_activity_time = last_activity_time;
  app.is_capturing_camera = is_capturing_camera;
  app.is_capturing_microphone = is_capturing_microphone;
  app.title = title;
  app.app_type = app_type;
  return app;
}

class VideoConferenceAshfeatureClientTest : public InProcessBrowserTest {
 public:
  void SetUp() override {
    scoped_feature_list_.InitAndEnableFeature(
        ash::features::kFeatureManagementVideoConference);

    InProcessBrowserTest::SetUp();
  }

  // Update the permission of current `app_id`.
  void UpdateAppPermision(const std::string& app_id,
                          bool has_microphone_permission) {
    auto* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
    if (app_id == kCrostiniVmId) {
      prefs->SetBoolean(crostini::prefs::kCrostiniMicAllowed,
                        has_microphone_permission);
    }
    if (app_id == kBorealisId) {
      prefs->SetBoolean(borealis::prefs::kBorealisMicAllowed,
                        has_microphone_permission);
    }
  }

  VideoConferenceManagerClient::MediaApps GetMediaApps() {
    return VideoConferenceAshFeatureClient::Get()->GetMediaApps();
  }

  // Returns current VideoConferenceMediaState in the VideoConferenceManagerAsh
  VideoConferenceMediaState GetMediaStateInVideoConferenceManagerAsh() {
    return VideoConferenceManagerAsh::Get()->GetAggregatedState();
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(VideoConferenceAshfeatureClientTest, GetMediaApps) {
  {
    // Notifying Crostini is using Mic.
    VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
        VmCameraMicManager::VmType::kCrostiniVm,
        VmCameraMicManager::DeviceType::kMic, true);

    // GetMediaApps should return kCrostinId.
    auto media_app_info = GetMediaApps();
    ASSERT_EQ(media_app_info.size(), 1u);
    const auto& info0 = media_app_info[0];

    const auto expected_media_app_info = MakeMediaAppInfo(
        /*id=*/info0.id,
        /*last_activity_time=*/info0.last_activity_time,
        /*is_capturing_camera=*/false,
        /*is_capturing_microphone=*/true,
        /*title=*/base::UTF8ToUTF16(std::string(kCrostiniVmId)),
        /*app_type=*/VideoConferenceAppType::kCrostiniVm);

    EXPECT_EQ(media_app_info[0], expected_media_app_info);

    // Stop accessing should remove the app.
    VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
        VmCameraMicManager::VmType::kCrostiniVm,
        VmCameraMicManager::DeviceType::kMic, false);

    EXPECT_TRUE(GetMediaApps().empty());
  }

  {
    // Notifying Borealis is using the microphone.
    VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
        VmCameraMicManager::VmType::kBorealis,
        VmCameraMicManager::DeviceType::kMic, true);

    // GetMediaApps should return Borealis.
    auto media_app_info = GetMediaApps();
    ASSERT_EQ(media_app_info.size(), 1u);
    const auto& info0 = media_app_info[0];

    const auto expected_media_app_info = MakeMediaAppInfo(
        /*id=*/info0.id,
        /*last_activity_time=*/info0.last_activity_time,
        /*is_capturing_camera=*/false,
        /*is_capturing_microphone=*/true,
        /*title=*/base::UTF8ToUTF16(std::string(kBorealisId)),
        /*app_type=*/VideoConferenceAppType::kBorealis);

    EXPECT_EQ(media_app_info[0], expected_media_app_info);

    VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
        VmCameraMicManager::VmType::kBorealis,
        VmCameraMicManager::DeviceType::kMic, false);
  }
}

IN_PROC_BROWSER_TEST_F(VideoConferenceAshfeatureClientTest,
                       HandleMediaUsageUpdate) {
  // Set initial permissions.
  UpdateAppPermision(kCrostiniVmId, /*has_microphone_permission=*/true);
  UpdateAppPermision(kBorealisId, /*has_microphone_permission=*/true);

  // All state should be false initially.
  VideoConferenceMediaState state = GetMediaStateInVideoConferenceManagerAsh();
  EXPECT_FALSE(state.has_media_app);
  EXPECT_FALSE(state.has_camera_permission);
  EXPECT_FALSE(state.has_microphone_permission);
  EXPECT_FALSE(state.is_capturing_camera);
  EXPECT_FALSE(state.is_capturing_microphone);
  EXPECT_FALSE(state.is_capturing_screen);

  // Notifying Crostini is using Mic.
  VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
      VmCameraMicManager::VmType::kCrostiniVm,
      VmCameraMicManager::DeviceType::kMic, true);

  // State should have update for microphone from CrostiniVm.
  state = GetMediaStateInVideoConferenceManagerAsh();
  EXPECT_TRUE(state.has_media_app);
  EXPECT_TRUE(state.has_microphone_permission);
  EXPECT_TRUE(state.is_capturing_microphone);
  EXPECT_FALSE(state.has_camera_permission);
  EXPECT_FALSE(state.is_capturing_camera);

  // Borealis starts using the microphone.
  VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
      VmCameraMicManager::VmType::kBorealis,
      VmCameraMicManager::DeviceType::kMic, true);

  state = GetMediaStateInVideoConferenceManagerAsh();
  EXPECT_TRUE(state.has_microphone_permission);
  EXPECT_TRUE(state.is_capturing_microphone);
  EXPECT_FALSE(state.is_capturing_camera);

  // Crostini stops while Borealis is still using the microphone.
  VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
      VmCameraMicManager::VmType::kCrostiniVm,
      VmCameraMicManager::DeviceType::kMic, false);
  state = GetMediaStateInVideoConferenceManagerAsh();
  EXPECT_TRUE(state.has_microphone_permission);
  EXPECT_TRUE(state.is_capturing_microphone);

  // Borealis stops using the microphone.
  VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
      VmCameraMicManager::VmType::kBorealis,
      VmCameraMicManager::DeviceType::kMic, false);

  state = GetMediaStateInVideoConferenceManagerAsh();
  EXPECT_FALSE(state.has_media_app);
  EXPECT_FALSE(state.has_camera_permission);
  EXPECT_FALSE(state.has_microphone_permission);
  EXPECT_FALSE(state.is_capturing_camera);
  EXPECT_FALSE(state.is_capturing_microphone);
  EXPECT_FALSE(state.is_capturing_screen);
}

IN_PROC_BROWSER_TEST_F(VideoConferenceAshfeatureClientTest,
                       HandleDeviceUsedWhileDisabled) {
  // Notify disabling state of camera and microphone from
  // video_conference_manager_ash.
  VideoConferenceManagerAsh::Get()->SetSystemMediaDeviceStatus(
      VideoConferenceMediaDevice::kCamera,
      /*enabled=*/false);
  VideoConferenceManagerAsh::Get()->SetSystemMediaDeviceStatus(
      VideoConferenceMediaDevice::kMicrophone,
      /*enabled=*/false);

  FakeVideoConferenceTrayController* fake_try_controller =
      static_cast<FakeVideoConferenceTrayController*>(
          VideoConferenceTrayController::Get());

  // Notifying Crostini is using Mic.
  VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
      VmCameraMicManager::VmType::kCrostiniVm,
      VmCameraMicManager::DeviceType::kMic, true);

  // One UsedWhileDisabled call should be sent to
  // FakeVideoConferenceTrayController.
  ASSERT_EQ(fake_try_controller->device_used_while_disabled_records().size(),
            1u);
  EXPECT_THAT(fake_try_controller->device_used_while_disabled_records().back(),
              testing::Pair(VideoConferenceMediaDevice::kMicrophone,
                            base::UTF8ToUTF16(std::string(kCrostiniVmId))));

}

}  // namespace ash
