// Copyright 2018 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 "components/journey/journey_info_fetcher.h"

#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "components/journey/journey_info_json_request.h"
#include "services/data_decoder/public/cpp/safe_json_parser.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

namespace journey {

namespace {
const char kChromeSyncScope[] = "https://www.googleapis.com/auth/chromememex";
}  // namespace

JourneyInfoFetcher::JourneyInfoFetcher(
    identity::IdentityManager* identity_manager,
    const scoped_refptr<network::SharedURLLoaderFactory>& loader_factory)
    : identity_manager_(identity_manager), loader_factory_(loader_factory) {}

JourneyInfoFetcher::~JourneyInfoFetcher() = default;

void JourneyInfoFetcher::FetchJourneyInfo(
    std::vector<int64_t> timestamps,
    FetchResponseAvailableCallback callback) {
  if (!identity_manager_->HasPrimaryAccount()) {
    FetchFinished(std::move(callback), /*result=*/nullptr,
                  "Primary Account is not Available. Sign in is required");
    return;
  }
  pending_requests_.emplace(std::move(timestamps), std::move(callback));
  RequestOAuthTokenService();
}

void JourneyInfoFetcher::RequestOAuthTokenService() {
  if (token_fetcher_)
    return;

  OAuth2TokenService::ScopeSet scopes{kChromeSyncScope};
  token_fetcher_ = std::make_unique<identity::PrimaryAccountAccessTokenFetcher>(
      "journey_info", identity_manager_, scopes,
      base::BindOnce(&JourneyInfoFetcher::AccessTokenFetchFinished,
                     base::Unretained(this)),
      identity::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable);
}

void JourneyInfoFetcher::AccessTokenFetchFinished(
    GoogleServiceAuthError error,
    identity::AccessTokenInfo access_token_info) {
  DCHECK(token_fetcher_);
  std::unique_ptr<identity::PrimaryAccountAccessTokenFetcher>
      token_fetcher_deleter(std::move(token_fetcher_));

  if (error.state() != GoogleServiceAuthError::NONE) {
    AccessTokenError(error);
    return;
  }

  DCHECK(!access_token_info.token.empty());

  while (!pending_requests_.empty()) {
    std::pair<std::vector<int64_t>, FetchResponseAvailableCallback>
        timestamp_and_callback = std::move(pending_requests_.front());
    pending_requests_.pop();
    StartRequest(timestamp_and_callback.first,
                 std::move(timestamp_and_callback.second),
                 access_token_info.token);
  }
}

void JourneyInfoFetcher::AccessTokenError(const GoogleServiceAuthError& error) {
  DCHECK_NE(error.state(), GoogleServiceAuthError::NONE);
  DLOG(WARNING) << "JourneyInfoFetcher::AccessTokenError "
                   "Unable to get token: "
                << error.ToString();

  while (!pending_requests_.empty()) {
    std::pair<std::vector<int64_t>, FetchResponseAvailableCallback>
        timestamp_and_callback = std::move(pending_requests_.front());
    pending_requests_.pop();

    FetchFinished(std::move(timestamp_and_callback.second), /*result=*/nullptr,
                  error.ToString());
  }
}

void JourneyInfoFetcher::StartRequest(const std::vector<int64_t>& timestamps,
                                      FetchResponseAvailableCallback callback,
                                      const std::string& oauth_access_token) {
  JourneyInfoJsonRequest::Builder builder;
  builder.SetTimestamps(timestamps)
      .SetAuthentication(
          base::StringPrintf("bearer %s", oauth_access_token.c_str()))
      .SetParseJsonCallback(base::BindRepeating(
          &data_decoder::SafeJsonParser::Parse,
          /*connector=*/nullptr));  // This is an Android-only component,
                                    // connector is unused on Android.
  auto json_request = builder.Build();
  JourneyInfoJsonRequest* raw_request = json_request.get();
  raw_request->Start(
      base::BindOnce(&JourneyInfoFetcher::JsonRequestDone,
                     base::Unretained(this), std::move(json_request),
                     std::move(callback)),
      loader_factory_);
}

const std::string& JourneyInfoFetcher::GetLastJsonForDebugging() const {
  return last_fetch_json_;
}

void JourneyInfoFetcher::JsonRequestDone(
    std::unique_ptr<JourneyInfoJsonRequest> request,
    FetchResponseAvailableCallback callback,
    std::unique_ptr<base::Value> result,
    const std::string& error_detail) {
  DCHECK(request);

  last_fetch_json_ = request->GetResponseString();
  FetchFinished(std::move(callback), std::move(result), error_detail);
}

void JourneyInfoFetcher::FetchFinished(FetchResponseAvailableCallback callback,
                                       std::unique_ptr<base::Value> result,
                                       const std::string& error_detail) {
  DCHECK((result && !result->is_none()) || error_detail != "");

  // Returned |result| can be empty while |error_detail| is "".
  std::move(callback).Run(std::move(result), error_detail);
}

}  // namespace journey
