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

#include "components/signin/public/webdata/token_web_data.h"

#include <memory>
#include <optional>

#include "base/functional/bind.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/expected_macros.h"
#include "components/signin/public/webdata/token_service_table.h"
#include "components/webdata/common/web_database_service.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"

using base::BindOnce;

class TokenWebDataBackend
    : public base::RefCountedDeleteOnSequence<TokenWebDataBackend> {
 public:
  explicit TokenWebDataBackend(
      scoped_refptr<base::SequencedTaskRunner> db_task_runner)
      : base::RefCountedDeleteOnSequence<TokenWebDataBackend>(
            std::move(db_task_runner)) {}

  WebDatabase::State RemoveAllTokens(WebDatabase* db) {
    if (TokenServiceTable::FromWebDatabase(db)->RemoveAllTokens()) {
      return WebDatabase::COMMIT_NEEDED;
    }
    return WebDatabase::COMMIT_NOT_NEEDED;
  }

  WebDatabase::State RemoveTokenForService(const std::string& service,
                                           WebDatabase* db) {
    if (TokenServiceTable::FromWebDatabase(db)->RemoveTokenForService(
            service)) {
      return WebDatabase::COMMIT_NEEDED;
    }
    return WebDatabase::COMMIT_NOT_NEEDED;
  }

  WebDatabase::State RemoveOtherTokens(
      const std::vector<std::string>& services_to_keep,
      WebDatabase* db) {
    if (TokenServiceTable::FromWebDatabase(db)->RemoveOtherTokens(
            services_to_keep)) {
      return WebDatabase::COMMIT_NEEDED;
    }
    return WebDatabase::COMMIT_NOT_NEEDED;
  }

  WebDatabase::State SetTokenForService(
      const std::string& service,
      const std::string& token,
      const std::vector<uint8_t>& wrapped_binding_key,
      bool mtls_token_binding,
      WebDatabase* db) {
    if (TokenServiceTable::FromWebDatabase(db)->SetTokenForService(
            service, token, wrapped_binding_key, mtls_token_binding)) {
      return WebDatabase::COMMIT_NEEDED;
    }
    return WebDatabase::COMMIT_NOT_NEEDED;
  }

  std::unique_ptr<WDTypedResult> GetAllTokens(WebDatabase* db) {
    SCOPED_UMA_HISTOGRAM_TIMER("Signin.TokenTable.GetAllTokensTime");
    TokenResult result;
    result.db_result = TokenServiceTable::FromWebDatabase(db)->GetAllTokens(
        &result.tokens, result.should_reencrypt);
    return std::make_unique<WDResult<TokenResult>>(TOKEN_RESULT,
                                                   std::move(result));
  }

  std::unique_ptr<WDTypedResult> GetAllWrappedBindingKeys(WebDatabase* db) {
    ASSIGN_OR_RETURN(
        absl::flat_hash_set<std::vector<uint8_t>> keys,
        TokenServiceTable::FromWebDatabase(db)->GetAllWrappedBindingKeys(),
        [] { return nullptr; });

    return std::make_unique<
        WDResult<absl::flat_hash_set<std::vector<uint8_t>>>>(
        WRAPPED_BINDING_KEYS_RESULT, std::move(keys));
  }

 protected:
  virtual ~TokenWebDataBackend() = default;

 private:
  friend class base::RefCountedDeleteOnSequence<TokenWebDataBackend>;
  friend class base::DeleteHelper<TokenWebDataBackend>;
};

TokenResult::TokenResult() = default;
TokenResult::TokenResult(const TokenResult& other) = default;
TokenResult& TokenResult::operator=(const TokenResult& other) = default;
TokenResult::TokenResult(TokenResult&& other) noexcept = default;
TokenResult& TokenResult::operator=(TokenResult&& other) noexcept = default;
TokenResult::~TokenResult() = default;

TokenWebData::TokenWebData(
    scoped_refptr<WebDatabaseService> wdbs,
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
    : WebDataServiceBase(wdbs, std::move(ui_task_runner)),
      token_backend_(
          base::MakeRefCounted<TokenWebDataBackend>(wdbs->GetDbSequence())) {}

void TokenWebData::SetTokenForService(
    const std::string& service,
    const std::string& token,
    const std::vector<uint8_t>& wrapped_binding_key,
    bool mtls_token_binding) {
  wdbs_->ScheduleDBTask(
      FROM_HERE,
      BindOnce(&TokenWebDataBackend::SetTokenForService, token_backend_,
               service, token, wrapped_binding_key, mtls_token_binding));
}

void TokenWebData::RemoveAllTokens() {
  wdbs_->ScheduleDBTask(
      FROM_HERE,
      BindOnce(&TokenWebDataBackend::RemoveAllTokens, token_backend_));
}

void TokenWebData::RemoveTokenForService(const std::string& service) {
  wdbs_->ScheduleDBTask(FROM_HERE,
                        BindOnce(&TokenWebDataBackend::RemoveTokenForService,
                                 token_backend_, service));
}

void TokenWebData::RemoveOtherTokens(
    const std::vector<std::string>& services_to_keep) {
  wdbs_->ScheduleDBTask(FROM_HERE,
                        BindOnce(&TokenWebDataBackend::RemoveOtherTokens,
                                 token_backend_, services_to_keep));
}

// Null on failure. Success is WDResult<std::string>
WebDataServiceBase::Handle TokenWebData::GetAllTokens(
    WebDataServiceConsumer* consumer) {
  return wdbs_->ScheduleDBTaskWithResult(
      FROM_HERE, BindOnce(&TokenWebDataBackend::GetAllTokens, token_backend_),
      consumer);
}

// Null on failure. Success is WDResult<std::vector<std::vector<uint8_t>>>
WebDataServiceBase::Handle TokenWebData::GetAllWrappedBindingKeys(
    WebDataServiceConsumer* consumer) {
  return wdbs_->ScheduleDBTaskWithResult(
      FROM_HERE,
      BindOnce(&TokenWebDataBackend::GetAllWrappedBindingKeys, token_backend_),
      consumer);
}

TokenWebData::~TokenWebData() = default;
