// 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 "components/metrics/structured/structured_metrics_service.h"

#include <memory>
#include <utility>

#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/structured/test/structured_metrics_mixin.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/ui/omnibox/omnibox_next_features.h"
#include "chrome/browser/unified_consent/unified_consent_service_factory.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "components/metrics/log_decoder.h"
#include "components/metrics/metrics_service_client.h"
#include "components/metrics/structured/structured_events.h"
#include "components/metrics/unsent_log_store.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
#include "components/sync/test/fake_server_network_resources.h"
#include "components/unified_consent/unified_consent_service.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"

namespace metrics {
namespace {

// The name hash of "TestProjectOne".
constexpr uint64_t kProjectOneHash = UINT64_C(16881314472396226433);

// The name hash of "TestProjectFive".
constexpr uint64_t kProjectFiveHash = UINT64_C(3960582687892677139);

// The name hash of "TestProjectSeven".
constexpr uint64_t kProjectSevenHash = UINT64_C(10319251808101486833);

// The name hash of "chrome::TestProjectOne::TestEventOne".
constexpr uint64_t kEventOneHash = UINT64_C(13593049295042080097);

// The name hash of "chrome::TestProjectFive::TestEventSix".
constexpr uint64_t kEventSixHash = UINT64_C(2873337042686447043);

// The name hash of "chrome::TestProjectSeven::TestEventEight".
constexpr uint64_t kEventEightHash = UINT64_C(16290206418240617738);

structured::StructuredMetricsService* GetSMService() {
  return g_browser_process->GetMetricsServicesManager()
      ->GetStructuredMetricsService();
}

}  // namespace

class StructuredMetricsServiceTestBase : public MixinBasedInProcessBrowserTest {
 public:
  StructuredMetricsServiceTestBase() = default;

  void SetUpOnMainThread() override {
    structured_metrics_mixin_.SetUpOnMainThread();
  }

  bool HasUnsentLogs() {
    return GetSMService()->log_store()->has_unsent_logs();
  }

  bool HasStagedLog() { return GetSMService()->log_store()->has_staged_log(); }

  void WaitForConsentChanges() { base::RunLoop().RunUntilIdle(); }
  void WaitUntilKeysReady() { structured_metrics_mixin_.WaitUntilKeysReady(); }

  std::unique_ptr<ChromeUserMetricsExtension> GetStagedLog() {
    if (!HasUnsentLogs()) {
      return nullptr;
    }

    auto* log_store = GetSMService()->log_store();
    if (log_store->has_staged_log()) {
      // For testing purposes, we examine the content of a staged log without
      // ever sending the log, so discard any previously staged log.
      log_store->DiscardStagedLog();
    }

    log_store->StageNextLog();
    if (!log_store->has_staged_log()) {
      return nullptr;
    }

    std::unique_ptr<ChromeUserMetricsExtension> uma_proto =
        std::make_unique<ChromeUserMetricsExtension>();
    if (!metrics::DecodeLogDataToProto(log_store->staged_log(),
                                       uma_proto.get())) {
      return nullptr;
    }
    return uma_proto;
  }

 protected:
  structured::StructuredMetricsMixin structured_metrics_mixin_{&mixin_host_};
};

class TestStructuredMetricsService : public StructuredMetricsServiceTestBase {
 public:
  TestStructuredMetricsService() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/
        {::features::kChromeStructuredMetrics},
        /*disabled_features=*/
        // TODO(crbug.com/452061489): Fix tests that fail when the WebUI Omnibox
        // is enabled and then remove these two Features.
        {omnibox::internal::kWebUIOmniboxPopup,
         omnibox::internal::kWebUIOmniboxAimPopup});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(TestStructuredMetricsService, EnabledWithConsent) {
  auto* sm_service = GetSMService();

  // Enable consent for profile.
  structured_metrics_mixin_.UpdateRecordingState(true);

  // Verify that recording and reporting are enabled.
  EXPECT_TRUE(sm_service->recording_enabled());
  EXPECT_TRUE(sm_service->reporting_active());
}

IN_PROC_BROWSER_TEST_F(TestStructuredMetricsService, DisabledWhenRevoked) {
  auto* sm_service = GetSMService();

  // Enable consent for profile.
  structured_metrics_mixin_.UpdateRecordingState(true);

  // Verify that recording and reporting are enabled.
  EXPECT_TRUE(sm_service->recording_enabled());
  EXPECT_TRUE(sm_service->reporting_active());

  // Revoke consent.
  structured_metrics_mixin_.UpdateRecordingState(false);

  // Verify that recording and reporting are disabled.
  EXPECT_FALSE(sm_service->recording_enabled());
  EXPECT_FALSE(sm_service->reporting_active());
}

IN_PROC_BROWSER_TEST_F(TestStructuredMetricsService,
                       // TODO(crbug.com/40931527): Re-enable this test
                       DISABLED_InMemoryPurgeOnConsentRevoke) {
  auto* sm_service = GetSMService();

  // Enable consent for profile.
  structured_metrics_mixin_.UpdateRecordingState(true);

  WaitForConsentChanges();

  // Verify that recording and reporting are enabled.
  EXPECT_TRUE(sm_service->recording_enabled());
  EXPECT_TRUE(sm_service->reporting_active());

  // Keys must be ready for events to be recorded actively. Otherwise, they will
  // be queued to be recorded and the test may fail.
  WaitUntilKeysReady();

  // Record a couple of events and verify that they are recorded.
  structured::StructuredMetricsClient::Record(
      structured::events::v2::test_project_one::TestEventOne()
          .SetTestMetricOne("metric one")
          .SetTestMetricTwo(10));

  structured::StructuredMetricsClient::Record(
      structured::events::v2::test_project_five::TestEventSix()
          .SetTestMetricSix("metric six"));

  // This will timeout and fail the test if events have not been recorded
  // successfully.
  structured_metrics_mixin_.WaitUntilEventRecorded(kProjectOneHash,
                                                   kEventOneHash);
  structured_metrics_mixin_.WaitUntilEventRecorded(kProjectFiveHash,
                                                   kEventSixHash);

  // Change the consent to force a purge.
  structured_metrics_mixin_.UpdateRecordingState(false);

  // There shouldn't be any staged or un-staged logs and no in-memory events.
  EXPECT_FALSE(HasUnsentLogs());
  EXPECT_FALSE(HasStagedLog());
  EXPECT_EQ(sm_service->recorder()->event_storage()->RecordedEventsCount(), 0);
}

// TODO(crbug.com/40931189): Re-enable this test
// Only flaky on chromeos-rel.
#if BUILDFLAG(IS_CHROMEOS) && defined(NDEBUG) && !defined(ADDRESS_SANITIZER)
#define MAYBE_StagedLogPurgeOnConsentRevoke \
  DISABLED_StagedLogPurgeOnConsentRevoke
#else
#define MAYBE_StagedLogPurgeOnConsentRevoke StagedLogPurgeOnConsentRevoke
#endif
IN_PROC_BROWSER_TEST_F(TestStructuredMetricsService,
                       MAYBE_StagedLogPurgeOnConsentRevoke) {
  auto* sm_service = GetSMService();

  // Enable consent for profile.
  structured_metrics_mixin_.UpdateRecordingState(true);

  WaitForConsentChanges();

  // Verify that recording and reporting are enabled.
  EXPECT_TRUE(sm_service->recording_enabled());
  EXPECT_TRUE(sm_service->reporting_active());

  // Keys must be ready for events to be recorded actively. Otherwise, they will
  // be queued to be recorded and the test may fail.
  WaitUntilKeysReady();

  // Record a couple of events and verify that they are recorded.
  structured::StructuredMetricsClient::Record(
      structured::events::v2::test_project_one::TestEventOne()
          .SetTestMetricOne("metric one")
          .SetTestMetricTwo(10));

  structured::StructuredMetricsClient::Record(
      structured::events::v2::test_project_five::TestEventSix()
          .SetTestMetricSix("metric six"));

  // This will timeout and fail the test if events have not been recorded
  // successfully.
  structured_metrics_mixin_.WaitUntilEventRecorded(kProjectOneHash,
                                                   kEventOneHash);
  structured_metrics_mixin_.WaitUntilEventRecorded(kProjectFiveHash,
                                                   kEventSixHash);

  // Flush the in-memory events to a staged log.
  sm_service->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);

  // Change the consent to force a purge.
  structured_metrics_mixin_.UpdateRecordingState(false);

  // There shouldn't be any staged or un-staged logs and no in-memory events.
  EXPECT_FALSE(HasUnsentLogs());
  EXPECT_FALSE(HasStagedLog());
  EXPECT_EQ(sm_service->recorder()->event_storage()->RecordedEventsCount(), 0);
}

#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(TestStructuredMetricsService, SystemProfilePopulated) {
  auto* sm_service = GetSMService();

  // Enable consent for profile.
  structured_metrics_mixin_.UpdateRecordingState(true);

  // Wait for the consent to propagate.
  WaitForConsentChanges();

  // Verify that recording and reporting are enabled.
  EXPECT_TRUE(sm_service->recording_enabled());
  EXPECT_TRUE(sm_service->reporting_active());

  WaitUntilKeysReady();

  // Record an event inorder to build a log.
  structured::StructuredMetricsClient::Record(
      structured::events::v2::test_project_one::TestEventOne()
          .SetTestMetricOne("metric one")
          .SetTestMetricTwo(10));

  // This will timeout and fail the test if events have not been recorded
  // successfully.
  structured_metrics_mixin_.WaitUntilEventRecorded(kProjectOneHash,
                                                   kEventOneHash);

  // Flush the in-memory events to a staged log.
  sm_service->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);

  std::unique_ptr<ChromeUserMetricsExtension> uma_proto = GetStagedLog();
  ASSERT_NE(uma_proto.get(), nullptr);

  // Verify that the SystemProfile has been set appropriately.
  const SystemProfileProto& system_profile = uma_proto->system_profile();
  EXPECT_EQ(system_profile.app_version(),
            GetSMService()->GetMetricsServiceClient()->GetVersionString());
}
#endif  //  BUILDFLAG(IS_CHROMEOS)

// TODO(crbug.com/41485716): Flaky on linux-chromeos-rel.
IN_PROC_BROWSER_TEST_F(TestStructuredMetricsService,
                       DISABLED_PurgeForceRecordedEvents) {
  // This feature is intended only for events that are recorded before user
  // consent.
  auto* sm_service = GetSMService();
  // Simulates an initial state of disabled metrics.
  structured_metrics_mixin_.UpdateRecordingState(false);
  WaitForConsentChanges();

  structured::StructuredMetricsClient::Record(
      structured::events::v2::test_project_seven::TestEventEight());

  structured_metrics_mixin_.WaitUntilEventRecorded(kProjectSevenHash,
                                                   kEventEightHash);

  // Confirms the disabled metrics when consent is not given.
  structured_metrics_mixin_.UpdateRecordingState(false);
  WaitForConsentChanges();

  EXPECT_FALSE(HasUnsentLogs());
  EXPECT_FALSE(HasStagedLog());
  EXPECT_EQ(sm_service->recorder()->event_storage()->RecordedEventsCount(), 0);
}

IN_PROC_BROWSER_TEST_F(TestStructuredMetricsService, CreateLogs) {
  auto* sm_service = GetSMService();
  structured_metrics_mixin_.UpdateRecordingState(true);
  WaitForConsentChanges();

  structured::StructuredMetricsClient::Record(
      structured::events::v2::test_project_seven::TestEventEight());

  structured_metrics_mixin_.WaitUntilEventRecorded(kProjectSevenHash,
                                                   kEventEightHash);

  // Makes sure that the logs are created without issues.
  // Disable upload, CreateLogs: creates the logs and starts the upload process.
  base::RunLoop run_loop;
  sm_service->SetCreateLogsCallbackInTests(run_loop.QuitClosure());
  sm_service->CreateLogs(
      metrics::MetricsLogsEventManager::CreateReason::kUnknown,
      /*notify_scheduler=*/false);
  run_loop.Run();

  EXPECT_TRUE(HasUnsentLogs());

  std::unique_ptr<ChromeUserMetricsExtension> uma_proto = GetStagedLog();
  EXPECT_NE(uma_proto.get(), nullptr);

  EXPECT_EQ(uma_proto->structured_data().events_size(), 1);
}

}  // namespace metrics
