// 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/app_mode/metrics/network_connectivity_metrics_service.h"

#include "ash/constants/ash_pref_names.h"
#include "base/check_deref.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/sync_wifi/network_test_helper.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {

class NetworkConnectivityMetricsServiceTest : public testing::Test {
 public:
  NetworkConnectivityMetricsServiceTest() = default;

  NetworkConnectivityMetricsServiceTest(
      const NetworkConnectivityMetricsServiceTest&) = delete;
  NetworkConnectivityMetricsServiceTest& operator=(
      const NetworkConnectivityMetricsServiceTest&) = delete;

  void SetUp() override {
    helper_.SetUp();
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }
  void TearDown() override {
    TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->RemoveUserPref(
        ash::prefs::kKioskMetrics);
  }
  NetworkStateHandler* network_state_handler() {
    return NetworkHandler::Get()->network_state_handler();
  }
  base::HistogramTester* histogram_tester() { return histogram_tester_.get(); }

  std::optional<int> GetNetworkDropsFromLocalState() {
    return TestingBrowserProcess::GetGlobal()
        ->local_state()
        ->GetDict(ash::prefs::kKioskMetrics)
        .FindInt(kKioskNetworkDrops);
  }

  void SimulateConnectionFailure(const NetworkState* network,
                                 std::string error) {
    SetNetworkProperty(network->path(), shill::kStateProperty,
                       shill::kStateConfiguration);

    SetNetworkProperty(network->path(), shill::kErrorProperty, error);
    SetNetworkProperty(network->path(), shill::kStateProperty,
                       shill::kStateFailure);
  }

  const NetworkState* SimulateConnectionSuccess() {
    const NetworkState* network = CreateNetwork();

    SetNetworkProperty(network->path(), shill::kStateProperty,
                       shill::kStateOnline);
    return network;
  }

  const NetworkState* CreateNetwork() {
    std::string guid = helper_.ConfigureWiFiNetwork(
        "ssid", /*is_secured=*/true, helper_.primary_user(),
        /*has_connected=*/true,
        /*owned_by_user=*/true, /*configured_by_sync=*/true);
    return helper_.network_state_helper()
        .network_state_handler()
        ->GetNetworkStateFromGuid(guid);
  }

 private:
  void SetNetworkProperty(const std::string& service_path,
                          const std::string& key,
                          const std::string& value) {
    helper_.network_state_test_helper()->SetServiceProperty(service_path, key,
                                                            base::Value(value));
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  sync_wifi::NetworkTestHelper helper_;
};

TEST_F(NetworkConnectivityMetricsServiceTest, StartNotInitialized) {
  auto service = std::make_unique<NetworkConnectivityMetricsService>(
      CHECK_DEREF(TestingBrowserProcess::GetGlobal()->local_state()));
  EXPECT_TRUE(network_state_handler()->HasObserver(service.get()));
  EXPECT_FALSE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());
}

TEST_F(NetworkConnectivityMetricsServiceTest, StartOnlineGoOnline) {
  EXPECT_TRUE(SimulateConnectionSuccess() != nullptr);
  auto service = std::make_unique<NetworkConnectivityMetricsService>(
      CHECK_DEREF(TestingBrowserProcess::GetGlobal()->local_state()));
  EXPECT_TRUE(network_state_handler()->HasObserver(service.get()));
  EXPECT_TRUE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());

  // Nothing changes when go online from online.
  EXPECT_TRUE(SimulateConnectionSuccess() != nullptr);
  EXPECT_TRUE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());
}

TEST_F(NetworkConnectivityMetricsServiceTest, StartOnlineGoOfflineDrop) {
  const auto* network = SimulateConnectionSuccess();
  auto service = std::make_unique<NetworkConnectivityMetricsService>(
      CHECK_DEREF(TestingBrowserProcess::GetGlobal()->local_state()));
  EXPECT_TRUE(network_state_handler()->HasObserver(service.get()));
  EXPECT_TRUE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());

  // Network connectivity drop.
  SimulateConnectionFailure(network, shill::kErrorUnknownFailure);
  EXPECT_FALSE(service->is_online());
  EXPECT_EQ(std::optional<int>(1), GetNetworkDropsFromLocalState());
}

TEST_F(NetworkConnectivityMetricsServiceTest, StartOfflineGoOffline) {
  const auto* network = CreateNetwork();
  SimulateConnectionFailure(network, shill::kErrorUnknownFailure);

  auto service = std::make_unique<NetworkConnectivityMetricsService>(
      CHECK_DEREF(TestingBrowserProcess::GetGlobal()->local_state()));
  EXPECT_TRUE(network_state_handler()->HasObserver(service.get()));
  EXPECT_FALSE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());

  // Number of drops does not change when go offline from offline.
  SimulateConnectionFailure(network, shill::kErrorUnknownFailure);
  EXPECT_FALSE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());
}

TEST_F(NetworkConnectivityMetricsServiceTest, StartOfflineGoOnline) {
  SimulateConnectionFailure(CreateNetwork(), shill::kErrorUnknownFailure);

  auto service = std::make_unique<NetworkConnectivityMetricsService>(
      CHECK_DEREF(TestingBrowserProcess::GetGlobal()->local_state()));
  EXPECT_TRUE(network_state_handler()->HasObserver(service.get()));
  EXPECT_FALSE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());

  // Number of drops does not change when go online from offline.
  EXPECT_TRUE(SimulateConnectionSuccess() != nullptr);
  EXPECT_TRUE(service->is_online());
  EXPECT_EQ(std::optional<int>(0), GetNetworkDropsFromLocalState());
}

TEST_F(NetworkConnectivityMetricsServiceTest, LogAndReportNetworkDrops) {
  constexpr size_t kMaxNetworkDrops = 5;

  auto service = std::make_unique<NetworkConnectivityMetricsService>(
      CHECK_DEREF(TestingBrowserProcess::GetGlobal()->local_state()));
  EXPECT_TRUE(network_state_handler()->HasObserver(service.get()));

  // Disconnect / connect networks kMaxNetworkDrops times.
  for (size_t network_drops = 1; network_drops <= kMaxNetworkDrops;
       network_drops++) {
    const auto* network = SimulateConnectionSuccess();
    EXPECT_TRUE(service->is_online());
    EXPECT_EQ(std::optional<int>(network_drops - 1),
              GetNetworkDropsFromLocalState());
    SimulateConnectionFailure(network, shill::kErrorUnknownFailure);
    EXPECT_FALSE(service->is_online());
    EXPECT_EQ(std::optional<int>(network_drops),
              GetNetworkDropsFromLocalState());
  }

  // Check network-drops from Local State gets reported once the next kiosk
  // session starts.
  service = std::make_unique<NetworkConnectivityMetricsService>(
      CHECK_DEREF(TestingBrowserProcess::GetGlobal()->local_state()));
  histogram_tester()->ExpectBucketCount(kKioskNetworkDropsPerSessionHistogram,
                                        kMaxNetworkDrops, 1);
}

}  // namespace ash
