/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "storage/config_cache.h"

#include <bluetooth/log.h>

#include <ios>
#include <sstream>
#include <utility>

#include "hci/enum_helper.h"
#include "os/parameter_provider.h"
#include "storage/mutation.h"

namespace {

const std::unordered_set<std::string_view> kEncryptKeyNameList = {
        "LinkKey",      "LE_KEY_PENC", "LE_KEY_PID",  "LE_KEY_LID",
        "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK"};

bool TrimAfterNewLine(std::string& value) {
  std::string value_no_newline;
  size_t newline_position = value.find_first_of('\n');
  if (newline_position != std::string::npos) {
    value.erase(newline_position);
    return true;
  }
  return false;
}

bool InEncryptKeyNameList(std::string key) {
  return kEncryptKeyNameList.find(key) != kEncryptKeyNameList.end();
}

}  // namespace

namespace bluetooth {
namespace storage {

const std::unordered_set<std::string_view> kLePropertyNames = {
        "LE_KEY_PENC", "LE_KEY_PID", "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK"};

const std::unordered_set<std::string_view> kClassicPropertyNames = {
        "LinkKey", "SdpDiMaufacturer", "SdpDiModel", "SdpDiHardwareVersion", "SdpDiVendorSource"};

const std::string ConfigCache::kDefaultSectionName = "Global";

std::string kEncryptedStr = "encrypted";

ConfigCache::ConfigCache(size_t temp_device_capacity,
                         std::unordered_set<std::string_view> persistent_property_names)
    : persistent_property_names_(std::move(persistent_property_names)),
      information_sections_(),
      persistent_devices_(),
      temporary_devices_(temp_device_capacity) {}

void ConfigCache::SetPersistentConfigChangedCallback(
        std::function<void()> persistent_config_changed_callback) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  persistent_config_changed_callback_ = std::move(persistent_config_changed_callback);
}

ConfigCache::ConfigCache(ConfigCache&& other) noexcept
    : persistent_config_changed_callback_(nullptr),
      persistent_property_names_(std::move(other.persistent_property_names_)),
      information_sections_(std::move(other.information_sections_)),
      persistent_devices_(std::move(other.persistent_devices_)),
      temporary_devices_(std::move(other.temporary_devices_)) {
  log::assert_that(other.persistent_config_changed_callback_ == nullptr,
                   "Can't assign after setting the callback");
}

ConfigCache& ConfigCache::operator=(ConfigCache&& other) noexcept {
  if (&other == this) {
    return *this;
  }
  std::lock_guard<std::recursive_mutex> my_lock(mutex_);
  std::lock_guard<std::recursive_mutex> others_lock(other.mutex_);
  log::assert_that(other.persistent_config_changed_callback_ == nullptr,
                   "Can't assign after setting the callback");
  persistent_config_changed_callback_ = {};
  persistent_property_names_ = std::move(other.persistent_property_names_);
  information_sections_ = std::move(other.information_sections_);
  persistent_devices_ = std::move(other.persistent_devices_);
  temporary_devices_ = std::move(other.temporary_devices_);
  return *this;
}

bool ConfigCache::operator==(const ConfigCache& rhs) const {
  std::lock_guard<std::recursive_mutex> my_lock(mutex_);
  std::lock_guard<std::recursive_mutex> others_lock(rhs.mutex_);
  return persistent_property_names_ == rhs.persistent_property_names_ &&
         information_sections_ == rhs.information_sections_ &&
         persistent_devices_ == rhs.persistent_devices_ &&
         temporary_devices_ == rhs.temporary_devices_;
}

bool ConfigCache::operator!=(const ConfigCache& rhs) const { return !(*this == rhs); }

void ConfigCache::Clear() {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (information_sections_.size() > 0) {
    information_sections_.clear();
    PersistentConfigChangedCallback();
  }
  if (persistent_devices_.size() > 0) {
    persistent_devices_.clear();
    PersistentConfigChangedCallback();
  }
  if (temporary_devices_.size() > 0) {
    temporary_devices_.clear();
  }
}

bool ConfigCache::HasSection(const std::string& section) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return information_sections_.contains(section) || persistent_devices_.contains(section) ||
         temporary_devices_.contains(section);
}

bool ConfigCache::HasProperty(const std::string& section, const std::string& property) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  auto section_iter = information_sections_.find(section);
  if (section_iter != information_sections_.end()) {
    return section_iter->second.find(property) != section_iter->second.end();
  }
  section_iter = persistent_devices_.find(section);
  if (section_iter != persistent_devices_.end()) {
    return section_iter->second.find(property) != section_iter->second.end();
  }
  section_iter = temporary_devices_.find(section);
  if (section_iter != temporary_devices_.end()) {
    return section_iter->second.find(property) != section_iter->second.end();
  }
  return false;
}

std::optional<std::string> ConfigCache::GetProperty(const std::string& section,
                                                    const std::string& property) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  auto section_iter = information_sections_.find(section);
  if (section_iter != information_sections_.end()) {
    auto property_iter = section_iter->second.find(property);
    if (property_iter != section_iter->second.end()) {
      return property_iter->second;
    }
  }
  section_iter = persistent_devices_.find(section);
  if (section_iter != persistent_devices_.end()) {
    auto property_iter = section_iter->second.find(property);
    if (property_iter != section_iter->second.end()) {
      std::string value = property_iter->second;
      if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && value == kEncryptedStr) {
        return os::ParameterProvider::GetBtKeystoreInterface()->get_key(section + "-" + property);
      }
      return value;
    }
  }
  section_iter = temporary_devices_.find(section);
  if (section_iter != temporary_devices_.end()) {
    auto property_iter = section_iter->second.find(property);
    if (property_iter != section_iter->second.end()) {
      return property_iter->second;
    }
  }
  return std::nullopt;
}

void ConfigCache::SetProperty(std::string section, std::string property, std::string value) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  TrimAfterNewLine(section);
  TrimAfterNewLine(property);
  TrimAfterNewLine(value);
  log::assert_that(!section.empty(), "Empty section name not allowed");
  log::assert_that(!property.empty(), "Empty property name not allowed");
  if (!IsDeviceSection(section)) {
    auto section_iter = information_sections_.find(section);
    if (section_iter == information_sections_.end()) {
      section_iter = information_sections_
                             .try_emplace_back(section, common::ListMap<std::string, std::string>{})
                             .first;
    }
    section_iter->second.insert_or_assign(property, std::move(value));
    PersistentConfigChangedCallback();
    return;
  }
  auto section_iter = persistent_devices_.find(section);
  if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {
    // move paired devices or create new paired device when a link key is set
    auto section_properties = temporary_devices_.extract(section);
    if (section_properties) {
      section_iter =
              persistent_devices_.try_emplace_back(section, std::move(section_properties->second))
                      .first;
    } else {
      section_iter = persistent_devices_
                             .try_emplace_back(section, common::ListMap<std::string, std::string>{})
                             .first;
    }
  }
  if (section_iter != persistent_devices_.end()) {
    bool is_encrypted = value == kEncryptedStr;
    if ((!value.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&
        os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property) &&
        !is_encrypted) {
      if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(
                  section + "-" + property, value)) {
        value = kEncryptedStr;
      }
    }
    section_iter->second.insert_or_assign(property, std::move(value));
    PersistentConfigChangedCallback();
    return;
  }
  section_iter = temporary_devices_.find(section);
  if (section_iter == temporary_devices_.end()) {
    auto triple =
            temporary_devices_.try_emplace(section, common::ListMap<std::string, std::string>{});
    section_iter = std::get<0>(triple);
  }
  section_iter->second.insert_or_assign(property, std::move(value));
}

bool ConfigCache::RemoveSection(const std::string& section) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  // sections are unique among all three maps, hence removing from one of them is enough
  if (information_sections_.extract(section) || persistent_devices_.extract(section)) {
    PersistentConfigChangedCallback();
    return true;
  } else {
    return temporary_devices_.extract(section).has_value();
  }
}

bool ConfigCache::RemoveProperty(const std::string& section, const std::string& property) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  auto section_iter = information_sections_.find(section);
  if (section_iter != information_sections_.end()) {
    auto value = section_iter->second.extract(property);
    // if section is empty after removal, remove the whole section as empty section is not allowed
    if (section_iter->second.size() == 0) {
      information_sections_.erase(section_iter);
    }
    if (value.has_value()) {
      PersistentConfigChangedCallback();
      return true;
    } else {
      return false;
    }
  }
  section_iter = persistent_devices_.find(section);
  if (section_iter != persistent_devices_.end()) {
    auto value = section_iter->second.extract(property);
    // if section is empty after removal, remove the whole section as empty section is not allowed
    if (section_iter->second.size() == 0) {
      persistent_devices_.erase(section_iter);
    } else if (value && IsPersistentProperty(property)) {
      // move unpaired device
      auto section_properties = persistent_devices_.extract(section);
      temporary_devices_.insert_or_assign(section, std::move(section_properties->second));
    }
    if (value.has_value()) {
      PersistentConfigChangedCallback();
      if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&
          os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property)) {
        os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(
                section + "-" + property, "");
      }
      return true;
    } else {
      return false;
    }
  }
  section_iter = temporary_devices_.find(section);
  if (section_iter != temporary_devices_.end()) {
    auto value = section_iter->second.extract(property);
    if (section_iter->second.size() == 0) {
      temporary_devices_.erase(section_iter);
    }
    return value.has_value();
  }
  return false;
}

void ConfigCache::ConvertEncryptOrDecryptKeyIfNeeded() {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  log::info("");
  auto persistent_sections = GetPersistentSections();
  for (const auto& section : persistent_sections) {
    auto section_iter = persistent_devices_.find(section);
    for (const auto& property : kEncryptKeyNameList) {
      auto property_iter = section_iter->second.find(std::string(property));
      if (property_iter != section_iter->second.end()) {
        bool is_encrypted = property_iter->second == kEncryptedStr;
        if ((!property_iter->second.empty()) &&
            os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&
            os::ParameterProvider::IsCommonCriteriaMode() && !is_encrypted) {
          if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(
                      section + "-" + std::string(property), property_iter->second)) {
            SetProperty(section, std::string(property), kEncryptedStr);
          }
        }
        if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && is_encrypted) {
          std::string value_str = os::ParameterProvider::GetBtKeystoreInterface()->get_key(
                  section + "-" + std::string(property));
          if (!os::ParameterProvider::IsCommonCriteriaMode()) {
            SetProperty(section, std::string(property), value_str);
          }
        }
      }
    }
  }
}

bool ConfigCache::IsDeviceSection(const std::string& section) {
  return hci::Address::IsValidAddress(section);
}

bool ConfigCache::IsPersistentProperty(const std::string& property) const {
  return persistent_property_names_.find(property) != persistent_property_names_.end();
}

void ConfigCache::RemoveSectionWithProperty(const std::string& property) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  size_t num_persistent_removed = 0;
  for (auto* config_section : {&information_sections_, &persistent_devices_}) {
    for (auto it = config_section->begin(); it != config_section->end();) {
      if (it->second.contains(property)) {
        log::info("Removing persistent section {} with property {}", it->first, property);
        it = config_section->erase(it);
        num_persistent_removed++;
        continue;
      }
      it++;
    }
  }
  for (auto it = temporary_devices_.begin(); it != temporary_devices_.end();) {
    if (it->second.contains(property)) {
      log::info("Removing temporary section {} with property {}", it->first, property);
      it = temporary_devices_.erase(it);
      continue;
    }
    it++;
  }
  if (num_persistent_removed > 0) {
    PersistentConfigChangedCallback();
  }
}

std::vector<std::string> ConfigCache::GetPersistentSections() const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  std::vector<std::string> paired_devices;
  paired_devices.reserve(persistent_devices_.size());
  for (const auto& elem : persistent_devices_) {
    paired_devices.emplace_back(elem.first);
  }
  return paired_devices;
}

void ConfigCache::Commit(std::queue<MutationEntry>& mutation_entries) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  while (!mutation_entries.empty()) {
    auto entry = std::move(mutation_entries.front());
    mutation_entries.pop();
    switch (entry.entry_type) {
      case MutationEntry::EntryType::SET:
        SetProperty(std::move(entry.section), std::move(entry.property), std::move(entry.value));
        break;
      case MutationEntry::EntryType::REMOVE_PROPERTY:
        RemoveProperty(entry.section, entry.property);
        break;
      case MutationEntry::EntryType::REMOVE_SECTION:
        RemoveSection(entry.section);
        break;
        // do not write a default case so that when a new enum is defined, compilation would fail
        // automatically
    }
  }
}

std::string ConfigCache::SerializeToLegacyFormat() const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  std::stringstream serialized;
  for (const auto* config_section : {&information_sections_, &persistent_devices_}) {
    for (const auto& section : *config_section) {
      serialized << "[" << section.first << "]" << std::endl;
      for (const auto& property : section.second) {
        serialized << property.first << " = " << property.second << std::endl;
      }
      serialized << std::endl;
    }
  }
  return serialized.str();
}

std::vector<ConfigCache::SectionAndPropertyValue> ConfigCache::GetSectionNamesWithProperty(
        const std::string& property) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  std::vector<SectionAndPropertyValue> result;
  for (auto* config_section : {&information_sections_, &persistent_devices_}) {
    for (const auto& elem : *config_section) {
      auto it = elem.second.find(property);
      if (it != elem.second.end()) {
        result.emplace_back(SectionAndPropertyValue{.section = elem.first, .property = it->second});
        continue;
      }
    }
  }
  for (const auto& elem : temporary_devices_) {
    auto it = elem.second.find(property);
    if (it != elem.second.end()) {
      result.emplace_back(SectionAndPropertyValue{.section = elem.first, .property = it->second});
      continue;
    }
  }
  return result;
}

std::vector<std::string> ConfigCache::GetPropertyNames(const std::string& section) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);

  std::vector<std::string> property_names;
  auto ProcessSections = [&](const auto& sections) {
    auto section_iter = sections.find(section);
    if (section_iter != sections.end()) {
      for (const auto& [property_name, value] : section_iter->second) {
        property_names.emplace_back(property_name);
      }
      return true;
    }
    return false;
  };

  // A section must exist in at most one map.
  if (ProcessSections(information_sections_)) {
    return property_names;
  }
  if (ProcessSections(persistent_devices_)) {
    return property_names;
  }
  ProcessSections(temporary_devices_);
  return property_names;
}

namespace {

bool FixDeviceTypeInconsistencyInSection(
        const std::string& section_name,
        common::ListMap<std::string, std::string>& device_section_entries) {
  if (!hci::Address::IsValidAddress(section_name)) {
    return false;
  }
  auto device_type_iter = device_section_entries.find("DevType");
  if (device_type_iter != device_section_entries.end() &&
      device_type_iter->second == std::to_string(hci::DeviceType::DUAL)) {
    // We might only have one of classic/LE keys for a dual device, but it is still a dual device,
    // so we should not change the DevType.
    return false;
  }

  // we will ignore the existing DevType, since it is not known to be a DUAL device so
  // the keys we have should be sufficient to infer the correct DevType
  bool is_le = false;
  bool is_classic = false;
  // default
  hci::DeviceType device_type = hci::DeviceType::BR_EDR;
  for (const auto& entry : device_section_entries) {
    if (kLePropertyNames.find(entry.first) != kLePropertyNames.end()) {
      is_le = true;
    }
    if (kClassicPropertyNames.find(entry.first) != kClassicPropertyNames.end()) {
      is_classic = true;
    }
  }
  if (is_classic && is_le) {
    device_type = hci::DeviceType::DUAL;
  } else if (is_classic) {
    device_type = hci::DeviceType::BR_EDR;
  } else if (is_le) {
    device_type = hci::DeviceType::LE;
  }
  bool inconsistent = true;
  std::string device_type_str = std::to_string(device_type);
  if (device_type_iter != device_section_entries.end()) {
    inconsistent = device_type_str != device_type_iter->second;
    if (inconsistent) {
      device_type_iter->second = std::move(device_type_str);
    }
  } else {
    device_section_entries.insert_or_assign("DevType", std::move(device_type_str));
  }
  return inconsistent;
}

}  // namespace

bool ConfigCache::FixDeviceTypeInconsistencies() {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  bool persistent_device_changed = false;
  for (auto* config_section : {&information_sections_, &persistent_devices_}) {
    for (auto& elem : *config_section) {
      if (FixDeviceTypeInconsistencyInSection(elem.first, elem.second)) {
        persistent_device_changed = true;
      }
    }
  }
  bool temp_device_changed = false;
  for (auto& elem : temporary_devices_) {
    if (FixDeviceTypeInconsistencyInSection(elem.first, elem.second)) {
      temp_device_changed = true;
    }
  }
  if (persistent_device_changed) {
    PersistentConfigChangedCallback();
  }
  return persistent_device_changed || temp_device_changed;
}

bool ConfigCache::HasAtLeastOneMatchingPropertiesInSection(
        const std::string& section,
        const std::unordered_set<std::string_view>& property_names) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  const common::ListMap<std::string, std::string>* section_ptr;
  if (!IsDeviceSection(section)) {
    auto section_iter = information_sections_.find(section);
    if (section_iter == information_sections_.end()) {
      return false;
    }
    section_ptr = &section_iter->second;
  } else {
    auto section_iter = persistent_devices_.find(section);
    if (section_iter == persistent_devices_.end()) {
      section_iter = temporary_devices_.find(section);
      if (section_iter == temporary_devices_.end()) {
        return false;
      }
    }
    section_ptr = &section_iter->second;
  }
  for (const auto& property : *section_ptr) {
    if (property_names.count(property.first) > 0) {
      return true;
    }
  }
  return false;
}

bool ConfigCache::IsPersistentSection(const std::string& section) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return persistent_devices_.contains(section);
}

}  // namespace storage
}  // namespace bluetooth
