// Copyright 2020 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/test/web_app_test_utils.h"

#include <stddef.h>

#include <algorithm>
#include <array>
#include <cstdint>
#include <deque>
#include <iterator>
#include <memory>
#include <optional>
#include <random>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>

#include "base/base64.h"
#include "base/base_paths.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.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_dialogs.h"
#include "chrome/browser/web_applications/model/app_installed_by.h"
#include "chrome/browser/web_applications/model/display_override.h"
#include "chrome/browser/web_applications/model/integrity_block_data.h"
#include "chrome/browser/web_applications/model/isolation_data.h"
#include "chrome/browser/web_applications/model/migration_behavior.h"
#include "chrome/browser/web_applications/model/migration_source.h"
#include "chrome/browser/web_applications/model/pending_migration_info.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/proto/web_app_isolation_data.pb.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/scope_extension_info.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/user_display_mode.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_chromeos_data.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_params.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/browser/web_applications/web_app_management_type.h"
#include "chrome/browser/web_applications/web_app_proto_utils.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/pref_names.h"
#include "components/base32/base32.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/file_handler.h"
#include "components/services/app_service/public/cpp/icon_info.h"
#include "components/services/app_service/public/cpp/protocol_handler_info.h"
#include "components/services/app_service/public/cpp/share_target.h"
#include "components/sync/base/time.h"
#include "components/sync/model/string_ordinal.h"
#include "components/sync/protocol/web_app_specifics.pb.h"
#include "components/web_package/signed_web_bundles/ecdsa_p256_public_key.h"
#include "components/web_package/signed_web_bundles/ecdsa_p256_sha256_signature.h"
#include "components/web_package/signed_web_bundles/ed25519_public_key.h"
#include "components/web_package/signed_web_bundles/ed25519_signature.h"
#include "components/web_package/signed_web_bundles/signed_web_bundle_id.h"
#include "components/web_package/signed_web_bundles/signed_web_bundle_signature_stack_entry.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/common/web_app_id.h"
#include "components/webapps/isolated_web_apps/types/iwa_version.h"
#include "components/webapps/isolated_web_apps/types/storage_location.h"
#include "components/webapps/isolated_web_apps/types/update_channel.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "third_party/blink/public/common/safe_url_pattern.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h"
#include "third_party/liburlpattern/part.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace {

std::vector<std::string> test_features = {
    "default_on_feature", "default_self_feature", "default_disabled_feature"};

constexpr std::string_view kEd25519PublicKeyBase64 =
    "3QrKLntnXWmBkrbLLoobC0uh7u0ahMcKQSqsbX4q7MM=";
constexpr std::string_view kEd25519SignatureHex =
    "94FBD9E3E0C0A4CD475067F1DC315E9EA30A5010350A9D3A4392CA9E0EBA854315372D7A6B"
    "48B6C4DDF9A5FAAE4E159D3B80632413DA2850B54A4D5D98EAE906";

constexpr std::string_view kEcdsaP256PublicKeyBase64 =
    "AxyCBzvQfu1yaF01392k1gu2qCtT1uA2+WfIEhlyJB5S";
constexpr std::string_view kEcdsaP256SHA256SignatureHex =
    "3044022007381524F538B04F99CCC62703F06C87F66EF41BDA18A22D8E57952AA23E53A6"
    "022063C7F81D3A44798CB95823FA38FC23B15E0483744657FF49E1E83AB8C06B63C2";

const std::array<web_app::SquareSizePx, 8> icon_sizes = {32,  48,  64,  96,
                                                         128, 256, 512, 1024};

}  // namespace

namespace web_app::test {

namespace {

class RandomHelper {
 public:
  explicit RandomHelper(int seed, bool non_zero)
      :  // Seed of 0 and 1 generate the same sequence, so skip 0.
        generator_(seed + 1),
        distribution_(0u, UINT32_MAX),
        non_zero_(non_zero) {}

  uint32_t next_uint() {
    return std::max(distribution_(generator_),
                    static_cast<uint32_t>(non_zero_));
  }

  // Return an unsigned int between 0 (inclusive) and bound (exclusive).
  uint32_t next_uint(uint32_t bound) {
    if (bound <= 1) {
      return 0;
    }
    uint32_t value = next_uint() % bound;
    if (value == 0 && non_zero_) {
      value = 1;
    }
    CHECK_LT(value, bound);
    return value;
  }

  bool next_bool() { return non_zero_ || next_uint() & 1u; }

  base::Time next_time() {
    return base::Time::UnixEpoch() + base::Milliseconds(next_uint());
  }

  int64_t next_proto_time() { return syncer::TimeToProtoTime(next_time()); }

  // max is inclusive.
  template <typename T, auto min = T::kMinValue, auto max = T::kMaxValue>
  T next_enum() {
    constexpr uint32_t min_u32 = static_cast<uint32_t>(min);
    constexpr uint32_t max_u32_exclusive = static_cast<uint32_t>(max) + 1;
    static_assert(min_u32 < max_u32_exclusive,
                  "min cannot be greater than max");
    return static_cast<T>(min_u32 + next_uint(max_u32_exclusive - min_u32));
  }

 private:
  std::default_random_engine generator_;
  std::uniform_int_distribution<uint32_t> distribution_;
  bool non_zero_;
};

#define NEXT_PROTO_ENUM(random_helper, T, skip_zero)         \
  static_cast<T>(static_cast<uint32_t>(skip_zero) +          \
                 random_helper.next_uint(T##_MAX - T##_MIN - \
                                         static_cast<uint32_t>(skip_zero)))

apps::FileHandlers CreateRandomFileHandlers(uint32_t suffix) {
  apps::FileHandlers file_handlers;

  for (unsigned int i = 0; i < 5; ++i) {
    std::string suffix_str =
        base::NumberToString(suffix) + base::NumberToString(i);

    apps::FileHandler::AcceptEntry accept_entry1;
    accept_entry1.mime_type = "application/" + suffix_str + "+foo";
    accept_entry1.file_extensions.insert("." + suffix_str + "a");
    accept_entry1.file_extensions.insert("." + suffix_str + "b");

    apps::FileHandler::AcceptEntry accept_entry2;
    accept_entry2.mime_type = "application/" + suffix_str + "+bar";
    accept_entry2.file_extensions.insert("." + suffix_str + "a");
    accept_entry2.file_extensions.insert("." + suffix_str + "b");

    apps::FileHandler file_handler;
    file_handler.action = GURL("https://example.com/open-" + suffix_str);
    file_handler.accept.push_back(std::move(accept_entry1));
    file_handler.accept.push_back(std::move(accept_entry2));
    file_handler.display_name = base::ASCIIToUTF16(suffix_str) + u" file";

    file_handlers.push_back(std::move(file_handler));
  }

  return file_handlers;
}

apps::ShareTarget CreateRandomShareTarget(uint32_t suffix) {
  apps::ShareTarget share_target;
  share_target.action =
      GURL("https://example.com/path/target/" + base::NumberToString(suffix));
  share_target.method = (suffix % 2 == 0) ? apps::ShareTarget::Method::kPost
                                          : apps::ShareTarget::Method::kGet;
  share_target.enctype = (suffix / 2 % 2 == 0)
                             ? apps::ShareTarget::Enctype::kMultipartFormData
                             : apps::ShareTarget::Enctype::kFormUrlEncoded;

  if (suffix % 3 != 0) {
    share_target.params.title = "title" + base::NumberToString(suffix);
  }
  if (suffix % 3 != 1) {
    share_target.params.text = "text" + base::NumberToString(suffix);
  }
  if (suffix % 3 != 2) {
    share_target.params.url = "url" + base::NumberToString(suffix);
  }

  for (uint32_t index = 0; index < suffix % 5; ++index) {
    apps::ShareTarget::Files files;
    files.name = "files" + base::NumberToString(index);
    files.accept.push_back(".extension" + base::NumberToString(index));
    files.accept.push_back("type/subtype" + base::NumberToString(index));
    share_target.params.files.push_back(files);
  }

  return share_target;
}

std::vector<apps::ProtocolHandlerInfo> CreateRandomProtocolHandlers(
    uint32_t suffix) {
  std::vector<apps::ProtocolHandlerInfo> protocol_handlers;

  for (unsigned int i = 0; i < 5; ++i) {
    std::string suffix_str =
        base::NumberToString(suffix) + base::NumberToString(i);

    apps::ProtocolHandlerInfo protocol_handler;
    protocol_handler.protocol = "web+test" + suffix_str;
    protocol_handler.url = GURL("https://example.com/").Resolve(suffix_str);

    protocol_handlers.push_back(std::move(protocol_handler));
  }

  return protocol_handlers;
}

ScopeExtensions CreateRandomScopeExtensions(uint32_t suffix,
                                            RandomHelper& random) {
  ScopeExtensions scope_extensions;
  for (unsigned int i = 0; i < 3; ++i) {
    std::string suffix_str =
        base::NumberToString(suffix) + base::NumberToString(i);

    if (random.next_bool()) {
      auto scope_extension = ScopeExtensionInfo::CreateForOrigin(
          url::Origin::Create(GURL("https://app-" + suffix_str + ".com/")),
          /*has_origin_wildcard*/ random.next_bool());
      scope_extensions.insert(std::move(scope_extension));
    } else {
      std::string random_scope = base::NumberToString(random.next_uint());
      auto scope_extension = ScopeExtensionInfo::CreateForScope(
          GURL("https://app-" + suffix_str + ".com/" + random_scope),
          /*has_origin_wildcard*/ random.next_bool());
      scope_extensions.insert(std::move(scope_extension));
    }
  }

  return scope_extensions;
}

std::vector<WebAppShortcutsMenuItemInfo> CreateRandomShortcutsMenuItemInfos(
    const GURL& scope,
    int num,
    RandomHelper& random) {
  const uint32_t suffix = random.next_uint();
  std::vector<WebAppShortcutsMenuItemInfo> shortcuts_menu_item_infos;
  for (int i = num - 1; i >= 0; --i) {
    std::string suffix_str =
        base::NumberToString(suffix) + base::NumberToString(i);
    WebAppShortcutsMenuItemInfo shortcut_info;
    shortcut_info.url = scope.Resolve("shortcut" + suffix_str);
    shortcut_info.name = base::UTF8ToUTF16("shortcut" + suffix_str);

    std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_icons_any;
    std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_icons_maskable;
    std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_icons_monochrome;

    for (int j = random.next_uint(4) + 1; j >= 0; --j) {
      std::string icon_suffix_str = suffix_str + base::NumberToString(j);
      WebAppShortcutsMenuItemInfo::Icon shortcut_icon;
      shortcut_icon.url = scope.Resolve("/shortcuts/icon" + icon_suffix_str);
      // Within each shortcut_icons_*, square_size_px must be unique.
      shortcut_icon.square_size_px = (j * 10) + random.next_uint(10);
      int icon_purpose = random.next_uint(3);
      switch (icon_purpose) {
        case 0:
          shortcut_icons_any.push_back(std::move(shortcut_icon));
          break;
        case 1:
          shortcut_icons_maskable.push_back(std::move(shortcut_icon));
          break;
        case 2:
          shortcut_icons_monochrome.push_back(std::move(shortcut_icon));
          break;
      }
    }

    shortcut_info.SetShortcutIconInfosForPurpose(IconPurpose::ANY,
                                                 std::move(shortcut_icons_any));
    shortcut_info.SetShortcutIconInfosForPurpose(
        IconPurpose::MASKABLE, std::move(shortcut_icons_maskable));
    shortcut_info.SetShortcutIconInfosForPurpose(
        IconPurpose::MONOCHROME, std::move(shortcut_icons_monochrome));

    shortcuts_menu_item_infos.push_back(std::move(shortcut_info));
  }
  return shortcuts_menu_item_infos;
}

std::vector<IconSizes> CreateRandomDownloadedShortcutsMenuIconsSizes(
    int num,
    RandomHelper& random) {
  std::vector<IconSizes> results;
  for (int i = 0; i < num; ++i) {
    IconSizes result;
    std::vector<SquareSizePx> shortcuts_menu_icon_sizes_any;
    std::vector<SquareSizePx> shortcuts_menu_icon_sizes_maskable;
    std::vector<SquareSizePx> shortcuts_menu_icon_sizes_monochrome;
    for (int j = 0; j < i; ++j) {
      shortcuts_menu_icon_sizes_any.push_back(random.next_uint(256) + 1);
      shortcuts_menu_icon_sizes_maskable.push_back(random.next_uint(256) + 1);
      shortcuts_menu_icon_sizes_monochrome.push_back(random.next_uint(256) + 1);
    }
    result.SetSizesForPurpose(IconPurpose::ANY,
                              std::move(shortcuts_menu_icon_sizes_any));
    result.SetSizesForPurpose(IconPurpose::MASKABLE,
                              std::move(shortcuts_menu_icon_sizes_maskable));
    result.SetSizesForPurpose(IconPurpose::MONOCHROME,
                              std::move(shortcuts_menu_icon_sizes_monochrome));
    results.push_back(std::move(result));
  }
  return results;
}

std::vector<blink::Manifest::ImageResource> CreateRandomHomeTabIcons(
    RandomHelper& random) {
  std::vector<blink::Manifest::ImageResource> icons;

  for (int i = random.next_uint(4) + 1; i >= 0; --i) {
    blink::Manifest::ImageResource icon;

    int mime_type = random.next_uint(3);
    switch (mime_type) {
      case 0:
        icon.src = GURL("https://example.com/image" + base::NumberToString(i) +
                        ".png");
        icon.type = base::UTF8ToUTF16(std::string("image/png"));
        break;
      case 1:
        icon.src = GURL("https://example.com/image" + base::NumberToString(i) +
                        ".svg");
        icon.type = base::UTF8ToUTF16(std::string("image/svg+xml"));
        break;
      case 2:
        icon.src = GURL("https://example.com/image" + base::NumberToString(i) +
                        ".webp");
        icon.type = base::UTF8ToUTF16(std::string("image/webp"));
        break;
    }

    // Icon sizes can be non square
    std::vector<gfx::Size> sizes;
    for (int j = random.next_uint(3) + 1; j > 0; --j) {
      int x = j * random.next_uint(200);
      int y = j * random.next_uint(200);
      sizes.emplace_back(x, y);
    }
    icon.sizes = std::move(sizes);

    std::vector<blink::mojom::ManifestImageResource_Purpose> purposes = {
        blink::mojom::ManifestImageResource_Purpose::ANY,
        blink::mojom::ManifestImageResource_Purpose::MASKABLE,
        blink::mojom::ManifestImageResource_Purpose::MONOCHROME};

    std::vector<blink::mojom::ManifestImageResource_Purpose> purpose;

    for (int j = random.next_uint(purposes.size()); j >= 0; --j) {
      unsigned index = random.next_uint(purposes.size());
      purpose.push_back(purposes[index]);
      purposes.erase(purposes.begin() + index);
    }
    icon.purpose = std::move(purpose);
    icons.push_back(std::move(icon));
  }
  return icons;
}

std::vector<blink::SafeUrlPattern> CreateRandomUrlPatterns(
    RandomHelper& random) {
  std::vector<blink::SafeUrlPattern> url_patterns;

  for (int i = random.next_uint(4) + 1; i >= 0; --i) {
    blink::SafeUrlPattern url_pattern;

    for (int j = random.next_uint(4) + 1; j >= 0; --j) {
      liburlpattern::Part part;

      std::vector<liburlpattern::PartType> part_types = {
          liburlpattern::PartType::kFixed,
          liburlpattern::PartType::kFullWildcard,
          liburlpattern::PartType::kSegmentWildcard};
      std::vector<liburlpattern::Modifier> modifiers = {
          liburlpattern::Modifier::kZeroOrMore,
          liburlpattern::Modifier::kOptional,
          liburlpattern::Modifier::kOneOrMore, liburlpattern::Modifier::kNone};
      part.type = part_types[random.next_uint(part_types.size())];
      part.value = "value" + base::NumberToString(j);
      if (part.type == liburlpattern::PartType::kFullWildcard ||
          part.type == liburlpattern::PartType::kSegmentWildcard) {
        part.prefix = "prefix" + base::NumberToString(j);
        part.name = "name" + base::NumberToString(j);
        part.suffix = "suffix" + base::NumberToString(j);
      }

      part.modifier = modifiers[random.next_uint(modifiers.size())];

      url_pattern.pathname.push_back(std::move(part));
    }

    url_patterns.push_back(std::move(url_pattern));
  }
  return url_patterns;
}

proto::os_state::WebAppOsIntegration GenerateRandomWebAppOsIntegration(
    RandomHelper& random,
    WebApp& app) {
  proto::os_state::WebAppOsIntegration state;

  // Randomly fill shortcuts data.
  auto* shortcuts = state.mutable_shortcut();
  shortcuts->set_title(app.untranslated_name());
  shortcuts->set_description(app.untranslated_description());
  auto* first_shortcut = shortcuts->add_icon_data_any();
  first_shortcut->set_icon_size(32);
  first_shortcut->set_timestamp(random.next_proto_time());

  // Randomly fill protocols_handled.
  auto* protocols_handled = state.mutable_protocols_handled();
  int num_protocols = random.next_uint(/*bound=*/3);
  for (int i = 0; i < num_protocols; i++) {
    auto* protocol = protocols_handled->add_protocols();
    protocol->set_protocol(base::StrCat({"web+test", base::NumberToString(i)}));
    protocol->set_url(
        base::StrCat({app.start_url().spec(), base::NumberToString(i)}));
  }

  // Randomly fill run_on_os_login.
  const std::array<proto::os_state::RunOnOsLogin::Mode, 3>
      run_on_os_login_modes = {
          proto::os_state::RunOnOsLogin::MODE_NOT_RUN,
          proto::os_state::RunOnOsLogin::MODE_WINDOWED,
          proto::os_state::RunOnOsLogin::MODE_MINIMIZED,
      };
  state.mutable_run_on_os_login()->set_run_on_os_login_mode(
      run_on_os_login_modes[random.next_uint(/*bound=*/3)]);

  // Randomly fill uninstallation registration logic.
  state.mutable_uninstall_registration()->set_registered_with_os(
      random.next_bool());
  state.mutable_uninstall_registration()->set_display_name(
      app.untranslated_name());

  // Randomly fill shortcuts menu information.
  auto* shortcut_menus = state.mutable_shortcut_menus();
  int count_shortcut_menu_items = random.next_uint(/*bound=*/2);
  for (int i = 0; i < count_shortcut_menu_items; i++) {
    auto* menu_info = shortcut_menus->add_shortcut_menu_info();
    menu_info->set_shortcut_name(
        base::StrCat({"shortcut_name", base::NumberToString(i)}));
    menu_info->set_shortcut_launch_url(
        base::StrCat({app.scope().spec(), base::NumberToString(i)}));

    auto* data_any = menu_info->add_icon_data_any();
    data_any->set_icon_size(16 * random.next_uint(/*bound=*/4));
    data_any->set_timestamp(random.next_proto_time());

    auto* data_maskable = menu_info->add_icon_data_maskable();
    data_maskable->set_icon_size(16 * random.next_uint(/*bound=*/4));
    data_maskable->set_timestamp(random.next_proto_time());

    auto* data_monochrome = menu_info->add_icon_data_monochrome();
    data_monochrome->set_icon_size(16 * random.next_uint(/*bound=*/4));
    data_monochrome->set_timestamp(random.next_proto_time());
  }

  // Randomly fill file handling information.
  auto* file_handling = state.mutable_file_handling();
  int count_file_handlers = random.next_uint(/*bound=*/2);
  for (int i = 0; i < count_file_handlers; i++) {
    auto* file_handlers = file_handling->add_file_handlers();
    int count_accepts = random.next_uint(/*bound=*/2);
    file_handlers->set_action(
        base::StrCat({"https://file.open/", base::NumberToString(i)}));
    file_handlers->set_display_name(
        base::StrCat({"file_type", base::NumberToString(i)}));
    for (int j = 0; j < count_accepts; j++) {
      auto* accept = file_handlers->add_accept();
      accept->set_mimetype(
          base::StrCat({"application/type", base::NumberToString(i)}));
      accept->add_file_extensions(
          base::StrCat({"foo", base::NumberToString(i)}));
      accept->add_file_extensions(
          base::StrCat({"bar", base::NumberToString(i)}));
    }
  }

  return state;
}

// Helper to create a single signature info based on type.
web_package::SignedWebBundleSignatureInfo CreateSignatureInfo(
    web_package::SignedWebBundleId::Type type) {
  switch (type) {
    case web_package::SignedWebBundleId::Type::kEd25519PublicKey: {
      auto public_key = *web_package::Ed25519PublicKey::Create(
          *base::Base64Decode(kEd25519PublicKeyBase64));
      std::vector<uint8_t> data;
      CHECK(base::HexStringToBytes(kEd25519SignatureHex, &data));
      auto signature = *web_package::Ed25519Signature::Create(data);
      return web_package::SignedWebBundleSignatureInfoEd25519(
          std::move(public_key), std::move(signature));
    }
    case web_package::SignedWebBundleId::Type::kEcdsaP256PublicKey: {
      auto public_key = *web_package::EcdsaP256PublicKey::Create(
          *base::Base64Decode(kEcdsaP256PublicKeyBase64));
      std::vector<uint8_t> data;
      CHECK(base::HexStringToBytes(kEcdsaP256SHA256SignatureHex, &data));
      auto signature = *web_package::EcdsaP256SHA256Signature::Create(data);
      return web_package::SignedWebBundleSignatureInfoEcdsaP256SHA256(
          std::move(public_key), std::move(signature));
    }
    default:
      NOTREACHED() << "Unknown SignedWebBundleId::Type encountered.";
  }
}

// Creates an IntegrityBlockData object with the primary key type guaranteed to
// be present, and a random mix of other available key types.
std::optional<IntegrityBlockData> CreateRandomIntegrityBlockData(
    RandomHelper& random,
    web_package::SignedWebBundleId::Type primary_key_type) {
  if (!random.next_bool()) {
    return std::nullopt;
  }
  std::vector<web_package::SignedWebBundleSignatureInfo> signatures;
  signatures.push_back(CreateSignatureInfo(primary_key_type));

  std::vector<web_package::SignedWebBundleId::Type> available_secondary_types =
      {web_package::SignedWebBundleId::Type::kEd25519PublicKey,
       web_package::SignedWebBundleId::Type::kEcdsaP256PublicKey};

  std::erase(available_secondary_types, primary_key_type);

  // Randomly include secondary signature types.
  for (const auto& secondary_type : available_secondary_types) {
    if (random.next_bool()) {
      signatures.push_back(CreateSignatureInfo(secondary_type));
    }
  }

  // Randomly include an unknown signature type.
  if (random.next_bool()) {
    signatures.push_back(web_package::SignedWebBundleSignatureInfoUnknown());
  }

  std::mt19937 rng(random.next_uint());
  std::ranges::shuffle(signatures, rng);
  return IntegrityBlockData(std::move(signatures));
}

std::vector<blink::Manifest::RelatedApplication>
CreateRandomRelatedApplications(RandomHelper& random) {
  std::vector<blink::Manifest::RelatedApplication> related_applications;
  const std::array<std::string, 7> platforms = {
      "chrome_web_store", "play",    "chromeos_play", "webapp",
      "windows",          "f-droid", "amazon"};
  for (int i = random.next_uint(4) + 1; i >= 0; --i) {
    blink::Manifest::RelatedApplication related_application;
    related_application.platform =
        base::UTF8ToUTF16(platforms[random.next_uint(platforms.size())]);
    bool set_url = random.next_bool();
    bool set_id = !set_url || random.next_bool();
    if (set_url) {
      related_application.url =
          GURL("https://example.com/" + base::NumberToString(i));
    }
    if (set_id) {
      related_application.id =
          base::UTF8ToUTF16("id" + base::NumberToString(i));
    }
    related_applications.push_back(std::move(related_application));
  }
  return related_applications;
}

std::vector<apps::IconInfo> CreateRandomIconMetadata(RandomHelper& random,
                                                     const GURL& base_url) {
  const int num_icons = random.next_uint(10) + 1;
  std::vector<apps::IconInfo> icons(num_icons);
  for (int i = 0; i < num_icons; i++) {
    apps::IconInfo icon;
    icon.url = base_url.Resolve(
        base::StrCat({"/icons", base::NumberToString(random.next_uint())}));
    icon.square_size_px = icon_sizes[random.next_uint(8)];

    int purpose = random.next_uint(4);
    if (purpose == 0) {
      icon.purpose = apps::IconInfo::Purpose::kAny;
    }
    if (purpose == 1) {
      icon.purpose = apps::IconInfo::Purpose::kMaskable;
    }
    if (purpose == 2) {
      icon.purpose = apps::IconInfo::Purpose::kMonochrome;
    }
    // if (purpose == 3), leave purpose unset. Should default to ANY.
    icons[i] = icon;
  }
  return icons;
}

std::vector<MigrationSource> CreateRandomMigrationSources(
    RandomHelper& random) {
  std::vector<MigrationSource> sources;
  int num_sources = random.next_uint(3);
  for (int i = 0; i < num_sources; ++i) {
    GURL manifest_id("https://example.com/manifest_id_" +
                     base::NumberToString(random.next_uint()));
    MigrationBehavior behavior = random.next_enum<MigrationBehavior>();
    std::optional<GURL> install_url;
    if (random.next_bool()) {
      install_url = GURL("https://example.com/install_url_" +
                         base::NumberToString(random.next_uint()));
    }
    sources.emplace_back(webapps::ManifestId(std::move(manifest_id)), behavior,
                         std::move(install_url));
  }
  return sources;
}

std::optional<PendingMigrationInfo> CreateRandomPendingMigrationInfos(
    RandomHelper& random) {
  if (!random.next_bool()) {
    return std::nullopt;
  }
  uint32_t random_id = random.next_uint();
  webapps::ManifestId manifest_id(GURL("https://example.com/manifest_id_" +
                                       base::NumberToString(random_id)));
  MigrationBehavior behavior = random.next_enum<MigrationBehavior>();
  return PendingMigrationInfo(manifest_id, behavior);
}

}  // namespace

std::unique_ptr<WebApp> CreateWebApp(const GURL& start_url,
                                     WebAppManagement::Type source_type,
                                     const GURL& scope) {
  auto web_app = std::make_unique<WebApp>(
      GenerateManifestIdFromStartUrlOnly(start_url), start_url,
      scope.is_valid() ? scope : start_url.GetWithoutFilename());
  // Adding OS integration to this app introduces too many edge cases in tests.
  // Simply set this to partially installed w/ no os integration, and the
  // correct OS integration state to match that.
  web_app->SetInstallState(
      proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION);
  web_app->AddSource(source_type);
  web_app->SetDisplayMode(blink::mojom::DisplayMode::kStandalone);
  web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
  web_app->SetName("Name");
  proto::os_state::WebAppOsIntegration os_state;
  web_app->SetCurrentOsIntegrationStates(os_state);

  return web_app;
}

std::unique_ptr<WebApp> CreateWebAppFromSyncProto(
    sync_pb::WebAppSpecifics& sync_proto) {
  CHECK(sync_proto.has_start_url() && GURL(sync_proto.start_url()).is_valid());
  if (!sync_proto.has_scope()) {
    sync_proto.set_scope(
        GURL(sync_proto.start_url()).GetWithoutFilename().spec());
  }
  auto web_app = std::make_unique<WebApp>(sync_proto);
  web_app->AddSource(WebAppManagement::kSync);
  return web_app;
}

CreateRandomWebAppParams::CreateRandomWebAppParams() = default;
CreateRandomWebAppParams::CreateRandomWebAppParams(
    const CreateRandomWebAppParams& other) = default;
CreateRandomWebAppParams& CreateRandomWebAppParams::operator=(
    const CreateRandomWebAppParams& other) = default;
CreateRandomWebAppParams::~CreateRandomWebAppParams() = default;

std::unique_ptr<WebApp> CreateRandomWebApp(
    const CreateRandomWebAppParams& params) {
  RandomHelper random(params.seed, params.non_zero);

  const bool is_iwa = !random.next_bool();
  const std::string seed_str = base::NumberToString(params.seed);

  std::optional<std::string> relative_manifest_id;
  if (random.next_bool()) {
    std::string manifest_id_path = "manifest_id_" + seed_str;
    if (random.next_bool()) {
      manifest_id_path += "?query=test";
    }
    relative_manifest_id = manifest_id_path;
  }
  std::string scope_path = "scope" + seed_str;
  // Ensure scope ends with a slash for correct resolution.
  if (!scope_path.ends_with("/")) {
    scope_path += "/";
  }

  GURL base_url = params.base_url;
  std::optional<web_package::SignedWebBundleId::Type> primary_key_type;

  if (is_iwa) {
    std::optional<web_package::SignedWebBundleId> web_bundle_id;

    // Randomly select primary key type.
    primary_key_type =
        random.next_bool()
            ? web_package::SignedWebBundleId::Type::kEd25519PublicKey
            : web_package::SignedWebBundleId::Type::kEcdsaP256PublicKey;

    // Generate SignedWebBundleId based on the primary key type.
    if (*primary_key_type ==
        web_package::SignedWebBundleId::Type::kEd25519PublicKey) {
      auto public_key = *web_package::Ed25519PublicKey::Create(
          *base::Base64Decode(kEd25519PublicKeyBase64));
      web_bundle_id =
          web_package::SignedWebBundleId::CreateForPublicKey(public_key);
    } else {
      auto public_key = *web_package::EcdsaP256PublicKey::Create(
          *base::Base64Decode(kEcdsaP256PublicKeyBase64));
      web_bundle_id =
          web_package::SignedWebBundleId::CreateForPublicKey(public_key);
    }
    base_url = GURL("isolated-app://" + web_bundle_id->id());
  }

  GURL scope = base_url.Resolve(scope_path);
  GURL start_url = scope.Resolve("start" + seed_str);
  const webapps::ManifestId manifest_id =
      relative_manifest_id
          ? GenerateManifestId(relative_manifest_id.value(), start_url)
          : GenerateManifestIdFromStartUrlOnly(start_url);

  const std::string name = "Name" + seed_str;
  const std::string description = "Description" + seed_str;
  auto app = std::make_unique<WebApp>(manifest_id, start_url, scope);

  app->SetName(name);
  app->SetDescription(description);

  if (random.next_bool()) {
    app->SetThemeColor(SkColorSetA(random.next_uint(), SK_AlphaOPAQUE));
  }
  if (random.next_bool()) {
    app->SetBackgroundColor(SkColorSetA(random.next_uint(), SK_AlphaOPAQUE));
  }
  if (random.next_bool()) {
    app->SetDarkModeThemeColor(SkColorSetA(random.next_uint(), SK_AlphaOPAQUE));
  }
  if (random.next_bool()) {
    app->SetDarkModeBackgroundColor(
        SkColorSetA(random.next_uint(), SK_AlphaOPAQUE));
  }

  app->SetInstallState(random.next_enum<proto::InstallState,
                                        /*min=*/proto::InstallState_MIN,
                                        /*max=*/
                                        proto::InstallState_MAX>());

  std::vector<WebAppManagement::Type> management_types;

  // Generate all possible permutations of field values in a random way:
  bool is_system_app = params.allow_system_source && !random.next_bool();
  if (is_system_app) {
    app->AddSource(WebAppManagement::kSystem);
    management_types.push_back(WebAppManagement::kSystem);
  }
  // System web apps cannot also have other sources.
  if (!is_system_app) {
    if (!params.exclude_fields_with_side_effects && random.next_bool()) {
      app->AddSource(WebAppManagement::kPolicy);
      management_types.push_back(WebAppManagement::kPolicy);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kWebAppStore);
      management_types.push_back(WebAppManagement::kWebAppStore);
    }
    if (random.next_bool() &&
        app->install_state() != proto::SUGGESTED_FROM_MIGRATION) {
      app->AddSource(WebAppManagement::kSync);
      management_types.push_back(WebAppManagement::kSync);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kUserInstalled);
      management_types.push_back(WebAppManagement::kUserInstalled);
    }
    if (!params.exclude_fields_with_side_effects && random.next_bool()) {
      app->AddSource(WebAppManagement::kDefault);
      management_types.push_back(WebAppManagement::kDefault);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kSubApp);
      management_types.push_back(WebAppManagement::kSubApp);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kKiosk);
      management_types.push_back(WebAppManagement::kKiosk);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kIwaShimlessRma);
      management_types.push_back(WebAppManagement::kIwaShimlessRma);
    }
    if (!params.exclude_fields_with_side_effects && random.next_bool()) {
      app->AddSource(WebAppManagement::kIwaPolicy);
      management_types.push_back(WebAppManagement::kIwaPolicy);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kIwaUserInstalled);
      management_types.push_back(WebAppManagement::kIwaUserInstalled);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kOem);
      management_types.push_back(WebAppManagement::kOem);
    }
    if (random.next_bool()) {
      app->AddSource(WebAppManagement::kOneDriveIntegration);
      management_types.push_back(WebAppManagement::kOneDriveIntegration);
    }
    if (!params.exclude_fields_with_side_effects && random.next_bool()) {
      app->AddSource(WebAppManagement::kApsDefault);
      management_types.push_back(WebAppManagement::kApsDefault);
    }
  }
  // Must always be at least one source.
  if (!app->HasAnySources()) {
    app->AddSource(WebAppManagement::kUserInstalled);
    management_types.push_back(WebAppManagement::kUserInstalled);
  }

  app->SetIsFromSyncAndPendingInstallation(random.next_bool());

  const std::array<sync_pb::WebAppSpecifics::UserDisplayMode, 3>
      user_display_modes = {
          sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER,
          sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE,
          sync_pb::WebAppSpecifics_UserDisplayMode_TABBED,
      };
  // Explicitly set a UserDisplayMode for each platform (instead of calling
  // `SetUserDisplayMode/SetPlatformSpecificUserDisplayMode` which sets the
  // current platform's value only) so the test expectations are consistent
  // across platforms.
  {
    app->UpdateDefaultUserDisplayModeInSyncProto(
        user_display_modes[random.next_uint(3)]);
    app->UpdateCrOsUserDisplayModeInSyncProto(
        user_display_modes[random.next_uint(3)]);
    CHECK(HasCurrentPlatformUserDisplayMode(app->sync_proto()));

    if (random.next_bool()) {
      app->SetUserPageOrdinal(syncer::StringOrdinal::CreateInitialOrdinal());
    }

    if (random.next_bool()) {
      app->SetUserLaunchOrdinal(syncer::StringOrdinal::CreateInitialOrdinal());
    }
  }

  app->SetLastBadgingTime(random.next_time());
  if (random.next_bool()) {
    app->SetLastLaunchTime(random.next_time());
  }
  app->SetFirstInstallTime(random.next_time());

  const std::array<DisplayMode, 4> display_modes = {
      DisplayMode::kBrowser,
      DisplayMode::kMinimalUi,
      DisplayMode::kStandalone,
      DisplayMode::kFullscreen,
  };
  app->SetDisplayMode(display_modes[random.next_uint(4)]);

  // Add only unique display modes.
  std::set<DisplayMode> display_mode_override;
  int num_display_mode_override_tries = random.next_uint(5);
  for (int i = 0; i < num_display_mode_override_tries; i++) {
    display_mode_override.insert(display_modes[random.next_uint(4)]);
  }
  std::vector<DisplayOverride> override_items;
  for (DisplayMode display_mode : display_mode_override) {
    override_items.push_back(
        display_mode == DisplayMode::kUnframed && random.next_bool()
            ? DisplayOverride::CreateUnframed(CreateRandomUrlPatterns(random))
            : DisplayOverride::Create(display_mode));
  }
  app->SetDisplayModeOverride(std::move(override_items));

  if (random.next_bool()) {
    app->SetLaunchQueryParams(base::NumberToString(random.next_uint()));
  }

  app->SetRunOnOsLoginMode(random.next_enum<RunOnOsLoginMode>());

  app->SetManifestIcons(CreateRandomIconMetadata(random, params.base_url));

  if (random.next_bool()) {
    app->SetDownloadedIconSizes(IconPurpose::ANY,
                                {icon_sizes[random.next_uint(8)]});
  }
  if (random.next_bool()) {
    app->SetDownloadedIconSizes(IconPurpose::MASKABLE,
                                {icon_sizes[random.next_uint(8)]});
  }
  if (random.next_bool()) {
    app->SetDownloadedIconSizes(IconPurpose::MONOCHROME,
                                {icon_sizes[random.next_uint(8)]});
  }
  app->SetIsGeneratedIcon(random.next_bool());

  app->SetFileHandlers(CreateRandomFileHandlers(random.next_uint()));
  if (random.next_bool()) {
    app->SetShareTarget(CreateRandomShareTarget(random.next_uint()));
  }

  webapps::WebappInstallSource install_source =
      random.next_enum<webapps::WebappInstallSource, 0>();
  app->SetLatestInstallSource(install_source);

  app->SetProtocolHandlers(CreateRandomProtocolHandlers(random.next_uint()));
  app->SetScopeExtensions(
      CreateRandomScopeExtensions(random.next_uint(), random));

  ScopeExtensions validated_scope_extensions;
  std::ranges::copy_if(app->scope_extensions(),
                       std::inserter(validated_scope_extensions,
                                     validated_scope_extensions.begin()),
                       [&random](const ScopeExtensionInfo& extension) {
                         return random.next_bool();
                       });
  app->SetValidatedScopeExtensions(validated_scope_extensions);
  if (random.next_bool()) {
    app->SetOriginAssociationLastValidationCheckTime(random.next_time());
  }
  app->SetIsDiyApp(random.next_bool());
  app->SetWasShortcutApp(random.next_bool());

  if (random.next_bool()) {
    app->SetLockScreenStartUrl(scope.Resolve(
        "lock_screen_start_url" + base::NumberToString(random.next_uint())));
  }
  if (random.next_bool()) {
    app->SetNoteTakingNewNoteUrl(
        scope.Resolve("new_note" + base::NumberToString(random.next_uint())));
  }

  const int num_additional_search_terms = random.next_uint(8);
  std::vector<std::string> additional_search_terms(num_additional_search_terms);
  for (int i = 0; i < num_additional_search_terms; ++i) {
    additional_search_terms[i] =
        "Foo_" + seed_str + "_" + base::NumberToString(i);
  }
  app->SetAdditionalSearchTerms(std::move(additional_search_terms));

  int num_shortcut_items = static_cast<int>(random.next_uint(4)) + 1;
  auto item_infos =
      CreateRandomShortcutsMenuItemInfos(scope, num_shortcut_items, random);
  auto icons_sizes =
      CreateRandomDownloadedShortcutsMenuIconsSizes(num_shortcut_items, random);
  CHECK_EQ(item_infos.size(), icons_sizes.size());
  for (int i = 0; i < num_shortcut_items; ++i) {
    item_infos[i].downloaded_icon_sizes = std::move(icons_sizes[i]);
  }
  icons_sizes.clear();
  app->SetShortcutsMenuInfo(std::move(item_infos));

  app->SetManifestUrl(
      params.base_url.Resolve("/manifest" + seed_str + ".json"));

  const int num_allowed_launch_protocols = random.next_uint(8);
  std::vector<std::string> allowed_launch_protocols(
      num_allowed_launch_protocols);
  for (int i = 0; i < num_allowed_launch_protocols; ++i) {
    allowed_launch_protocols[i] =
        "web+test_" + seed_str + "_" + base::NumberToString(i);
  }
  app->SetAllowedLaunchProtocols(std::move(allowed_launch_protocols));

  const int num_disallowed_launch_protocols = random.next_uint(8);
  std::vector<std::string> disallowed_launch_protocols(
      num_disallowed_launch_protocols);
  for (int i = 0; i < num_disallowed_launch_protocols; ++i) {
    disallowed_launch_protocols[i] =
        "web+disallowed_" + seed_str + "_" + base::NumberToString(i);
  }
  app->SetDisallowedLaunchProtocols(std::move(disallowed_launch_protocols));

  app->SetWindowControlsOverlayEnabled(false);

  if (random.next_bool()) {
    if (random.next_bool()) {
      app->SetLaunchHandler(LaunchHandler(std::nullopt));
    } else {
      app->SetLaunchHandler(
          LaunchHandler{random.next_enum<LaunchHandler::ClientMode>()});
    }
  }

  app->SetManifestUpdateTime(random.next_time());

  if (IsChromeOsDataMandatory()) {
    // Use a separate random generator for CrOS so the result is deterministic
    // across cros and non-cros builds.
    RandomHelper cros_random(params.seed, params.non_zero);
    auto chromeos_data = std::make_optional<WebAppChromeOsData>();
    chromeos_data->show_in_launcher = cros_random.next_bool();
    chromeos_data->show_in_search_and_shelf = cros_random.next_bool();
    chromeos_data->show_in_management = cros_random.next_bool();
    chromeos_data->is_disabled = cros_random.next_bool();
    chromeos_data->oem_installed = cros_random.next_bool();
    // Comply with DCHECK that system apps and shimless RMA apps cannot be OEM
    // installed.
    if (app->IsSystemApp() || app->IsIwaShimlessRmaApp()) {
      chromeos_data->oem_installed = false;
    }
    app->SetWebAppChromeOsData(std::move(chromeos_data));
  }

  WebApp::ExternalConfigMap management_to_external_config;
  for (WebAppManagement::Type type : management_types) {
    if (type == WebAppManagement::kSync ||
        type == WebAppManagement::kUserInstalled ||
        WebAppManagement::IsIwaType(type)) {
      continue;
    }
    base::flat_set<GURL> install_urls;
    base::flat_set<std::string> additional_policy_ids;
    WebApp::ExternalManagementConfig config;
    if (random.next_bool()) {
      install_urls.emplace(
          params.base_url.Resolve("installer1_" + seed_str + "/"));
    }
    if (random.next_bool()) {
      install_urls.emplace(
          params.base_url.Resolve("installer2_" + seed_str + "/"));
    }
    if (random.next_bool()) {
      additional_policy_ids.emplace("policy_id_1_" + seed_str);
    }
    if (random.next_bool()) {
      additional_policy_ids.emplace("policy_id_2_" + seed_str);
    }
    config.is_placeholder = random.next_bool();
    config.install_urls = std::move(install_urls);
    config.additional_policy_ids = std::move(additional_policy_ids);
    management_to_external_config.insert_or_assign(type, std::move(config));
  }

  app->SetWebAppManagementExternalConfigMap(management_to_external_config);

  app->SetAppSizeInBytes(random.next_uint());
  app->SetDataSizeInBytes(random.next_uint());

  if (random.next_bool()) {
    blink::Manifest::TabStrip tab_strip;

    if (random.next_bool()) {
      blink::Manifest::HomeTabParams home_tab_params;
      if (random.next_bool()) {
        home_tab_params.icons = CreateRandomHomeTabIcons(random);
      }
      if (random.next_bool()) {
        home_tab_params.scope_patterns = CreateRandomUrlPatterns(random);
      }
      tab_strip.home_tab = std::move(home_tab_params);
    } else {
      tab_strip.home_tab =
          random.next_enum<blink::mojom::TabStripMemberVisibility>();
    }

    blink::Manifest::NewTabButtonParams new_tab_button_params;
    if (random.next_bool()) {
      new_tab_button_params.url = scope.Resolve(
          "new_tab_button_url" + base::NumberToString(random.next_uint()));
    }
    tab_strip.new_tab_button = new_tab_button_params;

    app->SetTabStrip(std::move(tab_strip));
  }

  app->SetAlwaysShowToolbarInFullscreen(random.next_bool());

  // Note: Even though OS integration sometimes should be guaranteed to be not
  // there or there based on the install state or
  // is_from_sync_and_pending_installation, Because
  // current_os_integration_states is set in a two-phase-commit style, it is
  // possible & normal for it to be out of sync with what is desired. So allow
  // it to be set to any value.
  app->SetCurrentOsIntegrationStates(
      GenerateRandomWebAppOsIntegration(random, *app));

  // Isolated Web App Isolation Data.
  if (is_iwa) {
    CHECK(primary_key_type.has_value());
    const bool dev_mode = random.next_bool();

    auto get_location_type = [&seed_str, &random,
                              dev_mode]() -> IsolatedWebAppStorageLocation {
      if (!dev_mode) {
        return IwaStorageOwnedBundle{
            base32::Base32Encode(base::as_byte_span(seed_str),
                                 base32::Base32EncodePolicy::OMIT_PADDING),
            /*dev_mode=*/false};
      } else {
        constexpr size_t kNumLocationTypes =
            std::variant_size<IsolatedWebAppStorageLocation::Variant>::value;
        std::array<IsolatedWebAppStorageLocation, kNumLocationTypes>
            location_types = {
                IwaStorageOwnedBundle{
                    base32::Base32Encode(
                        base::as_byte_span(seed_str),
                        base32::Base32EncodePolicy::OMIT_PADDING),
                    /*dev_mode=*/true},

                IwaStorageUnownedBundle{
                    base::FilePath::FromUTF8Unsafe(seed_str)},
                IwaStorageProxy{url::Origin::Create(
                    GURL(base::StrCat({"https://proxy-", seed_str, ".com/"})))},
            };
        return location_types.at(random.next_uint(kNumLocationTypes));
      }
    };

    IwaVersion iwa_version = *IwaVersion::Create({
        random.next_uint(UINT32_MAX - 1U),
        random.next_uint(),
        random.next_uint(),
    });

    IsolationData::Builder idb(get_location_type(), iwa_version);
    std::optional<IntegrityBlockData> integrity_block_data =
        CreateRandomIntegrityBlockData(random, *primary_key_type);
    if (integrity_block_data) {
      idb.SetIntegrityBlockData(std::move(*integrity_block_data));
    }

    if (random.next_bool()) {
      idb.SetControlledFramePartitions({"partition_name"});
    }
    if (random.next_bool()) {
      IwaVersion pending_version = [&]() {
        if (random.next_bool()) {
          // Case where `pending_version == version`. Useful for validating key
          // rotation scenarios.
          return iwa_version;
        }
        // Otherwise, create `pending_version > version`.
        uint32_t major_version = iwa_version.version().components()[0];
        CHECK_LT(major_version, UINT32_MAX - 1U);
        uint32_t delta = random.next_uint(UINT32_MAX - 1U - major_version) + 1;
        // `major_version` + `delta` < UINT32_MAX.
        return *IwaVersion::Create(
            {major_version + delta, random.next_uint(), random.next_uint()});
      }();
      CHECK_GE(pending_version, iwa_version);

      IsolationData::PendingUpdateInfo pending_update_info(
          get_location_type(), pending_version, integrity_block_data);
      idb.SetPendingUpdateInfo(std::move(pending_update_info));
    }
    if (dev_mode && random.next_bool()) {
      idb.SetUpdateManifestUrl(GURL("https://update-manifest.com"));
    }
    if (random.next_bool()) {
      idb.SetUpdateChannel(UpdateChannel::default_channel());
    }
    if (random.next_bool()) {
      proto::IsolationData::OpenedTabsCounterNotificationState proto_state;
      proto_state.set_acknowledged(random.next_bool());
      proto_state.set_times_shown(random.next_uint(3));
      idb.SetOpenedTabsCounterNotificationState(
          IsolationData::OpenedTabsCounterNotificationState(
              std::move(proto_state)));
    }
    app->SetIsolationData(std::move(idb).Build());
  }

  app->SetLinkCapturingUserPreference(NEXT_PROTO_ENUM(
      random, proto::LinkCapturingUserPreference, /*skip_zero=*/false));

  app->SetLatestInstallTime(random.next_time());
  if (random.next_bool()) {
    proto::GeneratedIconFix generated_icon_fix;
    generated_icon_fix.set_source(NEXT_PROTO_ENUM(
        random, proto::GeneratedIconFixSource, /*skip_zero=*/true));
    generated_icon_fix.set_window_start_time(random.next_proto_time());
    if (random.next_bool()) {
      generated_icon_fix.set_last_attempt_time(random.next_proto_time());
    }
    generated_icon_fix.set_attempt_count(random.next_uint(100));
    app->SetGeneratedIconFix(generated_icon_fix);
  }

  app->SetSupportedLinksOfferIgnoreCount(random.next_uint());
  app->SetSupportedLinksOfferDismissCount(random.next_uint());
  app->SetRelatedApplications(CreateRandomRelatedApplications(random));
  app->SetDiyAppIconsMaskedOnMac(random.next_bool());

  if (random.next_bool()) {
    proto::PendingUpdateInfo pending_update_info;
    if (random.next_bool()) {
      pending_update_info.set_name(name);
    }

    if (random.next_bool() || !pending_update_info.has_name()) {
      std::vector<apps::IconInfo> trusted_icons_to_update =
          CreateRandomIconMetadata(random, params.base_url);
      std::vector<apps::IconInfo> manifest_icons_to_update =
          CreateRandomIconMetadata(random, params.base_url);

      // A mapping of the icon purpose to the downloaded icon sizes.
      std::map<sync_pb::WebAppIconInfo::Purpose, std::vector<int32_t>>
          trusted_sizes_by_purpose;
      std::map<sync_pb::WebAppIconInfo::Purpose, std::vector<int32_t>>
          manifest_sizes_by_purpose;

      for (const auto& trusted_icon : trusted_icons_to_update) {
        *pending_update_info.add_trusted_icons() =
            AppIconInfoToSyncProto(trusted_icon);
        const auto icon_purpose =
            IconInfoPurposeToSyncPurpose(trusted_icon.purpose);
        const int32_t icon_size = trusted_icon.square_size_px.value();

        trusted_sizes_by_purpose[icon_purpose].push_back(icon_size);
      }

      for (const auto& manifest_icon : manifest_icons_to_update) {
        *pending_update_info.add_manifest_icons() =
            AppIconInfoToSyncProto(manifest_icon);
        const auto icon_purpose =
            IconInfoPurposeToSyncPurpose(manifest_icon.purpose);
        const int32_t icon_size = manifest_icon.square_size_px.value();

        manifest_sizes_by_purpose[icon_purpose].push_back(icon_size);
      }

      for (const auto& trusted_size_purpose : trusted_sizes_by_purpose) {
        proto::DownloadedIconSizeInfo downloaded_icon_info;
        downloaded_icon_info.set_purpose(trusted_size_purpose.first);
        for (int32_t size : trusted_size_purpose.second) {
          downloaded_icon_info.add_icon_sizes(size);
        }
        *pending_update_info.add_downloaded_trusted_icons() =
            downloaded_icon_info;
      }

      for (const auto& manifest_size_purpose : manifest_sizes_by_purpose) {
        proto::DownloadedIconSizeInfo downloaded_icon_info;
        downloaded_icon_info.set_purpose(manifest_size_purpose.first);
        for (int32_t size : manifest_size_purpose.second) {
          downloaded_icon_info.add_icon_sizes(size);
        }
        *pending_update_info.add_downloaded_manifest_icons() =
            downloaded_icon_info;
      }
    }

    pending_update_info.set_was_ignored(random.next_bool());
    app->SetPendingUpdateInfo(pending_update_info);
  }

  app->SetTrustedIcons(CreateRandomIconMetadata(random, params.base_url));
  if (random.next_bool()) {
    app->SetStoredTrustedIconSizes(IconPurpose::ANY,
                                   {icon_sizes[random.next_uint(8)]});
  }
  if (random.next_bool()) {
    app->SetStoredTrustedIconSizes(IconPurpose::MASKABLE,
                                   {icon_sizes[random.next_uint(8)]});
  }

  base::Time first_install_time = random.next_time();
  if (random.next_bool()) {
    app->AddInstalledByInfo(web_app::AppInstalledBy(
        first_install_time,
        params.base_url.Resolve("installed_by1_" + seed_str + "/")));
  }
  if (random.next_bool()) {
    // Ensure the second timestamp is later than the first.
    base::Time second_install_time = first_install_time + base::Milliseconds(1);
    app->AddInstalledByInfo(web_app::AppInstalledBy(
        second_install_time,
        params.base_url.Resolve("installed_by2_" + seed_str + "/")));
  }

  if (!params.exclude_fields_with_side_effects) {
    app->SetUnvalidatedMigrationSources(CreateRandomMigrationSources(random));
    std::vector<MigrationSource> validated_sources;
    std::ranges::copy_if(
        app->unvalidated_migration_sources(),
        std::back_inserter(validated_sources),
        [&random](const MigrationSource&) { return random.next_bool(); });
    app->SetValidatedMigrationSources(std::move(validated_sources));
    app->SetPendingMigrationInfo(CreateRandomPendingMigrationInfos(random));
  }

  return app;
}

void TestAcceptDialogCallback(
    base::WeakPtr<WebAppScreenshotFetcher>,
    content::WebContents* initiator_web_contents,
    std::unique_ptr<WebAppInstallInfo> web_app_info,
    WebAppInstallationAcceptanceCallback acceptance_callback) {
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          std::move(acceptance_callback), true /*accept*/,
          std::move(web_app_info),
          base::BindOnce(
              [](bool success, base::OnceClosure reparent_or_launch_app) {
                if (success && reparent_or_launch_app) {
                  std::move(reparent_or_launch_app).Run();
                }
              })));
}

void TestDeclineDialogCallback(
    base::WeakPtr<WebAppScreenshotFetcher>,
    content::WebContents* initiator_web_contents,
    std::unique_ptr<WebAppInstallInfo> web_app_info,
    WebAppInstallationAcceptanceCallback acceptance_callback) {
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(acceptance_callback), false /*accept*/,
                     std::move(web_app_info), base::DoNothing()));
}

// TODO(b/329703817): Make this smarter by waiting for a specific dialog, and
// then triggering accept on the dialog.
webapps::AppId InstallPwaForCurrentUrl(BrowserWindowInterface* browser) {
  // Depending on the installability criteria, different dialogs can be used.
  base::AutoReset<web_app::InstallDialogTestResponse> auto_accept =
      web_app::SetPwaInstallationAutoRespondForTesting(
          web_app::InstallDialogTestResponse::kAcceptAndLaunch);
  base::AutoReset<web_app::CreateShortcutDialogCheckState> auto_check =
      web_app::SetCreateShortcutDialogCheckStateForTesting(
          web_app::CreateShortcutDialogCheckState::kChecked);
  WebAppTestInstallWithOsHooksObserver observer(browser->GetProfile());
  observer.BeginListening();
  CHECK(chrome::ExecuteCommand(browser, IDC_INSTALL_PWA));
  webapps::AppId app_id = observer.Wait();
  return app_id;
}

void CheckServiceWorkerStatus(const GURL& url,
                              content::StoragePartition* storage_partition,
                              content::ServiceWorkerCapability status) {
  base::RunLoop run_loop;
  content::ServiceWorkerContext* service_worker_context =
      storage_partition->GetServiceWorkerContext();
  service_worker_context->CheckHasServiceWorker(
      url, blink::StorageKey::CreateFirstParty(url::Origin::Create(url)),
      base::BindLambdaForTesting(
          [&run_loop, status](content::ServiceWorkerCapability capability) {
            CHECK_EQ(status, capability);
            run_loop.Quit();
          }));
  run_loop.Run();
}

void SetWebAppSettingsListPref(Profile* profile, std::string_view pref) {
  auto result = base::JSONReader::ReadAndReturnValueWithError(
      pref, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
  DCHECK(result.has_value()) << result.error().message;
  DCHECK(result->is_list());
  profile->GetPrefs()->Set(prefs::kWebAppSettings, std::move(*result));
}

void AddInstallUrlData(PrefService* pref_service,
                       WebAppSyncBridge* sync_bridge,
                       const webapps::AppId& app_id,
                       const GURL& url,
                       const ExternalInstallSource& source) {
  ScopedRegistryUpdate update = sync_bridge->BeginUpdate();
  WebApp* app_to_update = update->UpdateApp(app_id);
  DCHECK(app_to_update);

  // Adding external app data (source and URL) to web_app DB.
  app_to_update->AddInstallURLToManagementExternalConfigMap(
      ConvertExternalInstallSourceToSource(source), url);
}

void AddInstallUrlAndPlaceholderData(PrefService* pref_service,
                                     WebAppSyncBridge* sync_bridge,
                                     const webapps::AppId& app_id,
                                     const GURL& url,
                                     const ExternalInstallSource& source,
                                     bool is_placeholder) {
  ScopedRegistryUpdate update = sync_bridge->BeginUpdate();
  WebApp* app_to_update = update->UpdateApp(app_id);
  DCHECK(app_to_update);

  // Adding install_url, source and placeholder information to the web_app DB.
  app_to_update->AddExternalSourceInformation(
      ConvertExternalInstallSourceToSource(source), url, is_placeholder);
}

void SynchronizeOsIntegration(Profile* profile,
                              const webapps::AppId& app_id,
                              std::optional<SynchronizeOsOptions> options) {
  base::test::TestFuture<void> sync_future;
  WebAppProvider::GetForTest(profile)->scheduler().SynchronizeOsIntegration(
      app_id, sync_future.GetCallback(), options);
  EXPECT_TRUE(sync_future.Wait());
}
std::vector<web_package::SignedWebBundleSignatureInfo> CreateSignatures() {
  std::vector<web_package::SignedWebBundleSignatureInfo> signatures;

  signatures.push_back(CreateSignatureInfo(
      web_package::SignedWebBundleId::Type::kEcdsaP256PublicKey));

  signatures.push_back(CreateSignatureInfo(
      web_package::SignedWebBundleId::Type::kEd25519PublicKey));

  signatures.push_back(web_package::SignedWebBundleSignatureInfoUnknown());

  return signatures;
}

gfx::Image LoadTestImageFromDisk(const base::FilePath& file_path,
                                 bool read_from_test_dir) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  // Create the `image_path` from `file_path` if the path is an absolute path,
  // else construct from the test data directory.
  base::FilePath image_path =
      read_from_test_dir
          ? base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT)
                .Append(file_path)
          : file_path;
  std::optional<std::vector<uint8_t>> png_data =
      base::ReadFileToBytes(image_path);
  CHECK(png_data.has_value());
  return gfx::Image::CreateFrom1xPNGBytes(base::as_byte_span(*png_data));
}

}  // namespace web_app::test
