// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/prefs/testing_pref_store.h"
#include "base/strings/string_util.h"
#include "chrome/browser/supervised_user/supervised_user_settings_service.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "sync/api/fake_sync_change_processor.h"
#include "sync/api/sync_change.h"
#include "sync/api/sync_change_processor_wrapper_for_test.h"
#include "sync/api/sync_error_factory_mock.h"
#include "sync/protocol/sync.pb.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

class MockSyncErrorFactory : public syncer::SyncErrorFactory {
 public:
  explicit MockSyncErrorFactory(syncer::ModelType type);
  ~MockSyncErrorFactory() override;

  // SyncErrorFactory implementation:
  syncer::SyncError CreateAndUploadError(
      const tracked_objects::Location& location,
      const std::string& message) override;

 private:
  syncer::ModelType type_;

  DISALLOW_COPY_AND_ASSIGN(MockSyncErrorFactory);
};

MockSyncErrorFactory::MockSyncErrorFactory(syncer::ModelType type)
    : type_(type) {}

MockSyncErrorFactory::~MockSyncErrorFactory() {}

syncer::SyncError MockSyncErrorFactory::CreateAndUploadError(
    const tracked_objects::Location& location,
    const std::string& message) {
  return syncer::SyncError(location,
                           syncer::SyncError::DATATYPE_ERROR,
                           message,
                           type_);
}

}  // namespace

const char kAtomicItemName[] = "X-Wombat";
const char kSettingsName[] = "TestingSetting";
const char kSettingsValue[] = "SettingsValue";
const char kSplitItemName[] = "X-SuperMoosePowers";

class SupervisedUserSettingsServiceTest : public ::testing::Test {
 protected:
  SupervisedUserSettingsServiceTest() {}
  ~SupervisedUserSettingsServiceTest() override {}

  scoped_ptr<syncer::SyncChangeProcessor> CreateSyncProcessor() {
    sync_processor_.reset(new syncer::FakeSyncChangeProcessor);
    return scoped_ptr<syncer::SyncChangeProcessor>(
        new syncer::SyncChangeProcessorWrapperForTest(sync_processor_.get()));
  }

  void StartSyncing(const syncer::SyncDataList& initial_sync_data) {
    scoped_ptr<syncer::SyncErrorFactory> error_handler(
        new MockSyncErrorFactory(syncer::SUPERVISED_USER_SETTINGS));
    syncer::SyncMergeResult result = settings_service_.MergeDataAndStartSyncing(
        syncer::SUPERVISED_USER_SETTINGS,
        initial_sync_data,
        CreateSyncProcessor(),
        error_handler.Pass());
    EXPECT_FALSE(result.error().IsSet());
  }

  void UploadSplitItem(const std::string& key, const std::string& value) {
    split_items_.SetStringWithoutPathExpansion(key, value);
    settings_service_.UploadItem(
        SupervisedUserSettingsService::MakeSplitSettingKey(kSplitItemName,
                                                           key),
        scoped_ptr<base::Value>(new base::StringValue(value)));
  }

  void UploadAtomicItem(const std::string& value) {
    atomic_setting_value_.reset(new base::StringValue(value));
    settings_service_.UploadItem(
        kAtomicItemName,
        scoped_ptr<base::Value>(new base::StringValue(value)));
  }

  void VerifySyncDataItem(syncer::SyncData sync_data) {
    const sync_pb::ManagedUserSettingSpecifics& supervised_user_setting =
        sync_data.GetSpecifics().managed_user_setting();
    base::Value* expected_value = NULL;
    if (supervised_user_setting.name() == kAtomicItemName) {
      expected_value = atomic_setting_value_.get();
    } else {
      EXPECT_TRUE(base::StartsWithASCII(supervised_user_setting.name(),
                                        std::string(kSplitItemName) + ':',
                                        true));
      std::string key =
          supervised_user_setting.name().substr(strlen(kSplitItemName) + 1);
      EXPECT_TRUE(split_items_.GetWithoutPathExpansion(key, &expected_value));
    }

    scoped_ptr<base::Value> value(
        base::JSONReader::DeprecatedRead(supervised_user_setting.value()));
    EXPECT_TRUE(expected_value->Equals(value.get()));
  }

  void OnNewSettingsAvailable(const base::DictionaryValue* settings) {
    if (!settings)
      settings_.reset();
    else
      settings_.reset(settings->DeepCopy());
  }

  // testing::Test overrides:
  void SetUp() override {
    TestingPrefStore* pref_store = new TestingPrefStore;
    settings_service_.Init(pref_store);
    settings_service_.Subscribe(
        base::Bind(&SupervisedUserSettingsServiceTest::OnNewSettingsAvailable,
                   base::Unretained(this)));
    pref_store->SetInitializationCompleted();
    ASSERT_FALSE(settings_);
    settings_service_.SetActive(true);
    ASSERT_TRUE(settings_);
  }

  void TearDown() override { settings_service_.Shutdown(); }

  content::TestBrowserThreadBundle thread_bundle_;
  base::DictionaryValue split_items_;
  scoped_ptr<base::Value> atomic_setting_value_;
  SupervisedUserSettingsService settings_service_;
  scoped_ptr<base::DictionaryValue> settings_;

  scoped_ptr<syncer::FakeSyncChangeProcessor> sync_processor_;
};

TEST_F(SupervisedUserSettingsServiceTest, ProcessAtomicSetting) {
  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(settings_);
  const base::Value* value = NULL;
  EXPECT_FALSE(settings_->GetWithoutPathExpansion(kSettingsName, &value));

  settings_.reset();
  syncer::SyncData data =
      SupervisedUserSettingsService::CreateSyncDataForSetting(
          kSettingsName, base::StringValue(kSettingsValue));
  syncer::SyncChangeList change_list;
  change_list.push_back(
      syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, data));
  syncer::SyncError error =
      settings_service_.ProcessSyncChanges(FROM_HERE, change_list);
  EXPECT_FALSE(error.IsSet()) << error.ToString();
  ASSERT_TRUE(settings_);
  ASSERT_TRUE(settings_->GetWithoutPathExpansion(kSettingsName, &value));
  std::string string_value;
  EXPECT_TRUE(value->GetAsString(&string_value));
  EXPECT_EQ(kSettingsValue, string_value);
}

TEST_F(SupervisedUserSettingsServiceTest, ProcessSplitSetting) {
  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(settings_);
  const base::Value* value = NULL;
  EXPECT_FALSE(settings_->GetWithoutPathExpansion(kSettingsName, &value));

  base::DictionaryValue dict;
  dict.SetString("foo", "bar");
  dict.SetBoolean("awesomesauce", true);
  dict.SetInteger("eaudecologne", 4711);

  settings_.reset();
  syncer::SyncChangeList change_list;
  for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
    syncer::SyncData data =
        SupervisedUserSettingsService::CreateSyncDataForSetting(
            SupervisedUserSettingsService::MakeSplitSettingKey(kSettingsName,
                                                               it.key()),
            it.value());
    change_list.push_back(
        syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, data));
  }
  syncer::SyncError error =
      settings_service_.ProcessSyncChanges(FROM_HERE, change_list);
  EXPECT_FALSE(error.IsSet()) << error.ToString();
  ASSERT_TRUE(settings_);
  ASSERT_TRUE(settings_->GetWithoutPathExpansion(kSettingsName, &value));
  const base::DictionaryValue* dict_value = NULL;
  ASSERT_TRUE(value->GetAsDictionary(&dict_value));
  EXPECT_TRUE(dict_value->Equals(&dict));
}

TEST_F(SupervisedUserSettingsServiceTest, SetLocalSetting) {
  const base::Value* value = NULL;
  EXPECT_FALSE(settings_->GetWithoutPathExpansion(kSettingsName, &value));

  settings_.reset();
  settings_service_.SetLocalSetting(
      kSettingsName,
      scoped_ptr<base::Value>(new base::StringValue(kSettingsValue)));
  ASSERT_TRUE(settings_);
  ASSERT_TRUE(settings_->GetWithoutPathExpansion(kSettingsName, &value));
  std::string string_value;
  EXPECT_TRUE(value->GetAsString(&string_value));
  EXPECT_EQ(kSettingsValue, string_value);
}

TEST_F(SupervisedUserSettingsServiceTest, UploadItem) {
  UploadSplitItem("foo", "bar");
  UploadSplitItem("blurp", "baz");
  UploadAtomicItem("hurdle");

  // Uploading should produce changes when we start syncing.
  StartSyncing(syncer::SyncDataList());
  ASSERT_EQ(3u, sync_processor_->changes().size());
  for (const syncer::SyncChange& sync_change : sync_processor_->changes()) {
    ASSERT_TRUE(sync_change.IsValid());
    EXPECT_EQ(syncer::SyncChange::ACTION_ADD, sync_change.change_type());
    VerifySyncDataItem(sync_change.sync_data());
  }

  // It should also show up in local Sync data.
  syncer::SyncDataList sync_data =
      settings_service_.GetAllSyncData(syncer::SUPERVISED_USER_SETTINGS);
  EXPECT_EQ(3u, sync_data.size());
  for (const syncer::SyncData& sync_data_item : sync_data)
    VerifySyncDataItem(sync_data_item);

  // Uploading after we have started syncing should work too.
  sync_processor_->changes().clear();
  UploadSplitItem("froodle", "narf");
  ASSERT_EQ(1u, sync_processor_->changes().size());
  syncer::SyncChange change = sync_processor_->changes()[0];
  ASSERT_TRUE(change.IsValid());
  EXPECT_EQ(syncer::SyncChange::ACTION_ADD, change.change_type());
  VerifySyncDataItem(change.sync_data());

  sync_data = settings_service_.GetAllSyncData(
      syncer::SUPERVISED_USER_SETTINGS);
  EXPECT_EQ(4u, sync_data.size());
  for (const syncer::SyncData& sync_data_item : sync_data)
    VerifySyncDataItem(sync_data_item);

  // Uploading an item with a previously seen key should create an UPDATE
  // action.
  sync_processor_->changes().clear();
  UploadSplitItem("blurp", "snarl");
  ASSERT_EQ(1u, sync_processor_->changes().size());
  change = sync_processor_->changes()[0];
  ASSERT_TRUE(change.IsValid());
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, change.change_type());
  VerifySyncDataItem(change.sync_data());

  sync_data = settings_service_.GetAllSyncData(
      syncer::SUPERVISED_USER_SETTINGS);
  EXPECT_EQ(4u, sync_data.size());
  for (const syncer::SyncData& sync_data_item : sync_data)
    VerifySyncDataItem(sync_data_item);

  sync_processor_->changes().clear();
  UploadAtomicItem("fjord");
  ASSERT_EQ(1u, sync_processor_->changes().size());
  change = sync_processor_->changes()[0];
  ASSERT_TRUE(change.IsValid());
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, change.change_type());
  VerifySyncDataItem(change.sync_data());

  sync_data = settings_service_.GetAllSyncData(
      syncer::SUPERVISED_USER_SETTINGS);
  EXPECT_EQ(4u, sync_data.size());
  for (const syncer::SyncData& sync_data_item : sync_data)
    VerifySyncDataItem(sync_data_item);

  // The uploaded items should not show up as settings.
  const base::Value* value = NULL;
  EXPECT_FALSE(settings_->GetWithoutPathExpansion(kAtomicItemName, &value));
  EXPECT_FALSE(settings_->GetWithoutPathExpansion(kSplitItemName, &value));

  // Restarting sync should not create any new changes.
  settings_service_.StopSyncing(syncer::SUPERVISED_USER_SETTINGS);
  StartSyncing(sync_data);
  ASSERT_EQ(0u, sync_processor_->changes().size());
}