// Copyright 2014 The Chromium OS 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 <brillo/http/http_connection_fake.h>

#include <base/logging.h>
#include <brillo/bind_lambda.h>
#include <brillo/http/http_request.h>
#include <brillo/mime_utils.h>
#include <brillo/streams/memory_stream.h>
#include <brillo/strings/string_utils.h>

namespace brillo {
namespace http {
namespace fake {

Connection::Connection(const std::string& url,
                       const std::string& method,
                       const std::shared_ptr<http::Transport>& transport)
    : http::Connection(transport), request_(url, method) {
  VLOG(1) << "fake::Connection created: " << method;
}

Connection::~Connection() {
  VLOG(1) << "fake::Connection destroyed";
}

bool Connection::SendHeaders(const HeaderList& headers,
                             brillo::ErrorPtr* /* error */) {
  request_.AddHeaders(headers);
  return true;
}

bool Connection::SetRequestData(StreamPtr stream,
                                brillo::ErrorPtr* /* error */) {
  request_.SetData(std::move(stream));
  return true;
}

bool Connection::FinishRequest(brillo::ErrorPtr*  /* error */) {
  using brillo::string_utils::ToString;
  request_.AddHeaders(
      {{request_header::kContentLength, ToString(request_.GetData().size())}});
  fake::Transport* transport = static_cast<fake::Transport*>(transport_.get());
  CHECK(transport) << "Expecting a fake transport";
  auto handler = transport->GetHandler(request_.GetURL(), request_.GetMethod());
  if (handler.is_null()) {
    LOG(ERROR) << "Received unexpected " << request_.GetMethod()
               << " request at " << request_.GetURL();
    response_.ReplyText(status_code::NotFound,
                        "<html><body>Not found</body></html>",
                        brillo::mime::text::kHtml);
  } else {
    handler.Run(request_, &response_);
  }
  return true;
}

RequestID Connection::FinishRequestAsync(
    const SuccessCallback& success_callback,
    const ErrorCallback& error_callback) {
  // Make sure the produced Closure holds a reference to the instance of this
  // connection.
  auto connection = std::static_pointer_cast<Connection>(shared_from_this());
  auto callback = [](std::shared_ptr<Connection> connection,
                     const SuccessCallback& success_callback,
                     const ErrorCallback& error_callback) {
    connection->FinishRequestAsyncHelper(success_callback, error_callback);
  };
  transport_->RunCallbackAsync(FROM_HERE,
                               base::Bind(callback,
                                          base::Passed(&connection),
                                          success_callback,
                                          error_callback));
  return 1;
}

void Connection::FinishRequestAsyncHelper(
    const SuccessCallback& success_callback,
    const ErrorCallback& error_callback) {
  brillo::ErrorPtr error;
  if (!FinishRequest(&error)) {
    error_callback.Run(1, error.get());
  } else {
    std::unique_ptr<Response> response{new Response{shared_from_this()}};
    success_callback.Run(1, std::move(response));
  }
}

int Connection::GetResponseStatusCode() const {
  return response_.GetStatusCode();
}

std::string Connection::GetResponseStatusText() const {
  return response_.GetStatusText();
}

std::string Connection::GetProtocolVersion() const {
  return response_.GetProtocolVersion();
}

std::string Connection::GetResponseHeader(
    const std::string& header_name) const {
  return response_.GetHeader(header_name);
}

StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) {
  // HEAD requests must not return body.
  if (request_.GetMethod() != request_type::kHead) {
    return MemoryStream::OpenRef(response_.GetData(), error);
  } else {
    // Return empty data stream for HEAD requests.
    return MemoryStream::OpenCopyOf(nullptr, 0, error);
  }
}

}  // namespace fake
}  // namespace http
}  // namespace brillo