// 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 "device/bluetooth/floss/floss_battery_manager_client.h"

#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_exported_object.h"
#include "dbus/mock_object_proxy.h"
#include "device/bluetooth/floss/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
using testing::DoAll;

void FakeExportMethod(
    const std::string& interface_name,
    const std::string& method_name,
    const dbus::ExportedObject::MethodCallCallback& method_call_callback,
    dbus::ExportedObject::OnExportedCallback on_exported_callback) {
  std::move(on_exported_callback)
      .Run(interface_name, method_name, /*success=*/true);
}
}  // namespace

namespace floss {

const uint32_t kTestCallbackId = 42;

class FlossBatteryManagerClientTest : public testing::Test,
                                      public FlossBatteryManagerClientObserver {
 public:
  base::Version GetCurrVersion() {
    return floss::version::GetMaximalSupportedVersion();
  }

  void SetUpMocks() {
    battery_manager_path_ =
        FlossDBusClient::GenerateBatteryManagerPath(adapter_index_);
    battery_manager_object_proxy_ =
        base::MakeRefCounted<::dbus::MockObjectProxy>(
            bus_.get(), kBatteryManagerInterface, battery_manager_path_);
    exported_callbacks_ = base::MakeRefCounted<::dbus::MockExportedObject>(
        bus_.get(),
        ::dbus::ObjectPath(FlossBatteryManagerClient::kExportedCallbacksPath));

    EXPECT_CALL(*bus_.get(),
                GetObjectProxy(kBatteryManagerInterface, battery_manager_path_))
        .WillRepeatedly(::testing::Return(battery_manager_object_proxy_.get()));
    EXPECT_CALL(*bus_.get(), GetExportedObject)
        .WillRepeatedly(::testing::Return(exported_callbacks_.get()));

    // Handle method calls on the object proxy
    ON_CALL(*battery_manager_object_proxy_.get(),
            CallMethodWithErrorResponse(
                HasMemberOf(battery_manager::kRegisterBatteryCallback),
                testing::_, testing::_))
        .WillByDefault(Invoke(
            this, &FlossBatteryManagerClientTest::HandleRegisterCallback));
    ON_CALL(*battery_manager_object_proxy_.get(),
            CallMethodWithErrorResponse(
                HasMemberOf(battery_manager::kGetBatteryInformation),
                testing::_, testing::_))
        .WillByDefault(Invoke(
            this, &FlossBatteryManagerClientTest::HandleGetBatteryInformation));
  }

  void SetUp() override {
    ::dbus::Bus::Options options;
    options.bus_type = ::dbus::Bus::BusType::SYSTEM;
    bus_ = base::MakeRefCounted<::dbus::MockBus>(std::move(options));
    callback_path_ =
        ::dbus::ObjectPath(FlossBatteryManagerClient::kExportedCallbacksPath);
    client_ = FlossBatteryManagerClient::Create();
    client_->AddObserver(this);
    SetUpMocks();
  }

  void TearDown() override { client_.reset(); }

  void HandleRegisterCallback(::dbus::MethodCall* method_call,
                              int timeout_ms,
                              ::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
    auto response = ::dbus::Response::CreateEmpty();
    ::dbus::MessageWriter msg(response.get());
    msg.AppendUint32(kTestCallbackId);

    std::move(cb).Run(response.get(), nullptr);
  }

  void HandleGetBatteryInformation(
      ::dbus::MethodCall* method_call,
      int timeout_ms,
      ::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
    auto response = ::dbus::Response::CreateEmpty();
    ::dbus::MessageWriter msg(response.get());

    std::move(cb).Run(response.get(), nullptr);
  }

  void HandleGetBatteryInfo(DBusResult<std::optional<BatterySet>> result) {
    callback_count_++;
  }

  void BatteryInfoUpdated(std::string remote_address,
                          BatterySet battery_set) override {
    callback_count_++;
  }

  // Test defined here to access private members.
  void TestInitializesCorrectly() {
    EXPECT_CALL(*exported_callbacks_.get(),
                ExportMethod(testing::_, testing::_, testing::_, testing::_))
        .WillRepeatedly(&FakeExportMethod);
    EXPECT_CALL(*battery_manager_object_proxy_.get(),
                CallMethodWithErrorResponse)
        .Times(testing::AnyNumber());
    // Expected specific method calls.
    EXPECT_CALL(*battery_manager_object_proxy_.get(),
                CallMethodWithErrorResponse(
                    HasMemberOf(battery_manager::kRegisterBatteryCallback),
                    testing::_, testing::_))
        .Times(1);
    client_->Init(bus_.get(), kBatteryManagerInterface, adapter_index_,
                  GetCurrVersion(), base::DoNothing());
    EXPECT_EQ(client_->battery_manager_callback_id_, kTestCallbackId);
    // Expected call to UnregisterCallback when client is destroyed
    EXPECT_CALL(*battery_manager_object_proxy_.get(),
                CallMethodWithErrorResponse(
                    HasMemberOf(battery_manager::kUnregisterBatteryCallback),
                    testing::_, testing::_))
        .WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
                     ::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
          dbus::MessageReader msg(method_call);
          // D-Bus method call should have 1 parameter.
          uint32_t param1;
          ASSERT_TRUE(FlossDBusClient::ReadAllDBusParams(&msg, &param1));
          EXPECT_EQ(kTestCallbackId, param1);
          EXPECT_FALSE(msg.HasMoreData());
        });
  }

  void TestForwardsCallbacks() {
    EXPECT_EQ(callback_count_, 0);
    client_->Init(bus_.get(), kBatteryManagerInterface, adapter_index_,
                  GetCurrVersion(), base::DoNothing());
    client_->BatteryInfoUpdated("11:11:11:11:11:11", BatterySet());
    EXPECT_EQ(callback_count_, 1);
  }

  void TestExportsDBusCorrectly() {
    // Get a reference to the callback handler
    dbus::ExportedObject::MethodCallCallback
        method_handler_on_battery_info_updated;
    EXPECT_CALL(*exported_callbacks_.get(),
                ExportMethod(battery_manager::kCallbackInterface,
                             battery_manager::kOnBatteryInfoUpdated, testing::_,
                             testing::_))
        .WillOnce(
            DoAll(testing::SaveArg<2>(&method_handler_on_battery_info_updated),
                  &FakeExportMethod));
    client_->Init(bus_.get(), kBatteryManagerInterface, adapter_index_,
                  GetCurrVersion(), base::DoNothing());
    ASSERT_TRUE(!!method_handler_on_battery_info_updated);

    // Set up DBus message
    dbus::MethodCall method_call("an.interface",
                                 battery_manager::kOnBatteryInfoUpdated);
    method_call.SetPath(callback_path_);
    method_call.SetSender(":0.1");
    method_call.SetSerial(1);
    dbus::MessageWriter writer(&method_call);
    BatterySet test_battery_set{};
    std::string test_battery_address = "11:11:11:11:11:11";
    FlossDBusClient::WriteAllDBusParams(&writer, test_battery_address,
                                        test_battery_set);

    // Invoke the callback and confirm that the DBus message is correctly parsed
    // and triggers callbacks.
    EXPECT_EQ(callback_count_, 0);
    method_handler_on_battery_info_updated.Run(
        &method_call,
        base::BindOnce([](std::unique_ptr<dbus::Response> response) {
          ASSERT_TRUE(!!response);
          EXPECT_NE(response->GetMessageType(),
                    dbus::Message::MessageType::MESSAGE_ERROR);
        }));
    EXPECT_EQ(callback_count_, 1);
  }

  void TestGetBatteryInfo() {
    EXPECT_CALL(*battery_manager_object_proxy_.get(),
                CallMethodWithErrorResponse)
        .Times(testing::AnyNumber());
    EXPECT_CALL(*battery_manager_object_proxy_.get(),
                CallMethodWithErrorResponse(
                    HasMemberOf(battery_manager::kGetBatteryInformation),
                    testing::_, testing::_))
        .Times(1);
    EXPECT_EQ(callback_count_, 0);
    client_->Init(bus_.get(), kBatteryManagerInterface, adapter_index_,
                  GetCurrVersion(), base::DoNothing());
    FlossDeviceId test_device{};
    test_device.address = "11:11:11:11:11:11";
    client_->GetBatteryInformation(
        base::BindOnce(&FlossBatteryManagerClientTest::HandleGetBatteryInfo,
                       weak_ptr_factory_.GetWeakPtr()),
        test_device);
    EXPECT_EQ(callback_count_, 1);
  }

  int callback_count_ = 0;
  int adapter_index_ = 5;
  dbus::ObjectPath callback_path_;
  dbus::ObjectPath battery_manager_path_;
  scoped_refptr<::dbus::MockBus> bus_;
  scoped_refptr<::dbus::MockExportedObject> exported_callbacks_;
  scoped_refptr<::dbus::MockObjectProxy> battery_manager_object_proxy_;
  std::unique_ptr<FlossBatteryManagerClient> client_;

  base::test::TaskEnvironment task_environment_;
  base::WeakPtrFactory<FlossBatteryManagerClientTest> weak_ptr_factory_{this};
};

TEST_F(FlossBatteryManagerClientTest, InitializesCorrectly) {
  TestInitializesCorrectly();
}

TEST_F(FlossBatteryManagerClientTest, ForwardsCallbacks) {
  TestForwardsCallbacks();
}

TEST_F(FlossBatteryManagerClientTest, ExportsDBusCorrectly) {
  TestExportsDBusCorrectly();
}

TEST_F(FlossBatteryManagerClientTest, GetBatteryInfo) {
  TestGetBatteryInfo();
}

}  // namespace floss
