// 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/services/app_service/public/cpp/app.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/time/time.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/intent_filter.h"
#include "components/services/app_service/public/cpp/permission.h"
#include "components/services/app_service/public/cpp/run_on_os_login_types.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace apps {

namespace {

constexpr AppType kAppType = AppType::kArc;
constexpr char kAppId[] = "abc";

template <typename T>
std::pair<T, T> TestValue();

template <>
std::pair<std::string, std::string> TestValue() {
  return {"apple", "banana"};
}

template <>
std::pair<bool, bool> TestValue() {
  return {true, false};
}

template <>
std::pair<base::Time, base::Time> TestValue() {
  return {base::Time::FromSecondsSinceUnixEpoch(1000),
          base::Time::FromSecondsSinceUnixEpoch(2000)};
}

template <>
std::pair<uint64_t, uint64_t> TestValue() {
  return {100, 200};
}

template <>
std::pair<base::DictValue, base::DictValue> TestValue() {
  base::DictValue dict1;
  dict1.Set("vm_name", "vm_name_value");
  base::DictValue dict2;
  dict2.Set("container_name", "container_name_value");
  return {std::move(dict1), std::move(dict2)};
}

bool IsEqual(AppPtr app1, AppPtr app2) {
  std::vector<AppPtr> apps1;
  apps1.push_back(std::move(app1));

  std::vector<AppPtr> apps2;
  apps2.push_back(std::move(app2));

  return IsEqual(apps1, apps2);
}

template <typename T>
void VerifyOptionalValue(std::optional<T> App::*field) {
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1.get()->*field = TestValue<T>().first;
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app2.get()->*field = TestValue<T>().first;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1.get()->*field = TestValue<T>().first;
    app2.get()->*field = TestValue<T>().second;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

}  // namespace

using AppTest = testing::Test;

TEST_F(AppTest, EmptyAppsIsEqual) {
  std::vector<AppPtr> apps1;
  std::vector<AppPtr> apps2;
  EXPECT_TRUE(IsEqual(apps1, apps2));
}

TEST_F(AppTest, VerifyAppsIsEqualForEmptyOptionalValues) {
  AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
  AppPtr app2 = app1->Clone();
  EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
}

TEST_F(AppTest, VerifyAppsIsEqualForReadiness) {
  // Verify the app is equal with the same `readiness`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->readiness = Readiness::kReady;
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `readiness`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->readiness = Readiness::kReady;
    AppPtr app2 = app1->Clone();
    app2->readiness = Readiness::kUninstalledByUser;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForName) {
  VerifyOptionalValue(&App::name);
}

TEST_F(AppTest, VerifyAppsIsEqualForShortName) {
  VerifyOptionalValue(&App::short_name);
}

TEST_F(AppTest, VerifyAppsIsEqualForPublisherId) {
  VerifyOptionalValue(&App::publisher_id);
}

TEST_F(AppTest, VerifyAppsIsEqualForDescription) {
  VerifyOptionalValue(&App::description);
}

TEST_F(AppTest, VerifyAppsIsEqualForVersion) {
  VerifyOptionalValue(&App::version);
}

TEST_F(AppTest, VerifyAppsIsEqualForAdditionalSearchTerms) {
  // Verify the app is equal with the same `additional_search_terms`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->additional_search_terms = {"aaa"};
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `additional_search_terms`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->additional_search_terms = {"aaa"};
    app2->additional_search_terms = {"bbb"};
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForIconKey) {
  // Verify the app is equal with the same `icon_key`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->icon_key = IconKey();
    app1->icon_key->update_version = 100;
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with an empty `icon_key` vs non-empty
  // `icon_key`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app2->icon_key = IconKey();
    app2->icon_key->update_version = 100;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `icon_key`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->icon_key = IconKey();
    app1->icon_key->update_version = 100;
    app2->icon_key = IconKey();
    app2->icon_key->update_version = 200;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForLastLaunchTime) {
  VerifyOptionalValue(&App::last_launch_time);
}

TEST_F(AppTest, VerifyAppsIsEqualForInstallTime) {
  VerifyOptionalValue(&App::install_time);
}

TEST_F(AppTest, VerifyAppsIsEqualForPermissions) {
  // Verify the app is equal with the same `permissions`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->permissions.push_back(std::make_unique<Permission>(
        PermissionType::kLocation, TriState::kAllow, /*is_managed=*/true));
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `permissions`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->permissions.push_back(std::make_unique<Permission>(
        PermissionType::kLocation, TriState::kAllow, /*is_managed=*/true));
    app2->permissions.push_back(std::make_unique<Permission>(
        PermissionType::kLocation, TriState::kAllow, /*is_managed=*/false));
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForInstallReason) {
  // Verify the app is equal with the same `install_reason`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->install_reason = InstallReason::kUser;
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `install_reason`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->install_reason = InstallReason::kUser;
    app2->install_reason = InstallReason::kSystem;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForInstallSource) {
  // Verify the app is equal with the same `install_source`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->install_source = InstallSource::kPlayStore;
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `install_source`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->install_source = InstallSource::kPlayStore;
    app2->install_source = InstallSource::kBrowser;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForPolicyIds) {
  // Verify the app is equal with the same `policy_ids`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->policy_ids = {"policy1"};
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `install_source`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->policy_ids = {"policy1"};
    app2->policy_ids = {"policy2"};
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForIsPlatformApp) {
  VerifyOptionalValue(&App::is_platform_app);
}

TEST_F(AppTest, VerifyAppsIsEqualForRecommendable) {
  VerifyOptionalValue(&App::recommendable);
}

TEST_F(AppTest, VerifyAppsIsEqualForSearchable) {
  VerifyOptionalValue(&App::searchable);
}

TEST_F(AppTest, VerifyAppsIsEqualForShowInLauncher) {
  VerifyOptionalValue(&App::show_in_launcher);
}

TEST_F(AppTest, VerifyAppsIsEqualForShowInShelf) {
  VerifyOptionalValue(&App::show_in_shelf);
}

TEST_F(AppTest, VerifyAppsIsEqualForShowInSearch) {
  VerifyOptionalValue(&App::show_in_search);
}

TEST_F(AppTest, VerifyAppsIsEqualForShowInManagement) {
  VerifyOptionalValue(&App::show_in_management);
}

TEST_F(AppTest, VerifyAppsIsEqualForHandlesIntents) {
  VerifyOptionalValue(&App::handles_intents);
}

TEST_F(AppTest, VerifyAppsIsEqualForAllowUninstall) {
  VerifyOptionalValue(&App::allow_uninstall);
}

TEST_F(AppTest, VerifyAppsIsEqualForHasBadge) {
  VerifyOptionalValue(&App::has_badge);
}

TEST_F(AppTest, VerifyAppsIsEqualForPaused) {
  VerifyOptionalValue(&App::paused);
}

TEST_F(AppTest, VerifyAppsIsEqualForIntentFilters) {
  IntentFilterPtr intent_filter1 = std::make_unique<IntentFilter>();
  intent_filter1->activity_name = "abc";

  IntentFilterPtr intent_filter2 = std::make_unique<IntentFilter>();
  intent_filter2->activity_name = "xyz";

  // Verify the app is equal with the same `intent_filter`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->intent_filters.emplace().push_back(intent_filter1->Clone());
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `intent_filter`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->intent_filters.emplace().push_back(intent_filter1->Clone());
    app2->intent_filters.emplace().push_back(intent_filter2->Clone());
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is equal with null intent filters.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is equal with zero and null intent filters.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app2->intent_filters.emplace();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForResizeLocked) {
  VerifyOptionalValue(&App::resize_locked);
}

TEST_F(AppTest, VerifyAppsIsEqualForWindowMode) {
  // Verify the app is equal with the same `window_mode`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->window_mode = WindowMode::kBrowser;
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `window_mode`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->window_mode = WindowMode::kBrowser;
    app2->window_mode = WindowMode::kTabbedWindow;
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForRunOnOsLogin) {
  // Verify the app is equal with the same `run_on_os_login`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->run_on_os_login = RunOnOsLogin(RunOnOsLoginMode::kNotRun, false);
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with an empty `run_on_os_login` vs non-empty
  // `run_on_os_login`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app2->run_on_os_login = RunOnOsLogin(RunOnOsLoginMode::kNotRun, false);
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `run_on_os_login`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->run_on_os_login = RunOnOsLogin(RunOnOsLoginMode::kNotRun, false);
    app2->run_on_os_login = RunOnOsLogin(RunOnOsLoginMode::kNotRun, true);
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForAppSizeInBytes) {
  VerifyOptionalValue(&App::app_size_in_bytes);
}

TEST_F(AppTest, VerifyAppsIsEqualForDataSizeInBytes) {
  VerifyOptionalValue(&App::data_size_in_bytes);
}

TEST_F(AppTest, VerifyAppsIsEqualForSupportedLocales) {
  // Verify the app is equal with the same `supported_locales`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    app1->supported_locales = {"C"};
    AppPtr app2 = app1->Clone();
    EXPECT_TRUE(IsEqual(std::move(app1), std::move(app2)));
  }

  // Verify the app is not equal with different `supported_locales`.
  {
    AppPtr app1 = std::make_unique<App>(kAppType, kAppId);
    AppPtr app2 = app1->Clone();
    app1->supported_locales = {"C"};
    app2->supported_locales = {"B"};
    EXPECT_FALSE(IsEqual(std::move(app1), std::move(app2)));
  }
}

TEST_F(AppTest, VerifyAppsIsEqualForSelectedLocale) {
  VerifyOptionalValue(&App::selected_locale);
}

TEST_F(AppTest, VerifyAppsIsEqualForExtra) {
  VerifyOptionalValue(&App::extra);
}

}  // namespace apps
