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

#include "chromeos/ash/components/boca/session_api/create_session_request.h"

#include <string>
#include <utility>

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "chromeos/ash/components/boca/proto/roster.pb.h"
#include "chromeos/ash/components/boca/session_api/constants.h"
#include "chromeos/ash/components/boca/session_api/session_parser.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/base_requests.h"
#include "third_party/protobuf/src/google/protobuf/map_field_lite.h"

namespace ash::boca {

namespace {

std::string ParseErrorMsg(const std::string& response_body) {
  std::optional<base::Value> root =
      base::JSONReader::Read(response_body, base::JSON_PARSE_RFC);
  if (!root || !root->is_dict()) {
    return "";
  }

  const std::string* message =
      root->GetDict().FindStringByDottedPath("error.message");
  return message ? *message : "";
}

}  // namespace

//=================CreateSessionRequest================
CreateSessionRequest::CreateSessionRequest(
    google_apis::RequestSender* sender,
    std::string url_base,
    ::boca::UserIdentity teacher,
    base::TimeDelta duration,
    ::boca::Session::SessionState session_state,
    CreateSessionCallback callback)
    : UrlFetchRequestBase(sender,
                          google_apis::ProgressCallback(),
                          google_apis::ProgressCallback()),
      teacher_(std::move(teacher)),
      duration_(duration),
      session_state_(session_state),
      url_base_(url_base),
      callback_(std::move(callback)) {}

CreateSessionRequest ::~CreateSessionRequest() = default;

GURL CreateSessionRequest::GetURL() const {
  auto url = GURL(url_base_).Resolve(base::ReplaceStringPlaceholders(
      kCreateSessionUrlTemplate, {teacher_.gaia_id()}, nullptr));
  return url;
}

google_apis::ApiErrorCode CreateSessionRequest::MapReasonToError(
    google_apis::ApiErrorCode code,
    const std::string& reason) {
  return code;
}

bool CreateSessionRequest::IsSuccessfulErrorCode(
    google_apis::ApiErrorCode error) {
  return error == google_apis::HTTP_SUCCESS;
}

google_apis::HttpRequestMethod CreateSessionRequest::GetRequestType() const {
  return google_apis::HttpRequestMethod::kPost;
}

bool CreateSessionRequest::GetContentData(std::string* upload_content_type,
                                          std::string* upload_content) {
  *upload_content_type = boca::kContentTypeApplicationJson;

  // We have to do manual serialization because Json library only exists in
  // protobuf-full, but chromium only include protobuf-lite.
  base::DictValue root;
  // Session metadata.
  base::DictValue teacher;
  teacher.Set(kGaiaId, teacher_.gaia_id());
  teacher.Set(kFullName, teacher_.full_name());
  teacher.Set(kEmail, teacher_.email());

  root.Set(kTeacher, std::move(teacher));

  base::DictValue duration;
  duration.Set(kSeconds, static_cast<int>(duration_.InSeconds()));
  root.Set(kDuration, std::move(duration));

  root.Set(kSessionState, session_state_);

  // Enable access code
  base::DictValue joinCode;
  joinCode.Set(kJoinCodeEnabled, true);
  root.Set(kJoinCode, std::move(joinCode));

  // Roster info
  if (roster_) {
    base::DictValue roster;
    ParseRosterJsonFromProto(roster_.get(), &roster);
    root.Set(kRoster, std::move(roster));
  }

  base::DictValue student_config;

  // Ontask config
  if (on_task_config_) {
    base::DictValue on_task_config;
    ParseOnTaskConfigJsonFromProto(on_task_config_.get(), &on_task_config);
    student_config.Set(kOnTaskConfig, std::move(on_task_config));
  }

  // Caption Config
  if (captions_config_) {
    base::DictValue caption_config;
    ParseCaptionConfigJsonFromProto(captions_config_.get(), &caption_config);
    student_config.Set(kCaptionsConfig, std::move(caption_config));
  }

  base::DictValue group_student_config;
  group_student_config.Set(kMainStudentGroupName, student_config.Clone());
  // TODO(crbug.com/375051415): We duplicate the session config for access code
  // student for now, this should eventually be moved to server.
  group_student_config.Set(kAccessCodeGroupName, std::move(student_config));
  root.Set(kStudentGroupsConfig, std::move(group_student_config));

  *upload_content = base::WriteJson(root).value_or("");
  return true;
}

void CreateSessionRequest::ProcessURLFetchResults(
    const network::mojom::URLResponseHead* response_head,
    base::FilePath response_file,
    std::string response_body) {
  google_apis::ApiErrorCode error = GetErrorCode();
  switch (error) {
    case google_apis::HTTP_SUCCESS:
      blocking_task_runner()->PostTaskAndReplyWithResult(
          FROM_HERE,
          base::BindOnce(&GetSessionProtoFromJson, std::move(response_body),
                         /*=is_producer*/ true),
          base::BindOnce(&CreateSessionRequest::OnDataParsed,
                         weak_ptr_factory_.GetWeakPtr()));
      break;
    default:
      RunCallbackOnPrematureFailureWithMessage(error, std::move(response_body));
      OnProcessURLFetchResultsComplete();
      break;
  }
}

void CreateSessionRequest::RunCallbackOnPrematureFailure(
    google_apis::ApiErrorCode error) {
  std::move(callback_).Run(base::unexpected(std::make_pair(error, "")));
}

void CreateSessionRequest::RunCallbackOnPrematureFailureWithMessage(
    google_apis::ApiErrorCode error,
    std::string response_body) {
  const std::string& error_msg = ParseErrorMsg(response_body);
  std::move(callback_).Run(base::unexpected(std::make_pair(error, error_msg)));
}

void CreateSessionRequest::OverrideURLForTesting(std::string url) {
  url_base_ = std::move(url);
}

void CreateSessionRequest::OnDataParsed(
    std::unique_ptr<::boca::Session> session) {
  if (!session) {
    std::move(callback_).Run(
        base::unexpected(std::make_pair(google_apis::PARSE_ERROR, "")));
  } else {
    std::move(callback_).Run(std::move(session));
  }
  OnProcessURLFetchResultsComplete();
}
}  // namespace ash::boca
