// Copyright 2015 The Weave 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 "src/config.h"

#include <set>

#include <base/bind.h>
#include <base/guid.h>
#include <base/json/json_reader.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/values.h>
#include <weave/enum_to_string.h>

#include "src/data_encoding.h"
#include "src/privet/privet_types.h"
#include "src/string_utils.h"
#include "src/bind_lambda.h"

namespace weave {

const char kConfigName[] = "config";

namespace config_keys {

const char kVersion[] = "version";

const char kClientId[] = "client_id";
const char kClientSecret[] = "client_secret";
const char kApiKey[] = "api_key";
const char kOAuthURL[] = "oauth_url";
const char kServiceURL[] = "service_url";
const char kXmppEndpoint[] = "xmpp_endpoint";
const char kName[] = "name";
const char kDescription[] = "description";
const char kLocation[] = "location";
const char kLocalAnonymousAccessRole[] = "local_anonymous_access_role";
const char kLocalDiscoveryEnabled[] = "local_discovery_enabled";
const char kLocalPairingEnabled[] = "local_pairing_enabled";
const char kRefreshToken[] = "refresh_token";
const char kCloudId[] = "cloud_id";
const char kDeviceId[] = "device_id";
const char kRobotAccount[] = "robot_account";
const char kLastConfiguredSsid[] = "last_configured_ssid";
const char kSecret[] = "secret";
const char kRootClientTokenOwner[] = "root_client_token_owner";

}  // namespace config_keys

const char kWeaveUrl[] = "https://www.googleapis.com/weave/v1/";
const char kDeprecatedUrl[] = "https://www.googleapis.com/clouddevices/v1/";
const char kXmppEndpoint[] = "talk.google.com:5223";

namespace {

const int kCurrentConfigVersion = 1;

void MigrateFromV0(base::DictionaryValue* dict) {
  std::string cloud_id;
  if (dict->GetString(config_keys::kCloudId, &cloud_id) && !cloud_id.empty())
    return;
  scoped_ptr<base::Value> tmp;
  if (dict->Remove(config_keys::kDeviceId, &tmp))
    dict->Set(config_keys::kCloudId, std::move(tmp));
}

Config::Settings CreateDefaultSettings() {
  Config::Settings result;
  result.oauth_url = "https://accounts.google.com/o/oauth2/";
  result.service_url = kWeaveUrl;
  result.xmpp_endpoint = kXmppEndpoint;
  result.local_anonymous_access_role = AuthScope::kViewer;
  result.pairing_modes.insert(PairingType::kPinCode);
  result.device_id = base::GenerateGUID();
  return result;
}

const EnumToStringMap<RootClientTokenOwner>::Map kRootClientTokenOwnerMap[] = {
    {RootClientTokenOwner::kNone, "none"},
    {RootClientTokenOwner::kClient, "client"},
    {RootClientTokenOwner::kCloud, "cloud"},
};

}  // namespace

template <>
LIBWEAVE_EXPORT EnumToStringMap<RootClientTokenOwner>::EnumToStringMap()
    : EnumToStringMap(kRootClientTokenOwnerMap) {}

Config::Config(provider::ConfigStore* config_store)
    : settings_{CreateDefaultSettings()}, config_store_{config_store} {
  Load();
}

void Config::AddOnChangedCallback(const OnChangedCallback& callback) {
  on_changed_.push_back(callback);
  // Force to read current state.
  callback.Run(settings_);
}

const Config::Settings& Config::GetSettings() const {
  return settings_;
}

void Config::Load() {
  Transaction change{this};
  change.save_ = false;

  settings_ = CreateDefaultSettings();

  if (!config_store_)
    return;

  // Crash on any mistakes in defaults.
  CHECK(config_store_->LoadDefaults(&settings_));

  CHECK(!settings_.client_id.empty());
  CHECK(!settings_.client_secret.empty());
  CHECK(!settings_.api_key.empty());
  CHECK(!settings_.oauth_url.empty());
  CHECK(!settings_.service_url.empty());
  CHECK(!settings_.xmpp_endpoint.empty());
  CHECK(!settings_.oem_name.empty());
  CHECK(!settings_.model_name.empty());
  CHECK(!settings_.model_id.empty());
  CHECK(!settings_.name.empty());
  CHECK(!settings_.device_id.empty());
  CHECK_EQ(settings_.embedded_code.empty(),
           (settings_.pairing_modes.find(PairingType::kEmbeddedCode) ==
              settings_.pairing_modes.end()));

  // Values below will be generated at runtime.
  CHECK(settings_.cloud_id.empty());
  CHECK(settings_.refresh_token.empty());
  CHECK(settings_.robot_account.empty());
  CHECK(settings_.last_configured_ssid.empty());
  CHECK(settings_.secret.empty());
  CHECK(settings_.root_client_token_owner == RootClientTokenOwner::kNone);

  change.LoadState();
}

void Config::Transaction::LoadState() {
  if (!config_->config_store_)
    return;
  std::string json_string = config_->config_store_->LoadSettings(kConfigName);
  if (json_string.empty()) {
    json_string = config_->config_store_->LoadSettings();
    if (json_string.empty())
      return;
  }

  auto value = base::JSONReader::Read(json_string);
  base::DictionaryValue* dict = nullptr;
  if (!value || !value->GetAsDictionary(&dict)) {
    LOG(ERROR) << "Failed to parse settings.";
    return;
  }

  int loaded_version = 0;
  dict->GetInteger(config_keys::kVersion, &loaded_version);

  if (loaded_version != kCurrentConfigVersion) {
    LOG(INFO) << "State version mismatch. expected: " << kCurrentConfigVersion
              << ", loaded: " << loaded_version;
    save_ = true;
  }

  if (loaded_version == 0) {
    MigrateFromV0(dict);
  }

  std::string tmp;
  bool tmp_bool{false};

  if (dict->GetString(config_keys::kClientId, &tmp))
    set_client_id(tmp);

  if (dict->GetString(config_keys::kClientSecret, &tmp))
    set_client_secret(tmp);

  if (dict->GetString(config_keys::kApiKey, &tmp))
    set_api_key(tmp);

  if (dict->GetString(config_keys::kOAuthURL, &tmp))
    set_oauth_url(tmp);

  if (dict->GetString(config_keys::kServiceURL, &tmp)) {
    if (tmp == kDeprecatedUrl)
      tmp = kWeaveUrl;
    set_service_url(tmp);
  }

  if (dict->GetString(config_keys::kXmppEndpoint, &tmp)) {
    set_xmpp_endpoint(tmp);
  }

  if (dict->GetString(config_keys::kName, &tmp))
    set_name(tmp);

  if (dict->GetString(config_keys::kDescription, &tmp))
    set_description(tmp);

  if (dict->GetString(config_keys::kLocation, &tmp))
    set_location(tmp);

  AuthScope scope{AuthScope::kNone};
  if (dict->GetString(config_keys::kLocalAnonymousAccessRole, &tmp) &&
      StringToEnum(tmp, &scope)) {
    set_local_anonymous_access_role(scope);
  }

  if (dict->GetBoolean(config_keys::kLocalDiscoveryEnabled, &tmp_bool))
    set_local_discovery_enabled(tmp_bool);

  if (dict->GetBoolean(config_keys::kLocalPairingEnabled, &tmp_bool))
    set_local_pairing_enabled(tmp_bool);

  if (dict->GetString(config_keys::kCloudId, &tmp))
    set_cloud_id(tmp);

  if (dict->GetString(config_keys::kDeviceId, &tmp))
    set_device_id(tmp);

  if (dict->GetString(config_keys::kRefreshToken, &tmp))
    set_refresh_token(tmp);

  if (dict->GetString(config_keys::kRobotAccount, &tmp))
    set_robot_account(tmp);

  if (dict->GetString(config_keys::kLastConfiguredSsid, &tmp))
    set_last_configured_ssid(tmp);

  std::vector<uint8_t> secret;
  if (dict->GetString(config_keys::kSecret, &tmp) && Base64Decode(tmp, &secret))
    set_secret(secret);

  RootClientTokenOwner token_owner{RootClientTokenOwner::kNone};
  if (dict->GetString(config_keys::kRootClientTokenOwner, &tmp) &&
      StringToEnum(tmp, &token_owner)) {
    set_root_client_token_owner(token_owner);
  }
}

void Config::Save() {
  if (!config_store_)
    return;

  base::DictionaryValue dict;
  dict.SetInteger(config_keys::kVersion, kCurrentConfigVersion);

  dict.SetString(config_keys::kClientId, settings_.client_id);
  dict.SetString(config_keys::kClientSecret, settings_.client_secret);
  dict.SetString(config_keys::kApiKey, settings_.api_key);
  dict.SetString(config_keys::kOAuthURL, settings_.oauth_url);
  dict.SetString(config_keys::kServiceURL, settings_.service_url);
  dict.SetString(config_keys::kXmppEndpoint, settings_.xmpp_endpoint);
  dict.SetString(config_keys::kRefreshToken, settings_.refresh_token);
  dict.SetString(config_keys::kCloudId, settings_.cloud_id);
  dict.SetString(config_keys::kDeviceId, settings_.device_id);
  dict.SetString(config_keys::kRobotAccount, settings_.robot_account);
  dict.SetString(config_keys::kLastConfiguredSsid,
                 settings_.last_configured_ssid);
  dict.SetString(config_keys::kSecret, Base64Encode(settings_.secret));
  dict.SetString(config_keys::kRootClientTokenOwner,
                 EnumToString(settings_.root_client_token_owner));
  dict.SetString(config_keys::kName, settings_.name);
  dict.SetString(config_keys::kDescription, settings_.description);
  dict.SetString(config_keys::kLocation, settings_.location);
  dict.SetString(config_keys::kLocalAnonymousAccessRole,
                 EnumToString(settings_.local_anonymous_access_role));
  dict.SetBoolean(config_keys::kLocalDiscoveryEnabled,
                  settings_.local_discovery_enabled);
  dict.SetBoolean(config_keys::kLocalPairingEnabled,
                  settings_.local_pairing_enabled);

  std::string json_string;
  base::JSONWriter::WriteWithOptions(
      dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);

  config_store_->SaveSettings(
      kConfigName, json_string,
      base::Bind([](ErrorPtr error) { CHECK(!error); }));
}

Config::Transaction::~Transaction() {
  Commit();
}

void Config::Transaction::Commit() {
  if (!config_)
    return;
  if (save_)
    config_->Save();
  for (const auto& cb : config_->on_changed_)
    cb.Run(*settings_);
  config_ = nullptr;
}

}  // namespace weave