// Copyright 2017 The Chromium 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 "content/browser/background_fetch/background_fetch_test_base.h"

#include <stdint.h>
#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/common/service_worker/service_worker_status_code.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_url_parameters.h"
#include "content/public/test/fake_download_item.h"
#include "content/public/test/mock_download_manager.h"
#include "url/gurl.h"

namespace content {

namespace {

const char kTestOrigin[] = "https://example.com/";
const char kTestScriptUrl[] = "https://example.com/sw.js";

void DidRegisterServiceWorker(int64_t* out_service_worker_registration_id,
                              base::Closure quit_closure,
                              ServiceWorkerStatusCode status,
                              const std::string& status_message,
                              int64_t service_worker_registration_id) {
  DCHECK(out_service_worker_registration_id);
  EXPECT_EQ(SERVICE_WORKER_OK, status) << status_message;

  *out_service_worker_registration_id = service_worker_registration_id;

  quit_closure.Run();
}

void DidFindServiceWorkerRegistration(
    scoped_refptr<ServiceWorkerRegistration>* out_service_worker_registration,
    base::Closure quit_closure,
    ServiceWorkerStatusCode status,
    scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
  DCHECK(out_service_worker_registration);
  EXPECT_EQ(SERVICE_WORKER_OK, status) << ServiceWorkerStatusToString(status);

  *out_service_worker_registration = service_worker_registration;

  quit_closure.Run();
}

}  // namespace

// -----------------------------------------------------------------------------
// TestResponse

BackgroundFetchTestBase::TestResponse::TestResponse() = default;

BackgroundFetchTestBase::TestResponse::~TestResponse() = default;

// -----------------------------------------------------------------------------
// TestResponseBuilder

BackgroundFetchTestBase::TestResponseBuilder::TestResponseBuilder(
    int response_code)
    : response_(base::MakeUnique<BackgroundFetchTestBase::TestResponse>()) {
  response_->headers = make_scoped_refptr(new net::HttpResponseHeaders(
      "HTTP/1.1 " + std::to_string(response_code)));
}

BackgroundFetchTestBase::TestResponseBuilder::~TestResponseBuilder() = default;

BackgroundFetchTestBase::TestResponseBuilder&
BackgroundFetchTestBase::TestResponseBuilder::AddResponseHeader(
    const std::string& name,
    const std::string& value) {
  DCHECK(response_);
  response_->headers->AddHeader(name + ": " + value);
  return *this;
}

BackgroundFetchTestBase::TestResponseBuilder&
BackgroundFetchTestBase::TestResponseBuilder::SetResponseData(
    std::string data) {
  DCHECK(response_);
  response_->data.swap(data);
  return *this;
}

std::unique_ptr<BackgroundFetchTestBase::TestResponse>
BackgroundFetchTestBase::TestResponseBuilder::Build() {
  return std::move(response_);
}

// -----------------------------------------------------------------------------
// RespondingDownloadManager

// Faked download manager that will respond to known HTTP requests with a test-
// defined response. See CreateRequestWithProvidedResponse().
class BackgroundFetchTestBase::RespondingDownloadManager
    : public MockDownloadManager {
 public:
  RespondingDownloadManager() : weak_ptr_factory_(this) {}
  ~RespondingDownloadManager() override = default;

  // Responds to requests to |url| with the given |response|.
  void RegisterResponse(const GURL& url,
                        std::unique_ptr<TestResponse> response) {
    DCHECK_EQ(registered_responses_.count(url), 0u);
    registered_responses_[url] = std::move(response);
  }

  // Called when the Background Fetch system starts a download, all information
  // for which is contained in the |params|.
  void DownloadUrl(std::unique_ptr<DownloadUrlParameters> params) override {
    auto iter = registered_responses_.find(params->url());
    if (iter == registered_responses_.end())
      return;

    TestResponse* response = iter->second.get();

    std::unique_ptr<FakeDownloadItem> download_item =
        base::MakeUnique<FakeDownloadItem>();

    download_item->SetURL(params->url());
    download_item->SetUrlChain({params->url()});
    download_item->SetState(DownloadItem::DownloadState::IN_PROGRESS);
    download_item->SetGuid(base::GenerateGUID());
    download_item->SetStartTime(base::Time::Now());
    download_item->SetResponseHeaders(response->headers);

    // Asynchronously invoke the callback set on the |params|, and then continue
    // dealing with the response in this class.
    BrowserThread::PostTaskAndReply(
        BrowserThread::UI, FROM_HERE,
        base::Bind(params->callback(), download_item.get(),
                   DOWNLOAD_INTERRUPT_REASON_NONE),
        base::Bind(&RespondingDownloadManager::DidStartDownload,
                   weak_ptr_factory_.GetWeakPtr(), download_item.get()));

    download_items_.push_back(std::move(download_item));
  }

 private:
  // Called when the download has been "started" by the download manager. This
  // is where we finish the download by sending a single update.
  void DidStartDownload(FakeDownloadItem* download_item) {
    auto iter = registered_responses_.find(download_item->GetURL());
    DCHECK(iter != registered_responses_.end());

    TestResponse* response = iter->second.get();

    download_item->SetState(DownloadItem::DownloadState::COMPLETE);
    download_item->SetEndTime(base::Time::Now());

    base::FilePath response_path;
    if (!temp_directory_.IsValid())
      ASSERT_TRUE(temp_directory_.CreateUniqueTempDir());

    // Write the |response|'s data to a temporary file.
    ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory_.GetPath(),
                                               &response_path));

    ASSERT_NE(-1 /* error */,
              base::WriteFile(response_path, response->data.c_str(),
                              response->data.size()));

    download_item->SetTargetFilePath(response_path);
    download_item->SetReceivedBytes(response->data.size());
    download_item->SetMimeType("text/plain");

    // Notify the Job Controller about the download having been updated.
    download_item->NotifyDownloadUpdated();
  }

  // Map of URL to the response information associated with that URL.
  std::map<GURL, std::unique_ptr<TestResponse>> registered_responses_;

  // Only used to guarantee the lifetime of the created FakeDownloadItems.
  std::vector<std::unique_ptr<FakeDownloadItem>> download_items_;

  // Temporary directory in which successfully downloaded files will be stored.
  base::ScopedTempDir temp_directory_;

  base::WeakPtrFactory<RespondingDownloadManager> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(RespondingDownloadManager);
};

BackgroundFetchTestBase::BackgroundFetchTestBase()
    : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
      origin_(GURL(kTestOrigin)) {}

BackgroundFetchTestBase::~BackgroundFetchTestBase() {
  DCHECK(set_up_called_);
  DCHECK(tear_down_called_);
}

void BackgroundFetchTestBase::SetUp() {
  download_manager_ = new RespondingDownloadManager();

  // The |download_manager_| ownership is given to the BrowserContext, and the
  // BrowserContext will take care of deallocating it.
  BrowserContext::SetDownloadManagerForTesting(
      browser_context(), base::WrapUnique(download_manager_));

  set_up_called_ = true;
}

void BackgroundFetchTestBase::TearDown() {
  EXPECT_CALL(*download_manager_, Shutdown()).Times(1);

  service_worker_registrations_.clear();
  tear_down_called_ = true;
}

bool BackgroundFetchTestBase::CreateRegistrationId(
    const std::string& tag,
    BackgroundFetchRegistrationId* registration_id) {
  DCHECK(registration_id);
  DCHECK(registration_id->is_null());

  GURL script_url(kTestScriptUrl);

  int64_t service_worker_registration_id = kInvalidServiceWorkerRegistrationId;

  {
    base::RunLoop run_loop;
    embedded_worker_test_helper_.context()->RegisterServiceWorker(
        origin_.GetURL(), script_url, nullptr /* provider_host */,
        base::Bind(&DidRegisterServiceWorker, &service_worker_registration_id,
                   run_loop.QuitClosure()));

    run_loop.Run();
  }

  if (service_worker_registration_id == kInvalidServiceWorkerRegistrationId) {
    ADD_FAILURE() << "Could not obtain a valid Service Worker registration";
    return false;
  }

  scoped_refptr<ServiceWorkerRegistration> service_worker_registration;

  {
    base::RunLoop run_loop;
    embedded_worker_test_helper_.context()->storage()->FindRegistrationForId(
        service_worker_registration_id, origin_.GetURL(),
        base::Bind(&DidFindServiceWorkerRegistration,
                   &service_worker_registration, run_loop.QuitClosure()));

    run_loop.Run();
  }

  // Wait for the worker to be activated.
  base::RunLoop().RunUntilIdle();

  if (!service_worker_registration) {
    ADD_FAILURE() << "Could not find the new Service Worker registration.";
    return false;
  }

  *registration_id = BackgroundFetchRegistrationId(
      service_worker_registration->id(), origin_, tag);

  service_worker_registrations_.push_back(
      std::move(service_worker_registration));
  return true;
}

ServiceWorkerFetchRequest
BackgroundFetchTestBase::CreateRequestWithProvidedResponse(
    const std::string& method,
    const std::string& url,
    std::unique_ptr<TestResponse> response) {
  GURL gurl(url);

  // Register the |response| with the faked download manager.
  download_manager_->RegisterResponse(gurl, std::move(response));

  // Create a ServiceWorkerFetchRequest request with the same information.
  return ServiceWorkerFetchRequest(gurl, method, ServiceWorkerHeaderMap(),
                                   Referrer(), false /* is_reload */);
}

MockDownloadManager* BackgroundFetchTestBase::download_manager() {
  return download_manager_;
}

}  // namespace content
