// Copyright 2026 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/live_caption/google_api_translation_dispatcher.h"

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/metrics_hashes.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "components/live_caption/translation_dispatcher.h"
#include "components/live_caption/translation_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/header_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"

namespace captions {

// Request constants.
const size_t kMaxMessageSize = 1024 * 1024;  // 1MB
constexpr char kTranslateUrl[] =
    "https://translation.googleapis.com/language/translate/v2?key=%s";
constexpr char kUploadContentType[] = "application/json";
constexpr char kHttpErrorMessageTemplate[] =
    "Failed to recieve response, got errror: %s";

// Response constants.
constexpr char kDataKey[] = "data";
constexpr char kTranslationsKey[] = "translations";
constexpr char kTranslatedTextKey[] = "translatedText";

// Histogram names.
constexpr char kTranslationDispatcherLoadResultHistogram[] =
    "Accessibility.LiveTranslate.TranslationDispatcher.LoadResult";
constexpr char kTranslationDispatcherParseResultHistogram[] =
    "Accessibility.LiveTranslate.TranslationDispatcher.ParseResult";

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(TranslationDispatcherLoadResult)
enum class TranslationDispatcherLoadResult {
  kSuccess = 0,
  kNetworkError = 1,
  kHttpError = 2,
  kEmptyResponse = 3,
  kMaxValue = kEmptyResponse,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:TranslationDispatcherLoadResult)

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(TranslationDispatcherParseResult)
enum class TranslationDispatcherParseResult {
  kSuccess = 0,
  kJsonParseError = 1,
  kValueNotDict = 2,
  kDataDictNotFound = 3,
  kTranslationsListNotFound = 4,
  kTranslatedTextNotDict = 5,
  kTranslatedTextNotFound = 6,
  kMaxValue = kTranslatedTextNotFound,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:TranslationDispatcherParseResult)

GoogleApiTranslationDispatcher::GoogleApiTranslationDispatcher() = default;

GoogleApiTranslationDispatcher::GoogleApiTranslationDispatcher(
    std::string api_key,
    content::BrowserContext* browser_context)
    : api_key_(api_key), browser_context_(browser_context) {}

GoogleApiTranslationDispatcher::~GoogleApiTranslationDispatcher() = default;

void GoogleApiTranslationDispatcher::GetTranslation(
    std::string_view result,
    std::string_view source_language,
    std::string_view target_language,
    TranslateEventCallback callback) {
  if (!url_loader_factory_.is_bound() || !url_loader_factory_.is_connected()) {
    ResetURLLoaderFactory();
  }

  auto resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url =
      GURL(base::StringPrintf(kTranslateUrl, api_key_.c_str()));

  resource_request->method = net::HttpRequestHeaders::kPostMethod;
  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;

  net::NetworkTrafficAnnotationTag traffic_annotation =
      net::DefineNetworkTrafficAnnotation("cloud_speech_recognition",
                                          R"(
        semantics {
          sender: "Live Translate"
          description:
            "Chrome provides the ability to translate captions generated from "
            "output audio by using the Google Cloud Translate web service. "
            "Captions are sent to Google's servers and translated captions are "
            "returned."
          trigger:
            "Triggered in direct response to a user playing a media with audio "
            "with the Live Translate feature enabled."
          data: "Captions to be translated by Google's Cloud Translate web "
            "service."
          destination: GOOGLE_OWNED_SERVICE
          internal {
            contacts {
              email: "evanliu@google.com"
            }
          }
          user_data {
            type: OTHER
          }
          last_reviewed: "2024-10-11"
        }
        policy {
          cookies_allowed: NO
          setting:
            "You can enable or disable this feature via the caption settings "
            "page."
          chrome_policy {
            TranslateEnabled {
                TranslateEnabled: false
            }
          }
        })");
  url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
                                                 traffic_annotation);

  base::DictValue request_body;
  request_body.Set("q", result);
  request_body.Set("source", source_language);
  request_body.Set("target", target_language);
  request_body.Set("format", "text");

  std::optional<std::string> request_body_str = base::WriteJson(request_body);

  url_loader_->AttachStringForUpload(request_body_str.value_or(""),
                                     kUploadContentType);

  // Unretained is safe because |this| owns |url_loader_|.
  url_loader_->DownloadToString(
      url_loader_factory_.get(),
      base::BindOnce(&GoogleApiTranslationDispatcher::OnURLLoadComplete,
                     base::Unretained(this), std::move(callback)),
      kMaxMessageSize);

  base::UmaHistogramSparse("Accessibility.LiveTranslate.TargetLanguage",
                           base::HashMetricName(target_language));
  base::UmaHistogramSparse("Accessibility.LiveTranslate.SourceLanguage",
                           base::HashMetricName(source_language));
}

void GoogleApiTranslationDispatcher::ResetURLLoaderFactory() {
  network::mojom::URLLoaderFactoryParamsPtr params =
      network::mojom::URLLoaderFactoryParams::New();
  params->process_id = network::OriginatingProcessId::browser();
  params->is_trusted = false;
  params->automatically_assign_isolation_info = true;
  network::mojom::NetworkContext* network_context =
      browser_context_->GetDefaultStoragePartition()->GetNetworkContext();
  network_context->CreateURLLoaderFactory(
      url_loader_factory_.BindNewPipeAndPassReceiver(), std::move(params));
}

void GoogleApiTranslationDispatcher::OnURLLoadComplete(
    TranslateEventCallback callback,
    std::optional<std::string> response_body) {
  // Check that the request succeeded. First with Network Errors...
  if (static_cast<net::Error>(url_loader_->NetError()) != net::Error::OK) {
    base::UmaHistogramEnumeration(
        kTranslationDispatcherLoadResultHistogram,
        TranslationDispatcherLoadResult::kNetworkError);
    EmitError(
        std::move(callback),
        base::StringPrintf(kHttpErrorMessageTemplate,
                           net::ErrorToShortString(url_loader_->NetError())));
    return;
  }

  // and then check HTTP errors.
  if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers &&
      !network::IsSuccessfulStatus(
          url_loader_->ResponseInfo()->headers->response_code())) {
    base::UmaHistogramEnumeration(kTranslationDispatcherLoadResultHistogram,
                                  TranslationDispatcherLoadResult::kHttpError);
    EmitError(std::move(callback),
              base::StringPrintf(
                  kHttpErrorMessageTemplate,
                  base::NumberToString(
                      url_loader_->ResponseInfo()->headers->response_code())));
    return;
  }

  // Somehow the request succeeded but the body is empty.
  if (!response_body.has_value()) {
    base::UmaHistogramEnumeration(
        kTranslationDispatcherLoadResultHistogram,
        TranslationDispatcherLoadResult::kEmptyResponse);
    EmitError(std::move(callback),
              "Error parsing response: Translation dispatcher recieved a 2XX "
              "response, but the body was empty");
    return;
  }

  base::UmaHistogramEnumeration(kTranslationDispatcherLoadResultHistogram,
                                TranslationDispatcherLoadResult::kSuccess);

  std::optional<base::DictValue> result =
      base::JSONReader::ReadDict(*response_body, base::JSON_PARSE_RFC);
  if (!result) {
    base::UmaHistogramEnumeration(
        kTranslationDispatcherParseResultHistogram,
        TranslationDispatcherParseResult::kJsonParseError);
    EmitError(std::move(callback), "Error parsing response: value null");
    return;
  }

  const base::DictValue* data_dict = result->FindDict(kDataKey);
  if (!data_dict) {
    base::UmaHistogramEnumeration(
        kTranslationDispatcherParseResultHistogram,
        TranslationDispatcherParseResult::kDataDictNotFound);
    EmitError(std::move(callback),
              "Error parsing response: dictionary not found");
    return;
  }

  const base::ListValue* translations_list =
      data_dict->FindList(kTranslationsKey);
  if (!translations_list || translations_list->empty()) {
    base::UmaHistogramEnumeration(
        kTranslationDispatcherParseResultHistogram,
        TranslationDispatcherParseResult::kTranslationsListNotFound);
    EmitError(std::move(callback),
              "Error parsing response: translations not found");
    return;
  }

  const base::DictValue* translated_text = (*translations_list)[0].GetIfDict();
  if (!translated_text) {
    base::UmaHistogramEnumeration(
        kTranslationDispatcherParseResultHistogram,
        TranslationDispatcherParseResult::kTranslatedTextNotDict);
    EmitError(std::move(callback),
              "Error parsing response: translated list entry not found");
    return;
  }

  const std::string* value = translated_text->FindString(kTranslatedTextKey);
  if (!value) {
    base::UmaHistogramEnumeration(
        kTranslationDispatcherParseResultHistogram,
        TranslationDispatcherParseResult::kTranslatedTextNotFound);
    EmitError(std::move(callback),
              "Error parsing response: translated text not found");
    return;
  }

  base::UmaHistogramEnumeration(kTranslationDispatcherParseResultHistogram,
                                TranslationDispatcherParseResult::kSuccess);
  std::move(callback).Run(TranslateEvent(*value));
}

void GoogleApiTranslationDispatcher::EmitError(
    TranslateEventCallback callback,
    const std::string& message) const {
  std::move(callback).Run(base::unexpected<std::string>(message));
}

void GoogleApiTranslationDispatcher::SetURLLoaderFactoryForTest(  // IN-TEST
    mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory) {
  url_loader_factory_ = std::move(url_loader_factory);
}

}  // namespace captions
