// 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 "chrome/browser/web_applications/sampling_metrics_provider.h"

#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/test_mock_time_task_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/daily_metrics_helper.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"

namespace web_app {
namespace {

// This test class relies on the assumption that the only source of histogram
// emissions is the manual calls to SamplingMetricsProvider::EmitMetrics. This
// is currently guaranteed since the time between natural emissions (5 minutes)
// is significantly larger than test timeout.
class SamplingMetricsProviderInteractiveUiTest : public WebAppBrowserTestBase {
 public:
  void CheckWebAppCount(int web_app_count, bool is_active) {
    ukm::TestAutoSetUkmRecorder ukm_recorder;
    base::HistogramTester tester;
    web_app::SamplingMetricsProvider::EmitMetrics();

    // bucket0 means: web apps are not active
    // bucket1 means: web apps are active
    int bucket0_count = is_active ? 0 : 1;
    int bucket1_count = is_active ? 1 : 0;
    EXPECT_THAT(tester.GetAllSamples("WebApp.Engagement2.Active"),
                BucketsAre(base::Bucket(/*min=*/0, /*count=*/bucket0_count),
                           base::Bucket(/*min=*/1, /*count=*/bucket1_count)));
    EXPECT_THAT(tester.GetAllSamples("WebApp.Engagement2.Count"),
                BucketsAre(base::Bucket(/*min=*/web_app_count, /*count=*/1)));

    if (web_app_count == 0) {
      return;
    }

    // Flush UKM.
    using UkmEntry = ukm::builders::WebApp_DailyInteraction;
    FlushUkm(ukm_recorder);

    auto entries = ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
    auto* entry = entries[0].get();
    ukm_recorder.ExpectEntrySourceHasUrl(entry, GetInstallableAppURL());
    ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(entry, UkmEntry::kUsedName,
                                                   true);
    if (is_active) {
      EXPECT_TRUE(ukm::TestAutoSetUkmRecorder::EntryHasMetric(
          entry, UkmEntry::kForegroundDurationName));
      EXPECT_FALSE(ukm::TestAutoSetUkmRecorder::EntryHasMetric(
          entry, UkmEntry::kBackgroundDurationName));
    } else {
      EXPECT_FALSE(ukm::TestAutoSetUkmRecorder::EntryHasMetric(
          entry, UkmEntry::kForegroundDurationName));
      EXPECT_TRUE(ukm::TestAutoSetUkmRecorder::EntryHasMetric(
          entry, UkmEntry::kBackgroundDurationName));
    }
  }

  void FlushUkm(ukm::TestAutoSetUkmRecorder& ukm_recorder) {
    using UkmEntry = ukm::builders::WebApp_DailyInteraction;
    FlushAllRecordsForTesting(profile());
    ASSERT_TRUE(base::test::RunUntil([&]() {
      return ukm_recorder.GetEntriesByName(UkmEntry::kEntryName).size() == 1u;
    }));
  }

  void CheckOneUkmEntry() {
    ukm::TestAutoSetUkmRecorder ukm_recorder;

    // The UKM emission uses a bucket size of 28.8 minutes. We want to
    // distinguish between double and single emission. Each time we emit metrics
    // we emit 5 minutes of total time. This means we want to call this method 6
    // times. We expect to end up in bucket 2 (without duplicate emissions).
    // With duplicate emissions we'd end up in bucket 4.
    for (int i = 0; i < 6; ++i) {
      web_app::SamplingMetricsProvider::EmitMetrics();
    }
    FlushUkm(ukm_recorder);
    using UkmEntry = ukm::builders::WebApp_DailyInteraction;

    auto entries = ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
    auto* entry = entries[0].get();

    // The total foreground/background time should be equal to 30 minutes, which
    // is bucket 2.
    int64_t total_time = 0;
    const int64_t* foreground_time =
        ukm::TestAutoSetUkmRecorder::GetEntryMetric(
            entry, UkmEntry::kForegroundDurationName);
    if (foreground_time) {
      total_time += *foreground_time;
    }
    const int64_t* background_time =
        ukm::TestAutoSetUkmRecorder::GetEntryMetric(
            entry, UkmEntry::kBackgroundDurationName);
    if (background_time) {
      total_time += *background_time;
    }
    int expected_result = 24 * 60 * 60 / 50;
    EXPECT_EQ(total_time, expected_result);
  }

  webapps::AppId InstallTabbedPWA(const GURL& start_url) {
    auto web_app_info =
        WebAppInstallInfo::CreateWithStartUrlForTesting(start_url);
    web_app_info->scope = start_url.GetWithoutFilename();
    web_app_info->user_display_mode = mojom::UserDisplayMode::kBrowser;
    web_app_info->title = u"A Web App";
    return web_app::test::InstallWebApp(profile(), std::move(web_app_info));
  }
};

IN_PROC_BROWSER_TEST_F(SamplingMetricsProviderInteractiveUiTest,
                       OpenCloseAppBrowser) {
  // There are no web-apps open by default.
  CheckWebAppCount(/*web_app_count=*/0, /*is_active=*/false);

  // Install and launch an app browser.
  webapps::AppId app_id = InstallPWA(GetInstallableAppURL());
  BrowserWindowInterface* app_browser = LaunchWebAppBrowserAndWait(app_id);
  CheckWebAppCount(/*web_app_count=*/1, /*is_active=*/true);

  // Close.
  ui_test_utils::BrowserDestroyedObserver observer(app_browser);
  chrome::CloseWindow(app_browser);
  observer.Wait();
  CheckWebAppCount(/*web_app_count=*/0, /*is_active=*/false);
}

IN_PROC_BROWSER_TEST_F(SamplingMetricsProviderInteractiveUiTest, Tab) {
  // There are no web-apps open by default.
  CheckWebAppCount(/*web_app_count=*/0, /*is_active=*/false);

  // Install and launch a tabbed pwa.
  webapps::AppId app_id = InstallTabbedPWA(GetInstallableAppURL());
  BrowserWindowInterface* browser = LaunchBrowserForWebAppInTab(app_id);
  CheckWebAppCount(/*web_app_count=*/1, /*is_active=*/true);

  // Add a new tab that is not a PWA.
  AddBlankTabAndShow(browser);
  CheckWebAppCount(/*web_app_count=*/1, /*is_active=*/false);

  // Navigate the background PWA tab. Need to wait for navigation commit.
  // There are 3 tabs, so we want to navigate the second one.
  EXPECT_EQ(browser->tab_strip_model()->count(), 3);
  content::NavigationController::LoadURLParams params(GURL("about:blank"));
  auto* contents = browser->tab_strip_model()->GetTabAtIndex(1)->GetContents();
  CHECK(contents->GetController().LoadURLWithParams(params));
  content::WaitForLoadStop(contents);
  CheckWebAppCount(/*web_app_count=*/0, /*is_active=*/false);
}

// If the same PWA as multiple tabs open, only a single UKM event should be
// emitted.
IN_PROC_BROWSER_TEST_F(SamplingMetricsProviderInteractiveUiTest,
                       MultipleTabsSingleUkm) {
  webapps::AppId app_id = InstallTabbedPWA(GetInstallableAppURL());
  LaunchBrowserForWebAppInTab(app_id);
  LaunchBrowserForWebAppInTab(app_id);
  CheckOneUkmEntry();
}

// If the same PWA as multiple apps open, only a single UKM event should be
// emitted.
IN_PROC_BROWSER_TEST_F(SamplingMetricsProviderInteractiveUiTest,
                       MultipleAppsSingleUkm) {
  webapps::AppId app_id = InstallPWA(GetInstallableAppURL());
  LaunchWebAppBrowserAndWait(app_id);
  LaunchWebAppBrowserAndWait(app_id);
  CheckOneUkmEntry();
}

// Basic case for promotable.
IN_PROC_BROWSER_TEST_F(SamplingMetricsProviderInteractiveUiTest, Promotable) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  NavigateAndAwaitInstallabilityCheck(browser(), GetInstallableAppURL());
  AddBlankTabAndShow(browser());
  NavigateAndAwaitInstallabilityCheck(browser(), GetInstallableAppURL());

  web_app::SamplingMetricsProvider::EmitMetrics();
  FlushUkm(ukm_recorder);
  using UkmEntry = ukm::builders::WebApp_DailyInteraction;

  auto entries = ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
  auto* entry = entries[0].get();
  const int64_t* promotable = ukm::TestAutoSetUkmRecorder::GetEntryMetric(
      entry, UkmEntry::kPromotableName);
  ASSERT_TRUE(promotable);
  ASSERT_TRUE(*promotable);
}

// If PWA is installed as standalone, opening a tab does not count towards UKMs.
IN_PROC_BROWSER_TEST_F(SamplingMetricsProviderInteractiveUiTest,
                       MismatchedDisplayMode) {
  webapps::AppId app_id = InstallPWA(GetInstallableAppURL());
  LaunchBrowserForWebAppInTab(app_id);

  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Remove asynchronous code from FlushAllRecordsForTesting.
  SkipOriginCheckForTesting();

  // This should not emit any metrics.
  web_app::SamplingMetricsProvider::EmitMetrics();
  FlushAllRecordsForTesting(profile());

  // There should be no emissions.
  using UkmEntry = ukm::builders::WebApp_DailyInteraction;
  ASSERT_EQ(ukm_recorder.GetEntriesByName(UkmEntry::kEntryName).size(), 0u);
}

// Incognito windows should not cause crashes.
IN_PROC_BROWSER_TEST_F(SamplingMetricsProviderInteractiveUiTest,
                       IncognitoWindow) {
  CreateIncognitoBrowser(profile());
  CheckWebAppCount(/*web_app_count=*/0, /*is_active=*/false);
}

}  // namespace
}  // namespace web_app

