// 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 "ios/chrome/browser/policy/model/policy_app_interface.h"

#import <memory>
#import <optional>

#import "base/files/file_path.h"
#import "base/files/file_util.h"
#import "base/json/json_reader.h"
#import "base/json/json_writer.h"
#import "base/path_service.h"
#import "base/strings/sys_string_conversions.h"
#import "base/task/thread_pool.h"
#import "base/test/ios/wait_util.h"
#import "base/threading/scoped_blocking_call.h"
#import "base/values.h"
#import "components/policy/core/browser/browser_policy_connector.h"
#import "components/policy/core/browser/url_list/policy_blocklist_service.h"
#import "components/policy/core/browser/url_list/url_blocklist_manager.h"
#import "components/policy/core/common/cloud/cloud_policy_client.h"
#import "components/policy/core/common/cloud/cloud_policy_core.h"
#import "components/policy/core/common/cloud/cloud_policy_store.h"
#import "components/policy/core/common/cloud/device_management_service.h"
#import "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
#import "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#import "components/policy/core/common/configuration_policy_provider.h"
#import "components/policy/core/common/policy_bundle.h"
#import "components/policy/core/common/policy_loader_ios_constants.h"
#import "components/policy/core/common/policy_map.h"
#import "components/policy/core/common/policy_namespace.h"
#import "components/policy/core/common/policy_types.h"
#import "components/policy/policy_constants.h"
#import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h"
#import "ios/chrome/browser/policy/model/test_platform_policy_provider.h"
#import "ios/chrome/browser/policy_url_blocking/model/policy_url_blocking_service_factory.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/paths/paths.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/test/app/chrome_test_util.h"

namespace {

// Directory where device management token is stored. This value is from
// "ios/chrome/browser/policy/model/browser_dm_token_storage_ios.mm"
const char kDmTokenBaseDir[] =
    FILE_PATH_LITERAL("Google/Chrome Cloud Enrollment");

// Directory where cloud policy are stored. This value is from
// "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.cc"
const base::FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Policy");

// Returns a JSON-encoded string representing the given `base::Value`. If
// `value` is nullptr, returns a string representing a `base::Value` of type
// NONE.
NSString* SerializeValue(const base::Value* value) {
  if (!value) {
    // The representation for base::Value::Type::NONE, according to the
    // JSON spec at https://www.json.org/json-en.html
    return @"null";
  }

  const std::optional<std::string> json_string = base::WriteJson(*value);
  return base::SysUTF8ToNSString(json_string.value_or(std::string()));
}

// Takes a JSON-encoded string representing a `base::Value`, and deserializes
// into a `base::Value` pointer. If nullptr is given, returns a pointer to a
// `base::Value` of type NONE.
std::optional<base::Value> DeserializeValue(NSString* json_value) {
  if (!json_value.length) {
    return base::Value();
  }

  return base::JSONReader::Read(base::SysNSStringToUTF8(json_value),
                                base::JSON_PARSE_CHROMIUM_EXTENSIONS);
}

}  // namespace

@implementation PolicyAppInterface

+ (NSString*)valueForPlatformPolicy:(NSString*)policyKey {
  const std::string key = base::SysNSStringToUTF8(policyKey);

  BrowserPolicyConnectorIOS* connector =
      GetApplicationContext()->GetBrowserPolicyConnector();
  if (!connector) {
    return SerializeValue(nullptr);
  }

  const policy::ConfigurationPolicyProvider* platformProvider =
      connector->GetPlatformProvider();
  if (!platformProvider) {
    return SerializeValue(nullptr);
  }

  const policy::PolicyBundle& policyBundle = platformProvider->policies();
  const policy::PolicyMap& policyMap = policyBundle.Get(
      policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, ""));
  // `GetValueUnsafe` is used due to multiple policy types being handled.
  return SerializeValue(policyMap.GetValueUnsafe(key));
}

+ (void)setPolicyValue:(NSString*)jsonValue forKey:(NSString*)policyKey {
  std::optional<base::Value> value = DeserializeValue(jsonValue);
  policy::PolicyMap values;
  values.Set(base::SysNSStringToUTF8(policyKey), policy::POLICY_LEVEL_MANDATORY,
             policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
             std::move(value), /*external_data_fetcher=*/nullptr);
  GetTestPlatformPolicyProvider()->UpdateChromePolicy(values);
}

+ (void)mergePolicyValue:(NSString*)jsonValue forKey:(NSString*)policyKey {
  // Get the policy bundle.
  policy::MockConfigurationPolicyProvider* platformProvider =
      GetTestPlatformPolicyProvider();
  policy::PolicyBundle mutablePolicyBundle;
  mutablePolicyBundle.MergeFrom(platformProvider->policies());
  // Get the policy map.
  policy::PolicyNamespace chromePolicyNamespace(
      policy::PolicyDomain::POLICY_DOMAIN_CHROME, std::string());
  policy::PolicyMap& chromePolicyMap =
      mutablePolicyBundle.Get(chromePolicyNamespace);
  // Add the value.
  std::optional<base::Value> value = DeserializeValue(jsonValue);
  chromePolicyMap.Set(
      base::SysNSStringToUTF8(policyKey), policy::POLICY_LEVEL_MANDATORY,
      policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
      std::move(value), /*external_data_fetcher=*/nullptr);
  // Update the policy.
  platformProvider->UpdatePolicy(std::move(mutablePolicyBundle));
}

+ (void)clearPolicies {
  policy::PolicyMap values;
  GetTestPlatformPolicyProvider()->UpdateChromePolicy(values);
}

+ (void)clearAllPoliciesInNSUserDefault {
  [[NSUserDefaults standardUserDefaults]
      removeObjectForKey:kPolicyLoaderIOSConfigurationKey];
}

+ (BOOL)isURLBlocked:(NSString*)URL {
  return [self isURLBlocked:URL inIncognito:NO];
}

+ (BOOL)isURLBlocked:(NSString*)URL inIncognito:(BOOL)incognito {
  GURL gurl = GURL(base::SysNSStringToUTF8(URL));
  ProfileIOS* profile = incognito
                            ? chrome_test_util::GetCurrentIncognitoProfile()
                            : chrome_test_util::GetOriginalProfile();
  if (!profile) {
    return NO;
  }
  PolicyBlocklistService* service =
      PolicyBlocklistServiceFactory::GetForProfile(profile);
  return service->GetURLBlocklistState(gurl) ==
         policy::URLBlocklist::URLBlocklistState::URL_IN_BLOCKLIST;
}

+ (void)setBrowserCloudPolicyDataWithDomain:(NSString*)domain {
  policy::MachineLevelUserCloudPolicyManager* manager =
      GetApplicationContext()
          ->GetBrowserPolicyConnector()
          ->machine_level_user_cloud_policy_manager();
  DCHECK(manager);

  policy::CloudPolicyStore* store = manager->core()->store();
  DCHECK(store);

  auto policy_data = std::make_unique<enterprise_management::PolicyData>();
  policy_data->set_managed_by(base::SysNSStringToUTF8(domain));
  store->set_policy_data_for_testing(std::move(policy_data));
}

+ (void)setUserCloudPolicyDataWithDomain:(NSString*)domain {
  policy::UserCloudPolicyManager* manager =
      chrome_test_util::GetOriginalProfile()->GetUserCloudPolicyManager();
  DCHECK(manager);

  policy::CloudPolicyStore* store = manager->core()->store();
  DCHECK(store);

  auto policy_data = std::make_unique<enterprise_management::PolicyData>();
  policy_data->set_managed_by(base::SysNSStringToUTF8(domain));
  store->set_policy_data_for_testing(std::move(policy_data));
}

+ (BOOL)clearDMTokenDirectory {
  base::FilePath appDataDirPath;
  base::PathService::Get(base::DIR_APP_DATA, &appDataDirPath);
  base::FilePath dmTokenDirPath = appDataDirPath.Append(kDmTokenBaseDir);

  __block BOOL didComplete = NO;
  base::ThreadPool::PostTaskAndReply(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
      base::BindOnce(^{
        base::DeletePathRecursively(dmTokenDirPath);
      }),
      base::BindOnce(^{
        didComplete = YES;
      }));

  return base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^{
        return didComplete;
      });
}

+ (BOOL)isCloudPolicyClientRegistered {
  return GetApplicationContext()
      ->GetBrowserPolicyConnector()
      ->machine_level_user_cloud_policy_manager()
      ->core()
      ->client()
      ->is_registered();
}

+ (BOOL)clearCloudPolicyDirectory {
  base::FilePath userDataDir;
  base::PathService::Get(ios::DIR_USER_DATA, &userDataDir);
  base::FilePath policyDir = userDataDir.Append(kPolicyDir);

  __block BOOL didComplete = NO;
  base::ThreadPool::PostTaskAndReply(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
      base::BindOnce(^{
        base::DeletePathRecursively(policyDir);
      }),
      base::BindOnce(^{
        didComplete = YES;
      }));

  return base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^{
        return didComplete;
      });
}

+ (BOOL)hasUserPolicyDataInCurrentProfile {
  policy::UserCloudPolicyManager* manager =
      chrome_test_util::GetOriginalProfile()->GetUserCloudPolicyManager();
  DCHECK(manager);

  policy::CloudPolicyStore* store = manager->core()->store();
  DCHECK(store);

  return store->has_policy() && store->is_managed();
}

+ (BOOL)hasUserPolicyInCurrentProfile:(NSString*)policyName
                     withIntegerValue:(int)expectedValue {
  policy::UserCloudPolicyManager* manager =
      chrome_test_util::GetOriginalProfile()->GetUserCloudPolicyManager();
  DCHECK(manager);

  policy::CloudPolicyStore* store = manager->core()->store();
  DCHECK(store);

  const base::Value* value = store->policy_map().GetValue(
      base::SysNSStringToUTF8(policyName), base::Value::Type::INTEGER);

  return value && value->GetInt() == expectedValue;
}

@end
