// Copyright 2022 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_manager_ash.h"

#include <memory>
#include <utility>
#include <vector>

#include "ash/system/video_conference/video_conference_common.h"
#include "base/memory/raw_ref.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

VideoConferenceMediaAppInfo MakeMediaAppInfo(const base::UnguessableToken& id,
                                             base::Time last_activity_time,
                                             bool is_capturing_camera,
                                             bool is_capturing_microphone,
                                             bool is_capturing_screen,
                                             const std::u16string& title) {
  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.is_capturing_screen = is_capturing_screen;
  app.title = title;
  return app;
}

}  // namespace

class FakeVideoConferenceManagerAsh : public VideoConferenceManagerAsh {
 public:
  VideoConferenceMediaState& state() { return state_; }

 protected:
  void SendUpdatedState() override { state_ = GetAggregatedState(); }

 private:
  VideoConferenceMediaState state_;
};

class FakeVcManagerCppClient : public VideoConferenceManagerClient {
 public:
  explicit FakeVcManagerCppClient(FakeVideoConferenceManagerAsh& vc_manager)
      : id_(base::UnguessableToken::Create()), vc_manager_(vc_manager) {}
  FakeVcManagerCppClient(const FakeVcManagerCppClient&) = delete;
  FakeVcManagerCppClient& operator=(const FakeVcManagerCppClient&) = delete;
  ~FakeVcManagerCppClient() override { vc_manager_->UnregisterClient(id_); }

  // VideoConferenceManagerClient overrides
  MediaApps GetMediaApps() override { return apps_; }

  bool ReturnToApp(const base::UnguessableToken& id) override { NOTREACHED(); }

  bool SetSystemMediaDeviceStatus(VideoConferenceMediaDevice device,
                                  bool enabled) override {
    NOTREACHED();
  }

  // Public for testing.
  base::UnguessableToken id_;
  MediaApps apps_;
  const raw_ref<FakeVideoConferenceManagerAsh> vc_manager_;
};

class VideoConferenceManagerAshTest : public testing::Test {
 public:
  struct CaptureStatus {
    bool is_capturing_camera = false;
    bool is_capturing_microphone = false;
    bool is_capturing_screen = false;
  };

  FakeVideoConferenceManagerAsh& vc_manager() { return vc_manager_; }

  // Aggregates capturing statuses from all media apps into a |CaptureStatus|
  // and returns it.
  CaptureStatus GetAggregatedCaptureStatus(
      VideoConferenceManagerAsh::MediaApps apps) {
    bool is_capturing_camera = false;
    bool is_capturing_microphone = false;
    bool is_capturing_screen = false;

    for (auto& app : apps) {
      is_capturing_camera |= app.is_capturing_camera;
      is_capturing_microphone |= app.is_capturing_microphone;
      is_capturing_screen |= app.is_capturing_screen;
    }

    return {is_capturing_camera, is_capturing_microphone, is_capturing_screen};
  }

  // Public because this is test code.
  base::test::TaskEnvironment task_environment_;

 private:
  FakeVideoConferenceManagerAsh vc_manager_;
};

// Tests |VideoConferenceManagerAsh::GetMediaApps| returns correct aggregated
// results from all VcClients.
TEST_F(VideoConferenceManagerAshTest, VcManagerGetMediaApps) {
  const auto now = base::Time::Now();
  const auto duration = base::Seconds(1);

  std::unique_ptr<FakeVcManagerCppClient> client1 =
      std::make_unique<FakeVcManagerCppClient>(vc_manager());
  client1->apps_.push_back(MakeMediaAppInfo(
      /*id=*/base::UnguessableToken::Create(),
      /*last_activity_time=*/now,
      /*is_capturing_camera=*/false, /*is_capturing_microphone=*/false,
      /*is_capturing_screen=*/true, /*title=*/u"Test App0"));

  vc_manager().RegisterCppClient(client1.get(), client1->id_);

  // Basic functioning.
  base::RunLoop run_loop1;
  vc_manager().GetMediaApps(base::BindLambdaForTesting(
      [&](VideoConferenceManagerAsh::MediaApps apps) {
        EXPECT_EQ(apps.size(), 1u);
        EXPECT_EQ(apps[0].title, u"Test App0");

        auto status = GetAggregatedCaptureStatus(std::move(apps));

        EXPECT_FALSE(status.is_capturing_camera);
        EXPECT_FALSE(status.is_capturing_microphone);
        EXPECT_TRUE(status.is_capturing_screen);
        run_loop1.Quit();
      }));
  run_loop1.Run();

  client1->apps_.push_back(MakeMediaAppInfo(
      /*id=*/base::UnguessableToken::Create(),
      /*last_activity_time=*/now + duration * 10,
      /*is_capturing_camera=*/true, /*is_capturing_microphone=*/false,
      /*is_capturing_screen=*/true, /*title=*/u"Test App1"));

  base::RunLoop run_loop2;
  vc_manager().GetMediaApps(base::BindLambdaForTesting(
      [&](VideoConferenceManagerAsh::MediaApps apps) {
        EXPECT_EQ(apps.size(), 2UL);
        EXPECT_EQ(apps[0].title, u"Test App1");
        EXPECT_EQ(apps[1].title, u"Test App0");

        auto status = GetAggregatedCaptureStatus(std::move(apps));

        EXPECT_TRUE(status.is_capturing_camera);
        EXPECT_FALSE(status.is_capturing_microphone);
        EXPECT_TRUE(status.is_capturing_screen);
        run_loop2.Quit();
      }));
  run_loop2.Run();

  // Multiple clients.
  {
    std::unique_ptr<FakeVcManagerCppClient> client2 =
        std::make_unique<FakeVcManagerCppClient>(vc_manager());
    client2->apps_.push_back(MakeMediaAppInfo(
        /*id=*/base::UnguessableToken::Create(),
        /*last_activity_time=*/now + duration * 2,
        /*is_capturing_camera=*/false, /*is_capturing_microphone=*/true,
        /*is_capturing_screen=*/false, /*title=*/u"Test App2"));

    vc_manager().RegisterCppClient(client2.get(), client2->id_);

    base::RunLoop run_loop3;
    vc_manager().GetMediaApps(base::BindLambdaForTesting(
        [&](VideoConferenceManagerAsh::MediaApps apps) {
          EXPECT_EQ(apps.size(), 3UL);
          EXPECT_EQ(apps[0].title, u"Test App1");
          EXPECT_EQ(apps[1].title, u"Test App2");
          EXPECT_EQ(apps[2].title, u"Test App0");

          auto status = GetAggregatedCaptureStatus(std::move(apps));

          EXPECT_TRUE(status.is_capturing_camera);
          EXPECT_TRUE(status.is_capturing_microphone);
          EXPECT_TRUE(status.is_capturing_screen);
          run_loop3.Quit();
        }));
    run_loop3.Run();
  }

  client1->apps_.pop_back();

  // Expect disconnecting clients and modifying internal client state to work
  // correctly.
  base::RunLoop run_loop4;
  vc_manager().GetMediaApps(base::BindLambdaForTesting(
      [&](VideoConferenceManagerAsh::MediaApps apps) {
        EXPECT_EQ(apps.size(), 1u);

        auto status = GetAggregatedCaptureStatus(std::move(apps));

        EXPECT_FALSE(status.is_capturing_camera);
        EXPECT_FALSE(status.is_capturing_microphone);
        EXPECT_TRUE(status.is_capturing_screen);
        run_loop4.Quit();
      }));
  run_loop4.Run();
}

// Tests VcManager state correctly updates after |NotifyMediaUsageUpdate| calls.
TEST_F(VideoConferenceManagerAshTest, VcManagerNotifyMediaUsageUpdate) {
  std::unique_ptr<FakeVcManagerCppClient> client1 =
      std::make_unique<FakeVcManagerCppClient>(vc_manager());
  vc_manager().RegisterCppClient(client1.get(), client1->id_);

  // Expect initial values are all false.
  EXPECT_FALSE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_FALSE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_FALSE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);

  // Basic functioning.

  base::RunLoop run_loop1;
  VideoConferenceMediaUsageStatus status1(client1->id_);
  status1.state.has_media_app = true;
  status1.state.has_microphone_permission = true;
  status1.state.is_capturing_microphone = true;

  vc_manager().NotifyMediaUsageUpdate(
      std::move(status1), base::BindLambdaForTesting([&](bool success) {
        EXPECT_TRUE(success);
        run_loop1.Quit();
      }));
  run_loop1.Run();

  EXPECT_TRUE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_TRUE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_TRUE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);

  // Multiple clients.
  {
    std::unique_ptr<FakeVcManagerCppClient> client2 =
        std::make_unique<FakeVcManagerCppClient>(vc_manager());
    vc_manager().RegisterCppClient(client2.get(), client2->id_);

    base::RunLoop run_loop2;
    VideoConferenceMediaUsageStatus status2(client2->id_);
    status2.state.has_media_app = true;
    status2.state.has_camera_permission = true;
    status2.state.has_microphone_permission = true;
    status2.state.is_capturing_camera = true;
    status2.state.is_capturing_microphone = true;
    status2.state.is_capturing_screen = true;

    vc_manager().NotifyMediaUsageUpdate(
        std::move(status2), base::BindLambdaForTesting([&](bool success) {
          EXPECT_TRUE(success);
          run_loop2.Quit();
        }));
    run_loop2.Run();

    EXPECT_TRUE(vc_manager().state().has_media_app);
    EXPECT_TRUE(vc_manager().state().has_camera_permission);
    EXPECT_TRUE(vc_manager().state().has_microphone_permission);
    EXPECT_TRUE(vc_manager().state().is_capturing_camera);
    EXPECT_TRUE(vc_manager().state().is_capturing_microphone);
    EXPECT_TRUE(vc_manager().state().is_capturing_screen);
  }

  // Expect status is updated after a client disconnects.
  EXPECT_TRUE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_TRUE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_TRUE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);

  // Expect previously true fields are correctly reset on later
  // |NotifyMediaUsageUpdate| calls.
  base::RunLoop run_loop3;
  VideoConferenceMediaUsageStatus status3(client1->id_);
  status3.state.has_media_app = true;
  status3.state.has_microphone_permission = true;

  vc_manager().NotifyMediaUsageUpdate(
      std::move(status3), base::BindLambdaForTesting([&](bool success) {
        EXPECT_TRUE(success);
        run_loop3.Quit();
      }));
  run_loop3.Run();

  EXPECT_TRUE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_TRUE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_FALSE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);
}

}  // namespace ash
