// 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/sync/test/fake_server_http_post_provider.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "components/sync/test/fake_server.h"
#include "net/base/net_errors.h"

namespace fake_server {

// static
std::atomic_bool FakeServerHttpPostProvider::network_enabled_(true);

FakeServerHttpPostProviderFactory::FakeServerHttpPostProviderFactory(
    const base::WeakPtr<FakeServer>& fake_server,
    scoped_refptr<base::SequencedTaskRunner> fake_server_task_runner)
    : fake_server_(fake_server),
      fake_server_task_runner_(fake_server_task_runner) {}

FakeServerHttpPostProviderFactory::~FakeServerHttpPostProviderFactory() =
    default;

scoped_refptr<syncer::HttpPostProvider>
FakeServerHttpPostProviderFactory::Create() {
  return new FakeServerHttpPostProvider(fake_server_, fake_server_task_runner_);
}

FakeServerHttpPostProvider::FakeServerHttpPostProvider(
    const base::WeakPtr<FakeServer>& fake_server,
    scoped_refptr<base::SequencedTaskRunner> fake_server_task_runner)
    : fake_server_(fake_server),
      fake_server_task_runner_(fake_server_task_runner) {}

FakeServerHttpPostProvider::~FakeServerHttpPostProvider() = default;

void FakeServerHttpPostProvider::SetExtraRequestHeaders(
    const net::HttpRequestHeaders& headers) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  extra_request_headers_ = headers;
}

void FakeServerHttpPostProvider::SetURL(const GURL& url) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  request_url_ = url;
}

void FakeServerHttpPostProvider::SetPostPayload(const char* content_type,
                                                int content_length,
                                                const char* content) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  request_content_type_.assign(content_type);
  request_content_.assign(content, content_length);
}

bool FakeServerHttpPostProvider::MakeSynchronousPost(int* net_error_code,
                                                     int* http_status_code) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!network_enabled_) {
    response_.clear();
    *net_error_code = net::ERR_INTERNET_DISCONNECTED;
    *http_status_code = 0;
    return false;
  }

  synchronous_post_completion_.Reset();
  aborted_ = false;

  // It is assumed that a POST is being made to /command.
  int post_status_code = -1;
  std::string post_response;

  bool result = fake_server_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &FakeServerHttpPostProvider::HandleCommandOnFakeServerThread,
          base::RetainedRef(this), base::Unretained(&post_status_code),
          base::Unretained(&post_response)));

  if (!result) {
    response_.clear();
    *net_error_code = net::ERR_UNEXPECTED;
    *http_status_code = 0;
    return false;
  }

  {
    base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
    synchronous_post_completion_.Wait();
  }

  if (aborted_) {
    *net_error_code = net::ERR_ABORTED;
    return false;
  }

  // Zero means success.
  *net_error_code = 0;
  *http_status_code = post_status_code;
  response_ = post_response;

  return true;
}

int FakeServerHttpPostProvider::GetResponseContentLength() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return response_.length();
}

const char* FakeServerHttpPostProvider::GetResponseContent() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return response_.c_str();
}

const std::string FakeServerHttpPostProvider::GetResponseHeaderValue(
    const std::string& name) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return std::string();
}

void FakeServerHttpPostProvider::Abort() {
  // Note: This may be called on any thread, so no `sequence_checker_` here.
  // The sync thread could be blocked in MakeSynchronousPost(), waiting
  // for HandleCommandOnFakeServerThread() to be processed and completed.
  // This causes an immediate unblocking which will be returned as
  // net::ERR_ABORTED.
  aborted_ = true;
  synchronous_post_completion_.Signal();
}

// static
void FakeServerHttpPostProvider::DisableNetwork() {
  // Note: This may be called on any thread.
  network_enabled_ = false;
}

// static
void FakeServerHttpPostProvider::EnableNetwork() {
  // Note: This may be called on any thread.
  network_enabled_ = true;
}

void FakeServerHttpPostProvider::HandleCommandOnFakeServerThread(
    int* http_status_code,
    std::string* response) {
  DCHECK(fake_server_task_runner_->RunsTasksInCurrentSequence());

  if (!fake_server_) {
    // Server destroyed.
    Abort();
  }

  if (aborted_) {
    // Command explicitly aborted. |synchronous_post_completion_| has already
    // been signalled by the FakeServerHttpPostProvider::Abort call.
    return;
  }

  *http_status_code = fake_server_->HandleCommand(request_content_, response);
  synchronous_post_completion_.Signal();
}

}  // namespace fake_server
