// 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.

#import <memory>
#import <optional>
#import <string>

#import "base/json/json_reader.h"
#import "base/strings/string_util.h"
#import "base/strings/sys_string_conversions.h"
#import "components/enterprise/browser/enterprise_switches.h"
#import "components/policy/core/common/policy_loader_ios_constants.h"
#import "components/policy/policy_constants.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/policy/model/policy_app_interface.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

// Returns the value of a given policy, looked up in the current platform policy
// provider.
std::optional<base::Value> GetPlatformPolicy(const std::string& key) {
  std::string json_representation = base::SysNSStringToUTF8(
      [PolicyAppInterface valueForPlatformPolicy:base::SysUTF8ToNSString(key)]);
  return base::JSONReader::Read(json_representation,
                                base::JSON_PARSE_CHROMIUM_EXTENSIONS);
}

// Returns an AppLaunchConfiguration containing the given policy data.
// `policy_data` must be in XML format. `policy_data` is passed to the
// application regardless of whether `disable_policy` is true or false..
AppLaunchConfiguration GenerateAppLaunchConfiguration(std::string policy_data,
                                                      bool enable_cbcm) {
  AppLaunchConfiguration config;
  if (enable_cbcm) {
    config.additional_args.push_back(
        std::string("--") + switches::kEnableChromeBrowserCloudManagement);
  }

  // Remove whitespace from the policy data, because the XML parser does not
  // tolerate newlines.
  base::RemoveChars(policy_data, base::kWhitespaceASCII, &policy_data);

  // Commandline flags that start with a single "-" are automatically added to
  // the NSArgumentDomain in NSUserDefaults. Set fake policy data that can be
  // read by the production platform policy provider.
  config.additional_args.push_back(
      "-" + base::SysNSStringToUTF8(kPolicyLoaderIOSConfigurationKey));
  config.additional_args.push_back(policy_data);

  config.relaunch_policy = NoForceRelaunchAndResetState;
  return config;
}

}  // namespace

// Test case that uses the production platform policy provider.
@interface PolicyPlatformProviderTestCase : ChromeTestCase
@end

@implementation PolicyPlatformProviderTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  std::string policyData = "<dict>"
                           "    <key>EnableExperimentalPolicies</key>"
                           "    <array>"
                           "      <string>DefaultSearchProviderName</string>"
                           "      <string>SearchSuggestEnabled</string>"
                           "    </array>"
                           "    <key>DefaultSearchProviderName</key>"
                           "    <string>Test</string>"
                           "    <key>NotARegisteredPolicy</key>"
                           "    <string>Unknown</string>"
                           "    <key>SavingBrowserHistoryDisabled</key>"
                           "    <true/>"
                           "    <key>SearchSuggestEnabled</key>"
                           "    <false/>"
                           "</dict>";
  return GenerateAppLaunchConfiguration(policyData,
                                        /*enable_cbcm*/ false);
}

// Tests the values of policies that were explicitly set.
- (void)testPolicyExplicitlySet {
  std::optional<base::Value> searchValue =
      GetPlatformPolicy(policy::key::kDefaultSearchProviderName);
  GREYAssertTrue(searchValue && searchValue->is_string(),
                 @"searchValue was not of type string");
  GREYAssertEqual(searchValue->GetString(), std::string{"Test"},
                  @"searchValue had an unexpected value");

  std::optional<base::Value> suggestValue =
      GetPlatformPolicy(policy::key::kSearchSuggestEnabled);
  GREYAssertTrue(suggestValue && suggestValue->is_bool(),
                 @"suggestValue was not of type bool");
  GREYAssertFalse(suggestValue->GetBool(),
                  @"suggestValue had an unexpected value");
}

// Test the value of a policy that exists in the schema but was not explicitly
// set.
- (void)testPolicyNotSet {
  std::optional<base::Value> blocklistValue =
      GetPlatformPolicy(policy::key::kURLBlocklist);
  GREYAssertTrue(blocklistValue && blocklistValue->is_none(),
                 @"blocklistValue was unexpectedly present");
}

// Test the value of a policy that was set in the configuration but is unknown
// to the policy system.
- (void)testPolicyUnknown {
  std::optional<base::Value> unknownValue =
      GetPlatformPolicy("NotARegisteredPolicy");
  GREYAssertTrue(unknownValue && unknownValue->is_string(),
                 @"unknownValue was not of type string");
  GREYAssertEqual(unknownValue->GetString(), std::string{"Unknown"},
                  @"unknownValue had an unexpected value");
}

@end

// Test case that enables CBCM.
@interface CBCMEnabledTestCase : ChromeTestCase
@end

@implementation CBCMEnabledTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  std::string policyData = "<dict>"
                           "    <key>SearchSuggestEnabled</key>"
                           "    <false/>"
                           "</dict>";
  return GenerateAppLaunchConfiguration(policyData,
                                        /*enable_cbcm=*/true);
}

// Ensure that policies can still be correctly set, and that the browser is
// working normally by visiting the about:policy page.
- (void)testPoliciesWork {
  std::optional<base::Value> suggestValue =
      GetPlatformPolicy(policy::key::kSearchSuggestEnabled);
  GREYAssertTrue(suggestValue && suggestValue->is_bool(),
                 @"suggestValue was not of type bool");
  GREYAssertFalse(suggestValue->GetBool(),
                  @"suggestValue had an unexpected value");

  [ChromeEarlGrey loadURL:GURL(kChromeUIPolicyURL)];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_POLICY_SHOW_UNSET)];
}

@end

// Test case that disables CBCM.
@interface CBCMDisabledTestCase : ChromeTestCase
@end

@implementation CBCMDisabledTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  std::string policyData = "<dict>"
                           "    <key>SearchSuggestEnabled</key>"
                           "    <false/>"
                           "</dict>";
  return GenerateAppLaunchConfiguration(policyData,
                                        /*enable_cbcm=*/false);
}

// Ensure that policies can still be correctly set, and that the browser is
// working normally by visiting the about:policy page.
- (void)testPoliciesWork {
  std::optional<base::Value> suggestValue =
      GetPlatformPolicy(policy::key::kSearchSuggestEnabled);
  GREYAssertTrue(suggestValue && suggestValue->is_bool(),
                 @"suggestValue was not of type bool");
  GREYAssertFalse(suggestValue->GetBool(),
                  @"suggestValue had an unexpected value");

  [ChromeEarlGrey loadURL:GURL(kChromeUIPolicyURL)];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_POLICY_SHOW_UNSET)];
}

@end
