// Copyright 2018 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/update_client/protocol_serializer_json.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/command_line.h"
#include "base/json/json_writer.h"
#include "base/values.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "components/update_client/activity_data_service.h"

namespace update_client {

std::string ProtocolSerializerJSON::Serialize(
    const protocol_request::Request& request) const {
  base::DictValue root_node;
  base::DictValue request_node;
  request_node.Set("protocol", request.protocol_version);
  request_node.Set("ismachine", request.is_machine);
  request_node.Set("dedup", "cr");
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kComponentUpdaterCompatProtocols)) {
    // Don't change this string without consulting webview-leads. Add new
    // protocols to the 'else' branch below instead.
    request_node.Set("acceptformat", "crx3,download,puff,run");
  } else {
    request_node.Set("acceptformat", "crx3,download,puff,run,xz,zucc");
  }
  if (!request.additional_attributes.empty()) {
    for (const auto& [name, value] : request.additional_attributes) {
      request_node.Set(name, value);
    }
  }
  request_node.Set("sessionid", request.session_id);
  request_node.Set("requestid", request.request_id);
  request_node.Set("@updater", request.updatername);
  request_node.Set("prodversion", request.prodversion);
  request_node.Set("updaterversion", request.updaterversion);
  request_node.Set("@os", request.operating_system);
  request_node.Set("arch", request.arch);
#if BUILDFLAG(IS_WIN)
  if (request.is_wow64) {
    request_node.Set("wow64", request.is_wow64);
  }
#endif  // BUILDFLAG(IS_WIN)
  if (!request.updaterchannel.empty()) {
    request_node.Set("updaterchannel", request.updaterchannel);
  }
  if (!request.prodchannel.empty()) {
    request_node.Set("prodchannel", request.prodchannel);
  }
  if (!request.dlpref.empty()) {
    request_node.Set("dlpref", request.dlpref);
  }
  if (request.domain_joined) {
    request_node.Set("domainjoined", *request.domain_joined);
  }

  // HW platform information.
  base::DictValue hw_node;
  hw_node.Set("physmemory", static_cast<int>(request.hw.physmemory));
  hw_node.Set("sse", request.hw.sse);
  hw_node.Set("sse2", request.hw.sse2);
  hw_node.Set("sse3", request.hw.sse3);
  hw_node.Set("sse41", request.hw.sse41);
  hw_node.Set("sse42", request.hw.sse42);
  hw_node.Set("ssse3", request.hw.ssse3);
  hw_node.Set("avx", request.hw.avx);
  request_node.Set("hw", std::move(hw_node));

  // OS version and platform information.
  base::DictValue os_node;
  os_node.Set("platform", request.os.platform);
  os_node.Set("arch", request.os.arch);
  if (!request.os.version.empty()) {
    os_node.Set("version", request.os.version);
  }
  if (!request.os.service_pack.empty()) {
    os_node.Set("sp", request.os.service_pack);
  }
  request_node.Set("os", std::move(os_node));

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  if (request.updater) {
    const auto& updater = *request.updater;
    base::DictValue updater_node;
    updater_node.Set("name", updater.name);
    updater_node.Set("ismachine", updater.is_machine);
    updater_node.Set("autoupdatecheckenabled",
                     updater.autoupdate_check_enabled);
    updater_node.Set("updatepolicy", updater.update_policy);
    if (!updater.version.empty()) {
      updater_node.Set("version", updater.version);
    }
    if (updater.last_checked) {
      updater_node.Set("lastchecked", *updater.last_checked);
    }
    if (updater.last_started) {
      updater_node.Set("laststarted", *updater.last_started);
    }
    request_node.Set("updaters", std::move(updater_node));
  }
#endif

  base::ListValue app_nodes;
  for (const auto& app : request.apps) {
    base::DictValue app_node;
    app_node.Set("appid", app.app_id);
    app_node.Set("version", app.version);
    if (!app.ap.empty()) {
      app_node.Set("ap", app.ap);
    }
    if (!app.brand_code.empty()) {
      app_node.Set("brand", app.brand_code);
    }
    if (!app.lang.empty()) {
      app_node.Set("lang", app.lang);
    }
    if (app.install_date != kDateUnknown) {
      app_node.Set("installdate", app.install_date);
    }
    if (!app.install_id.empty()) {
      app_node.Set("iid", app.install_id);
    }
    if (!app.install_source.empty()) {
      app_node.Set("installsource", app.install_source);
    }
    if (!app.install_location.empty()) {
      app_node.Set("installedby", app.install_location);
    }
    // TODO(crbug.com/40145897): Test that this is never sent to the server if
    // the machine is not enterprise managed.
    if (!app.release_channel.empty()) {
      app_node.Set("release_channel", app.release_channel);
    }
    if (!app.cohort.empty()) {
      app_node.Set("cohort", app.cohort);
    }
    if (!app.cohort_name.empty()) {
      app_node.Set("cohortname", app.cohort_name);
    }
    if (!app.cohort_hint.empty()) {
      app_node.Set("cohorthint", app.cohort_hint);
    }
    if (app.enabled) {
      app_node.Set("enabled", *app.enabled);
    }

    if (!app.cached_hashes.empty()) {
      base::ListValue hash_list;
      for (const auto& hash : app.cached_hashes) {
        base::DictValue node;
        node.Set("sha256", hash);
        hash_list.Append(std::move(node));
      }
      app_node.Set("cached_items", std::move(hash_list));
    }

    if (app.disabled_reasons && !app.disabled_reasons->empty()) {
      base::ListValue disabled_nodes;
      for (const int disabled_reason : *app.disabled_reasons) {
        base::DictValue disabled_node;
        disabled_node.Set("reason", disabled_reason);
        disabled_nodes.Append(std::move(disabled_node));
      }
      app_node.Set("disabled", std::move(disabled_nodes));
    }

    for (const auto& [name, value] : app.installer_attributes) {
      app_node.Set(name, value);
    }

    if (app.update_check) {
      base::DictValue update_check_node;
      if (app.update_check->is_update_disabled) {
        update_check_node.Set("updatedisabled", true);
      }
      if (app.update_check->rollback_allowed) {
        update_check_node.Set("rollback_allowed", true);
      }
      if (app.update_check->same_version_update_allowed) {
        update_check_node.Set("sameversionupdate", true);
      }
      if (!app.update_check->target_version_prefix.empty()) {
        update_check_node.Set("targetversionprefix",
                              app.update_check->target_version_prefix);
      }
      app_node.Set("updatecheck", std::move(update_check_node));
    }

    if (!app.data.empty()) {
      base::ListValue data_nodes;
      for (const auto& data : app.data) {
        base::DictValue data_node;

        data_node.Set("name", data.name);
        if (data.name == "install") {
          data_node.Set("index", data.install_data_index);
        } else if (data.name == "untrusted") {
          data_node.Set("#text", data.untrusted_data);
        }

        data_nodes.Append(std::move(data_node));
      }

      if (!data_nodes.empty()) {
        app_node.Set("data", std::move(data_nodes));
      }
    }

    if (app.ping) {
      const auto& ping = *app.ping;
      base::DictValue ping_node;
      if (!ping.ping_freshness.empty()) {
        ping_node.Set("ping_freshness", ping.ping_freshness);
      }

      // Output "ad" or "a" only if the this app has been seen 'active'.
      if (ping.date_last_active) {
        ping_node.Set("ad", *ping.date_last_active);
      } else if (ping.days_since_last_active_ping) {
        ping_node.Set("a", *ping.days_since_last_active_ping);
      }

      // Output "rd" if valid or "r" as a last resort roll call metric.
      if (ping.date_last_roll_call) {
        ping_node.Set("rd", *ping.date_last_roll_call);
      } else {
        ping_node.Set("r", ping.days_since_last_roll_call);
      }
      app_node.Set("ping", std::move(ping_node));
    }

    if (app.events) {
      base::ListValue event_nodes;
      for (const auto& event : *app.events) {
        CHECK(!event.empty());
        event_nodes.Append(event.Clone());
      }
      app_node.Set("events", std::move(event_nodes));
    }

    app_nodes.Append(std::move(app_node));
  }

  if (!app_nodes.empty()) {
    request_node.Set("apps", std::move(app_nodes));
  }

  root_node.Set("request", std::move(request_node));

  std::string msg;
  return base::JSONWriter::WriteWithOptions(
             root_node, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
             &msg)
             ? msg
             : std::string();
}

}  // namespace update_client
