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

#include "chromeos/ash/components/wifi_p2p/wifi_p2p_controller.h"

#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chromeos/ash/components/dbus/patchpanel/fake_patchpanel_client.h"
#include "chromeos/ash/components/dbus/shill/fake_shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_clients.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/wifi_p2p/wifi_p2p_group.h"
#include "chromeos/ash/components/wifi_p2p/wifi_p2p_metrics_logger.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {

namespace {

constexpr char kDefaultIpv4Address[] = "100.0.0.1";
constexpr char kDefaultSSID[] = "DIRECT-A0";
constexpr char kDefaultPassphrase[] = "direct-passphrase";
constexpr char kAssignedSSID[] = "DIRECT-A0";
constexpr char kAssignedPassphrase[] = "assigned-passphrase";

}  // namespace

using OperationResult = WifiP2PController::OperationResult;

class WifiP2PControllerTest : public ::testing::Test {
 public:
  struct WifiP2POperationTestResult {
    OperationResult result;
    std::optional<WifiP2PGroup> group_metadata;
  };

  void SetUp() override {
    shill_clients::InitializeFakes();
    PatchPanelClient::InitializeFake();
  }

  void TearDown() override {
    if (WifiP2PController::IsInitialized()) {
      WifiP2PController::Shutdown();
    }
    PatchPanelClient::Shutdown();
    shill_clients::Shutdown();
  }

  void Init() {
    WifiP2PController::Initialize();
    base::RunLoop().RunUntilIdle();
  }

  WifiP2POperationTestResult CreateP2PGroup(
      std::optional<std::string> ssid,
      std::optional<std::string> passphrase) {
    WifiP2POperationTestResult test_result;
    base::RunLoop run_loop;
    WifiP2PController::Get()->CreateWifiP2PGroup(
        ssid, passphrase,
        base::BindLambdaForTesting(
            [&](OperationResult result,
                std::optional<WifiP2PGroup> group_metadata) {
              test_result.result = result;
              test_result.group_metadata = group_metadata;
              run_loop.Quit();
            }));
    base::RunLoop().RunUntilIdle();
    return test_result;
  }

  OperationResult DestroyP2PGroup(const int shill_id) {
    OperationResult test_result;
    base::RunLoop run_loop;
    WifiP2PController::Get()->DestroyWifiP2PGroup(
        shill_id, base::BindLambdaForTesting([&](OperationResult result) {
          test_result = result;
          run_loop.Quit();
        }));
    base::RunLoop().RunUntilIdle();
    return test_result;
  }

  WifiP2POperationTestResult ConnectP2PGroup(const std::string& ssid,
                                             const std::string& passphrase,
                                             uint32_t frequency) {
    WifiP2POperationTestResult test_result;
    base::RunLoop run_loop;
    WifiP2PController::Get()->ConnectToWifiP2PGroup(
        ssid, passphrase, frequency,
        base::BindLambdaForTesting(
            [&](OperationResult result,
                std::optional<WifiP2PGroup> group_metadata) {
              test_result.result = result;
              test_result.group_metadata = group_metadata;
              run_loop.Quit();
            }));
    base::RunLoop().RunUntilIdle();
    return test_result;
  }

  OperationResult DisconnectP2PGroup(const int shill_id) {
    OperationResult test_result;
    base::RunLoop run_loop;
    WifiP2PController::Get()->DisconnectFromWifiP2PGroup(
        shill_id, base::BindLambdaForTesting([&](OperationResult result) {
          test_result = result;
          run_loop.Quit();
        }));
    base::RunLoop().RunUntilIdle();
    return test_result;
  }

  bool TagSocket(int network_id, base::ScopedFD socket_fd) {
    bool result;
    base::RunLoop run_loop;
    WifiP2PController::Get()->TagSocket(
        network_id, std::move(socket_fd),
        base::BindLambdaForTesting([&](bool success) {
          result = success;
          run_loop.Quit();
        }));
    base::RunLoop().RunUntilIdle();
    return result;
  }

 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  base::HistogramTester histogram_tester_;
};

TEST_F(WifiP2PControllerTest, FeatureEnabled) {
  Init();
  base::RunLoop run_loop;
  ShillManagerClient::Get()->GetProperties(
      base::BindLambdaForTesting([&](std::optional<base::DictValue> result) {
        if (!result) {
          ADD_FAILURE() << "Error getting Shill manager properties";
          run_loop.Quit();
          return;
        }
        std::optional<bool> p2p_allowed =
            result->FindBool(shill::kP2PAllowedProperty);
        if (!p2p_allowed) {
          ADD_FAILURE()
              << "Error getting TetheringAllowed in Shill manager properties";
        } else {
          EXPECT_EQ(true, *p2p_allowed);
        }
        run_loop.Quit();
      }));
  run_loop.Run();
}

TEST_F(WifiP2PControllerTest, CreateP2PGroupWithCredentials_Success) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                        shill::kCreateP2PGroupResultSuccess);
  const WifiP2POperationTestResult& result_arguments =
      CreateP2PGroup(kAssignedSSID, kAssignedPassphrase);
  EXPECT_EQ(result_arguments.result, OperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.group_metadata);
  EXPECT_EQ(result_arguments.group_metadata->shill_id(), 0);
  EXPECT_EQ(result_arguments.group_metadata->frequency(), 1000u);
  EXPECT_EQ(result_arguments.group_metadata->network_id(), 1);
  EXPECT_EQ(result_arguments.group_metadata->ipv4_address(),
            kDefaultIpv4Address);
  EXPECT_EQ(result_arguments.group_metadata->ssid(), kAssignedSSID);
  EXPECT_EQ(result_arguments.group_metadata->passphrase(), kAssignedPassphrase);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram, OperationResult::kSuccess,
      1);
}

TEST_F(WifiP2PControllerTest, CreateP2PGroupWithoutCredentials_Success) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                        shill::kCreateP2PGroupResultSuccess);
  const WifiP2POperationTestResult& result_arguments =
      CreateP2PGroup(/*ssid=*/std::nullopt, /*passphrase=*/std::nullopt);
  EXPECT_EQ(result_arguments.result, OperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.group_metadata);
  EXPECT_EQ(result_arguments.group_metadata->shill_id(), 0);
  EXPECT_EQ(result_arguments.group_metadata->frequency(), 1000u);
  EXPECT_EQ(result_arguments.group_metadata->network_id(), 1);
  EXPECT_EQ(result_arguments.group_metadata->ipv4_address(),
            kDefaultIpv4Address);
  EXPECT_EQ(result_arguments.group_metadata->ssid(), kDefaultSSID);
  EXPECT_EQ(result_arguments.group_metadata->passphrase(), kDefaultPassphrase);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram, OperationResult::kSuccess,
      1);
}

TEST_F(WifiP2PControllerTest, CreateP2PGroupFailure_InvalidArguments) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kCreateP2PGroupResultInvalidArguments);
  const WifiP2POperationTestResult& result_arguments =
      CreateP2PGroup("ssid", "passphrase");
  EXPECT_EQ(result_arguments.result, OperationResult::kInvalidArguments);
  EXPECT_FALSE(result_arguments.group_metadata);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram,
      OperationResult::kInvalidArguments, 1);
}

TEST_F(WifiP2PControllerTest, CreateP2PGroupFailure_DBusError) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(FakeShillSimulatedResult::kFailure,
                                        std::string());
  const WifiP2POperationTestResult& result_arguments =
      CreateP2PGroup("DIRECT-1a", "passphrase");
  EXPECT_EQ(result_arguments.result, OperationResult::kDBusError);
  EXPECT_FALSE(result_arguments.group_metadata);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kCreateP2PGroupHistogram,
      OperationResult::kDBusError, 1);
}

TEST_F(WifiP2PControllerTest, DestroyP2PGroupSuccess) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateDestroyP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                         shill::kDestroyP2PGroupResultSuccess);
  const OperationResult& result = DestroyP2PGroup(/*shill_id=*/0);
  EXPECT_EQ(result, OperationResult::kSuccess);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kDestroyP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kDestroyP2PGroupHistogram,
      OperationResult::kSuccess, 1);
}

TEST_F(WifiP2PControllerTest, DestroyP2PGroupSuccess_GroupNotFound) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateDestroyP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                         shill::kDestroyP2PGroupResultNoGroup);
  const OperationResult& result = DestroyP2PGroup(/*shill_id=*/0);
  EXPECT_EQ(result, OperationResult::kGroupNotFound);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kDestroyP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kDestroyP2PGroupHistogram,
      OperationResult::kGroupNotFound, 1);
}

TEST_F(WifiP2PControllerTest, ConnectToP2PGroupSuccess) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateConnectToP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kConnectToP2PGroupResultSuccess);
  const WifiP2POperationTestResult& result_arguments =
      ConnectP2PGroup(kAssignedSSID, kAssignedPassphrase, /*frequency=*/5200u);
  EXPECT_EQ(result_arguments.result, OperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.group_metadata);
  EXPECT_EQ(result_arguments.group_metadata->shill_id(), 0);
  EXPECT_EQ(result_arguments.group_metadata->frequency(), 5200u);
  EXPECT_EQ(result_arguments.group_metadata->network_id(), 1);
  EXPECT_EQ(result_arguments.group_metadata->ipv4_address(),
            kDefaultIpv4Address);
  EXPECT_EQ(result_arguments.group_metadata->ssid(), kAssignedSSID);
  EXPECT_EQ(result_arguments.group_metadata->passphrase(), kAssignedPassphrase);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kConnectP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kConnectP2PGroupHistogram,
      OperationResult::kSuccess, 1);
}

TEST_F(WifiP2PControllerTest,
       ConnectToP2PGroupFailure_ConcurrencyNotSupported) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateConnectToP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kConnectToP2PGroupResultConcurrencyNotSupported);
  const WifiP2POperationTestResult& result_arguments =
      ConnectP2PGroup("DIRECT-1a", "passphrase", /*frequency=*/5200u);
  EXPECT_EQ(result_arguments.result, OperationResult::kConcurrencyNotSupported);
  EXPECT_FALSE(result_arguments.group_metadata);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kConnectP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kConnectP2PGroupHistogram,
      OperationResult::kConcurrencyNotSupported, 1);
}

TEST_F(WifiP2PControllerTest, DisconnectFromP2PGroupSuccess) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateDisconnectFromP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kDisconnectFromP2PGroupResultSuccess);
  const OperationResult& result = DisconnectP2PGroup(/*shill_id=*/0);
  EXPECT_EQ(result, OperationResult::kSuccess);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kDisconnectP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kDisconnectP2PGroupHistogram,
      OperationResult::kSuccess, 1);
}

TEST_F(WifiP2PControllerTest, DisconnectFromP2PGroupFailure_NotConnected) {
  Init();

  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateDisconnectFromP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kDisconnectFromP2PGroupResultNotConnected);
  const OperationResult& result = DisconnectP2PGroup(/*shill_id=*/0);
  EXPECT_EQ(result, OperationResult::kNotConnected);

  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kDisconnectP2PGroupHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kDisconnectP2PGroupHistogram,
      OperationResult::kNotConnected, 1);
}

TEST_F(WifiP2PControllerTest, GetP2PCapabilities) {
  auto capabilities_dict =
      base::DictValue().Set(shill::kP2PCapabilitiesGroupReadinessProperty,
                            shill::kP2PCapabilitiesGroupReadinessReady);
  capabilities_dict.Set(shill::kP2PCapabilitiesClientReadinessProperty,
                        shill::kP2PCapabilitiesClientReadinessReady);
  capabilities_dict.Set(shill::kP2PCapabilitiesP2PSupportedProperty,
                        /*is_p2p_supported=*/true);
  ShillManagerClient::Get()->GetTestInterface()->SetManagerProperty(
      shill::kP2PCapabilitiesProperty, base::Value(capabilities_dict.Clone()));

  Init();

  WifiP2PController::WifiP2PCapabilities result =
      WifiP2PController::Get()->GetP2PCapabilities();
  EXPECT_TRUE(result.is_owner_ready);
  EXPECT_TRUE(result.is_client_ready);
  EXPECT_TRUE(result.is_p2p_supported);
  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kWifiP2PCapabilitiesHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kWifiP2PCapabilitiesHistogram,
      WifiP2PMetricsLogger::WifiP2PMetricsCapabilities::
          kBothClientAndOwnerReady,
      1);

  capabilities_dict.Set(shill::kP2PCapabilitiesClientReadinessProperty,
                        shill::kP2PCapabilitiesClientReadinessNotReady);
  capabilities_dict.Set(shill::kP2PCapabilitiesP2PSupportedProperty,
                        /*is_p2p_supported=*/false);
  ShillManagerClient::Get()->GetTestInterface()->SetManagerProperty(
      shill::kP2PCapabilitiesProperty, base::Value(capabilities_dict.Clone()));
  base::RunLoop().RunUntilIdle();

  result = WifiP2PController::Get()->GetP2PCapabilities();
  EXPECT_TRUE(result.is_owner_ready);
  EXPECT_FALSE(result.is_client_ready);
  EXPECT_FALSE(result.is_p2p_supported);
  histogram_tester_.ExpectTotalCount(
      WifiP2PMetricsLogger::kWifiP2PCapabilitiesHistogram, 2);
  histogram_tester_.ExpectBucketCount(
      WifiP2PMetricsLogger::kWifiP2PCapabilitiesHistogram,
      WifiP2PMetricsLogger::WifiP2PMetricsCapabilities::kOnlyOwnerReady, 1);
}

TEST_F(WifiP2PControllerTest, TagSocketSuccess) {
  Init();

  EXPECT_TRUE(TagSocket(123, base::ScopedFD()));
  histogram_tester_.ExpectTotalCount(WifiP2PMetricsLogger::kTagSocketHistogram,
                                     1);
  histogram_tester_.ExpectBucketCount(WifiP2PMetricsLogger::kTagSocketHistogram,
                                      true, 1);
}

TEST_F(WifiP2PControllerTest, TagSocketFailure) {
  Init();
  FakePatchPanelClient::Get()->set_tag_socket_success_for_testing(
      /*success=*/false);
  EXPECT_FALSE(TagSocket(123, base::ScopedFD()));
  histogram_tester_.ExpectTotalCount(WifiP2PMetricsLogger::kTagSocketHistogram,
                                     1);
  histogram_tester_.ExpectBucketCount(WifiP2PMetricsLogger::kTagSocketHistogram,
                                      false, 1);
}

}  // namespace ash
