/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "cuttlefish/host/libs/metrics/metrics_conversion.h"

#include <chrono>
#include <string_view>
#include <variant>

#include "fmt/format.h"
#include "google/protobuf/timestamp.pb.h"

#include "cuttlefish/common/libs/utils/host_info.h"
#include "cuttlefish/host/commands/cvd/fetch/builds.h"
#include "cuttlefish/host/libs/config/data_image_policy.h"
#include "cuttlefish/host/libs/metrics/device_event_type.h"
#include "cuttlefish/host/libs/metrics/gce_environment.h"
#include "cuttlefish/host/libs/metrics/github_environment.h"
#include "cuttlefish/host/libs/metrics/guest_metrics.h"
#include "cuttlefish/host/libs/metrics/invoker.h"
#include "cuttlefish/host/libs/web/android_build.h"
#include "external_proto/cf_fetch_build.pb.h"
#include "external_proto/cf_fetch_builds.pb.h"
#include "external_proto/cf_fetch_complete.pb.h"
#include "external_proto/cf_fetch_failure.pb.h"
#include "external_proto/cf_fetch_start.pb.h"
#include "external_proto/cf_flags.pb.h"
#include "external_proto/cf_gce_environment.pb.h"
#include "external_proto/cf_github_actions_environment.pb.h"
#include "external_proto/cf_guest.pb.h"
#include "external_proto/cf_host.pb.h"
#include "external_proto/cf_log.pb.h"
#include "external_proto/cf_metrics_event_v2.pb.h"

namespace cuttlefish {
namespace {

using google::protobuf::Timestamp;
using logs::proto::wireless::android::cuttlefish::CuttlefishLogEvent;
using logs::proto::wireless::android::cuttlefish::events::CuttlefishBuild;
using logs::proto::wireless::android::cuttlefish::events::CuttlefishFetchBuilds;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishFetchComplete;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishFetchFailure;
using logs::proto::wireless::android::cuttlefish::events::CuttlefishFetchStart;
using logs::proto::wireless::android::cuttlefish::events::CuttlefishFlags;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishFlags_DataPolicy;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishFlags_GpuMode;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishGceEnvironment;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishGitHubActionsEnvironment;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishGitHubActionsEnvironment_Repository;
using logs::proto::wireless::android::cuttlefish::events::CuttlefishGuest;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishGuest_EventType;
using logs::proto::wireless::android::cuttlefish::events::CuttlefishHost;
using logs::proto::wireless::android::cuttlefish::events::
    CuttlefishHost_Invoker;
using logs::proto::wireless::android::cuttlefish::events::CuttlefishHost_OsType;
using logs::proto::wireless::android::cuttlefish::events::MetricsEventV2;

CuttlefishFlags_DataPolicy ConvertDataPolicy(DataImagePolicy policy) {
  switch (policy) {
    case DataImagePolicy::AlwaysCreate:
      return CuttlefishFlags_DataPolicy::
          CuttlefishFlags_DataPolicy_CUTTLEFISH_FLAGS_DATA_POLICY_ALWAYS_CREATE;
    case DataImagePolicy::ResizeUpTo:
      return CuttlefishFlags_DataPolicy::
          CuttlefishFlags_DataPolicy_CUTTLEFISH_FLAGS_DATA_POLICY_RESIZE_UP_TO;
    case DataImagePolicy::Unknown:
      return CuttlefishFlags_DataPolicy::
          CuttlefishFlags_DataPolicy_CUTTLEFISH_FLAGS_DATA_POLICY_UNSPECIFIED;
    case DataImagePolicy::UseExisting:
      return CuttlefishFlags_DataPolicy::
          CuttlefishFlags_DataPolicy_CUTTLEFISH_FLAGS_DATA_POLICY_USE_EXISTING;
  }
}

CuttlefishFlags_GpuMode ConvertGpuMode(GpuMode mode) {
  switch (mode) {
    case GpuMode::Auto:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_AUTO;
    case GpuMode::Custom:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_CUSTOM;
    case GpuMode::DrmVirgl:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_GUEST_VIRGL_RENDERER;
    case GpuMode::Gfxstream:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_GUEST_GFXSTREAM;
    case GpuMode::GfxstreamGuestAngle:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_GUEST_GFXSTREAM_GUEST_ANGLE;
    case GpuMode::GfxstreamGuestAngleHostLavapipe:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_GUEST_GFXSTREAM_GUEST_ANGLE_HOST_LAVAPIPE;
    case GpuMode::GfxstreamGuestAngleHostSwiftshader:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_GUEST_GFXSTREAM_GUEST_ANGLE_HOST_SWIFTSHADER;
    case GpuMode::GuestSwiftshader:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_GUEST_SWIFTSHADER;
    case GpuMode::GuestLavapipe:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_GUEST_LAVAPIPE;
    case GpuMode::None:
      return CuttlefishFlags_GpuMode::
          CuttlefishFlags_GpuMode_CUTTLEFISH_FLAGS_GPU_MODE_NONE;
  }
}

CuttlefishGuest_EventType ConvertDeviceEventType(DeviceEventType event_type) {
  switch (event_type) {
    case DeviceEventType::DeviceInstantiation:
      return CuttlefishGuest_EventType::
          CuttlefishGuest_EventType_CUTTLEFISH_GUEST_EVENT_TYPE_VM_INSTANTIATION;
    case DeviceEventType::DeviceBootStart:
      return CuttlefishGuest_EventType::
          CuttlefishGuest_EventType_CUTTLEFISH_GUEST_EVENT_TYPE_DEVICE_BOOT_START;
    case DeviceEventType::DeviceBootComplete:
      return CuttlefishGuest_EventType::
          CuttlefishGuest_EventType_CUTTLEFISH_GUEST_EVENT_TYPE_DEVICE_BOOT_COMPLETED;
    case DeviceEventType::DeviceStop:
      return CuttlefishGuest_EventType::
          CuttlefishGuest_EventType_CUTTLEFISH_GUEST_EVENT_TYPE_VM_STOP;
    case DeviceEventType::DeviceBootFailed:
      return CuttlefishGuest_EventType::
          CuttlefishGuest_EventType_CUTTLEFISH_GUEST_EVENT_TYPE_DEVICE_BOOT_FAILED;
  }
}

CuttlefishHost_OsType ConvertHostOs(const HostInfo& host_info) {
  switch (host_info.os) {
    case Os::Unknown:
      return CuttlefishHost_OsType::
          CuttlefishHost_OsType_CUTTLEFISH_HOST_OS_TYPE_UNSPECIFIED;
    case Os::Linux:
      switch (host_info.arch) {
        case Arch::Arm:
          return CuttlefishHost_OsType::
              CuttlefishHost_OsType_CUTTLEFISH_HOST_OS_TYPE_LINUX_AARCH32;
        case Arch::Arm64:
          return CuttlefishHost_OsType::
              CuttlefishHost_OsType_CUTTLEFISH_HOST_OS_TYPE_LINUX_AARCH64;
        case Arch::RiscV64:
          return CuttlefishHost_OsType::
              CuttlefishHost_OsType_CUTTLEFISH_HOST_OS_TYPE_LINUX_RISCV64;
        case Arch::X86:
          return CuttlefishHost_OsType::
              CuttlefishHost_OsType_CUTTLEFISH_HOST_OS_TYPE_LINUX_X86;
        case Arch::X86_64:
          return CuttlefishHost_OsType::
              CuttlefishHost_OsType_CUTTLEFISH_HOST_OS_TYPE_LINUX_X86_64;
      }
  }
}

CuttlefishHost_Invoker ConvertInvoker(const Invoker invoker) {
  switch (invoker) {
    case Invoker::Acloud:
      return CuttlefishHost_Invoker::
          CuttlefishHost_Invoker_CUTTLEFISH_HOST_INVOKER_ACLOUD;
    case Invoker::HostOrchestrator:
      return CuttlefishHost_Invoker::
          CuttlefishHost_Invoker_CUTTLEFISH_HOST_INVOKER_HOST_ORCHESTRATOR;
    case Invoker::None:
      return CuttlefishHost_Invoker::
          CuttlefishHost_Invoker_CUTTLEFISH_HOST_INVOKER_NONE;
    case Invoker::Unknown:
      return CuttlefishHost_Invoker::
          CuttlefishHost_Invoker_CUTTLEFISH_HOST_INVOKER_UNSPECIFIED;
  }
}

CuttlefishGitHubActionsEnvironment_Repository ConvertGitHubRepository(
    const GitHubRepository repository) {
  switch (repository) {
    case GitHubRepository::AndroidCuttlefish:
      return CuttlefishGitHubActionsEnvironment_Repository::
          CuttlefishGitHubActionsEnvironment_Repository_CUTTLEFISH_GITHUB_ACTIONS_ENVIRONMENT_REPOSITORY_GOOGLE_ANDROID_CUTTLEFISH;
    case GitHubRepository::CloudAndroidOrchestration:
      return CuttlefishGitHubActionsEnvironment_Repository::
          CuttlefishGitHubActionsEnvironment_Repository_CUTTLEFISH_GITHUB_ACTIONS_ENVIRONMENT_REPOSITORY_GOOGLE_CLOUD_ANDROID_ORCHESTRATION;
    case GitHubRepository::Unknown:
      return CuttlefishGitHubActionsEnvironment_Repository::
          CuttlefishGitHubActionsEnvironment_Repository_CUTTLEFISH_GITHUB_ACTIONS_ENVIRONMENT_REPOSITORY_UNSPECIFIED;
  }
}

struct PopulateEnvironment {
  CuttlefishHost& host;

  void operator()(const GceEnvironment& environment) {
    CuttlefishGceEnvironment& gce = *host.mutable_gce();
    gce.set_numeric_project_id(environment.numeric_project_id);
    gce.set_zone(environment.zone);
  }

  void operator()(GitHubRepository repository) {
    CuttlefishGitHubActionsEnvironment& github = *host.mutable_github();
    github.set_repository(ConvertGitHubRepository(repository));
  }

  void operator()(const UnknownEnvironment&) {}
};

void PopulateCuttlefishHostEnvironment(CuttlefishHost& host,
                                       const Environment& environment) {
  std::visit(PopulateEnvironment{.host = host}, environment);
}

void PopulateCuttlefishHost(CuttlefishHost& host,
                            const HostMetrics& host_metrics) {
  host.set_host_os(ConvertHostOs(host_metrics.os));
  host.set_host_os_version(host_metrics.os.release);
  host.set_invoker(ConvertInvoker(host_metrics.invoker));
  PopulateCuttlefishHostEnvironment(host, host_metrics.environment);
}

void PopulateCuttlefishGuest(CuttlefishGuest& guest,
                             const GuestMetrics& guest_metrics,
                             std::string_view session_id) {
  guest.set_event_type(ConvertDeviceEventType(guest_metrics.event_type));
  guest.set_guest_id(
      fmt::format("{}-{}", session_id, guest_metrics.instance_id));
  guest.set_guest_os_version(guest_metrics.os_version);

  CuttlefishFlags& flags = *guest.mutable_flags();
  flags.set_cpus(guest_metrics.flag_metrics.cpus);
  flags.set_daemon(guest_metrics.flag_metrics.daemon);
  flags.set_data_policy(
      ConvertDataPolicy(guest_metrics.flag_metrics.data_policy));
  flags.set_extra_kernel_cmdline(
      guest_metrics.flag_metrics.extra_kernel_cmdline);
  flags.set_gpu_mode_requested(
      ConvertGpuMode(guest_metrics.flag_metrics.gpu_mode));
  flags.set_guest_enforce_security(
      guest_metrics.flag_metrics.guest_enforce_security);
  flags.set_memory_mb(guest_metrics.flag_metrics.memory_mb);
  flags.set_restart_subprocesses(
      guest_metrics.flag_metrics.restart_subprocesses);
  flags.set_system_image_dir_specified(
      guest_metrics.flag_metrics.system_image_dir_specified);
}

void PopulateCuttlefishBuild(CuttlefishBuild& cf_build, const Build& build) {
  cf_build.set_build_id(std::visit([](auto&& arg) { return arg.id; }, build));
  cf_build.set_target(std::visit([](auto&& arg) { return arg.target; }, build));
  cf_build.set_product(
      std::visit([](auto&& arg) { return arg.product; }, build));
  if (const DeviceBuild* device_build = std::get_if<DeviceBuild>(&build)) {
    cf_build.set_branch(device_build->branch);
  }
}

void PopulateFetchBuilds(CuttlefishFetchBuilds& fetch_builds,
                         const Builds& builds) {
  if (builds.default_build) {
    CuttlefishBuild& base_build = *fetch_builds.mutable_base();
    PopulateCuttlefishBuild(base_build, builds.default_build.value());
  }
  if (builds.system) {
    CuttlefishBuild& system_build = *fetch_builds.mutable_system();
    PopulateCuttlefishBuild(system_build, builds.system.value());
  }
  if (builds.kernel) {
    CuttlefishBuild& kernel_build = *fetch_builds.mutable_kernel();
    PopulateCuttlefishBuild(kernel_build, builds.kernel.value());
  }
  if (builds.boot) {
    CuttlefishBuild& boot_build = *fetch_builds.mutable_boot();
    PopulateCuttlefishBuild(boot_build, builds.boot.value());
  }
  if (builds.bootloader) {
    CuttlefishBuild& bootloader_build = *fetch_builds.mutable_bootloader();
    PopulateCuttlefishBuild(bootloader_build, builds.bootloader.value());
  }
  if (builds.android_efi_loader) {
    CuttlefishBuild& android_efi_loader_build =
        *fetch_builds.mutable_android_efi_loader();
    PopulateCuttlefishBuild(android_efi_loader_build,
                            builds.android_efi_loader.value());
  }
  if (builds.otatools) {
    CuttlefishBuild& otatools_build = *fetch_builds.mutable_otatools();
    PopulateCuttlefishBuild(otatools_build, builds.otatools.value());
  }
  if (builds.test_suites) {
    CuttlefishBuild& test_suites_build = *fetch_builds.mutable_test_suites();
    PopulateCuttlefishBuild(test_suites_build, builds.test_suites.value());
  }
}

struct PopulateFetchEvent {
  MetricsEventV2& event;

  void operator()(const FetchStartMetrics& start_metrics) {
    CuttlefishFetchStart& start = *event.mutable_start();
    start.set_enable_local_caching(start_metrics.enable_local_caching);
    start.set_dynamic_super_image(start_metrics.dynamic_super_image_mixing);
  }

  void operator()(const FetchCompleteMetrics& complete_metrics) {
    CuttlefishFetchComplete& complete = *event.mutable_complete();
    complete.set_status_blocked(complete_metrics.status_blocked);
    complete.set_fetch_size_bytes(complete_metrics.fetch_size_bytes);
    for (const Builds& builds : complete_metrics.fetched_builds) {
      CuttlefishFetchBuilds& fetch_builds = *complete.add_builds();
      PopulateFetchBuilds(fetch_builds, builds);
    }
  }

  void operator()(const FetchFailedMetrics&) { event.mutable_failure(); }
};

void PopulateCuttlefishFetch(MetricsEventV2& event,
                             const FetchMetrics& fetch_metrics) {
  std::visit(PopulateFetchEvent{.event = event}, fetch_metrics);
}

}  // namespace

CuttlefishLogEvent BuildCuttlefishLogEvent(const MetricsData& metrics_data) {
  CuttlefishLogEvent cf_log_event;
  cf_log_event.set_device_type(CuttlefishLogEvent::CUTTLEFISH_DEVICE_TYPE_HOST);
  cf_log_event.set_session_id(metrics_data.session_id);
  cf_log_event.set_cuttlefish_version(metrics_data.cf_common_version);
  Timestamp& timestamp = *cf_log_event.mutable_timestamp_ms();
  timestamp.set_nanos((metrics_data.now.count() % 1000) * 1000000);
  timestamp.set_seconds(metrics_data.now.count() / 1000);

  MetricsEventV2& metrics_event = *cf_log_event.mutable_metrics_event_v2();

  CuttlefishHost& host = *metrics_event.mutable_host();
  PopulateCuttlefishHost(host, metrics_data.host_metrics);

  for (const GuestMetrics& guest_metric : metrics_data.guest_metrics) {
    CuttlefishGuest& guest = *metrics_event.add_guest();
    PopulateCuttlefishGuest(guest, guest_metric, metrics_data.session_id);
  }

  if (metrics_data.fetch_metrics) {
    PopulateCuttlefishFetch(metrics_event, metrics_data.fetch_metrics.value());
  }

  return cf_log_event;
}

}  // namespace cuttlefish
