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

#include "chrome/browser/support_tool/support_tool_util.h"

#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.h"
#include "chrome/browser/feedback/system_logs/log_sources/crash_ids_source.h"
#include "chrome/browser/feedback/system_logs/log_sources/device_event_log_source.h"
#include "chrome/browser/feedback/system_logs/log_sources/memory_details_log_source.h"
#include "chrome/browser/feedback/system_logs/log_sources/performance_log_source.h"
#include "chrome/browser/support_tool/data_collection_module.pb.h"
#include "chrome/browser/support_tool/policy_data_collector.h"
#include "chrome/browser/support_tool/signin_data_collector.h"
#include "chrome/browser/support_tool/support_tool_handler.h"
#include "chrome/browser/support_tool/system_log_source_data_collector_adaptor.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "build/chromeos_buildflags.h"
#include "chrome/browser/ash/system_logs/app_service_log_source.h"
#include "chrome/browser/ash/system_logs/bluetooth_log_source.h"
#include "chrome/browser/ash/system_logs/command_line_log_source.h"
#include "chrome/browser/ash/system_logs/connected_input_devices_log_source.h"
#include "chrome/browser/ash/system_logs/dbus_log_source.h"
#include "chrome/browser/ash/system_logs/iwlwifi_dump_log_source.h"
#include "chrome/browser/ash/system_logs/touch_log_source.h"
#include "chrome/browser/ash/system_logs/traffic_counters_log_source.h"
#include "chrome/browser/ash/system_logs/virtual_keyboard_log_source.h"
#include "chrome/browser/support_tool/ash/chrome_user_logs_data_collector.h"
#include "chrome/browser/support_tool/ash/network_health_data_collector.h"
#include "chrome/browser/support_tool/ash/network_routes_data_collector.h"
#include "chrome/browser/support_tool/ash/shill_data_collector.h"
#include "chrome/browser/support_tool/ash/system_logs_data_collector.h"
#include "chrome/browser/support_tool/ash/system_state_data_collector.h"
#include "chrome/browser/support_tool/ash/ui_hierarchy_data_collector.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "components/user_manager/user_manager.h"

#if BUILDFLAG(IS_CHROMEOS_WITH_HW_DETAILS)
#include "chrome/browser/ash/system_logs/reven_log_source.h"
#endif  // BUILDFLAG(IS_CHROMEOS_WITH_HW_DETAILS)
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#include "chrome/browser/support_tool/updater_data_collector.h"
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)

namespace {

// Data collector types that can work on every platform.
constexpr support_tool::DataCollectorType kDataCollectors[] = {
    support_tool::CHROME_INTERNAL,       support_tool::CRASH_IDS,
    support_tool::MEMORY_DETAILS,        support_tool::POLICIES,
    support_tool::CHROMEOS_DEVICE_EVENT, support_tool::PERFORMANCE,
    support_tool::SIGN_IN_STATE};

// Data collector types can only work on Chrome OS Ash.
constexpr support_tool::DataCollectorType kDataCollectorsChromeosAsh[] = {
    support_tool::CHROMEOS_UI_HIERARCHY,
    support_tool::CHROMEOS_COMMAND_LINE,
    support_tool::CHROMEOS_IWL_WIFI_DUMP,
    support_tool::CHROMEOS_TOUCH_EVENTS,
    support_tool::CHROMEOS_DBUS,
    support_tool::CHROMEOS_NETWORK_ROUTES,
    support_tool::CHROMEOS_SHILL,
    support_tool::CHROMEOS_SYSTEM_STATE,
    support_tool::CHROMEOS_SYSTEM_LOGS,
    support_tool::CHROMEOS_CHROME_USER_LOGS,
    support_tool::CHROMEOS_BLUETOOTH_FLOSS,
    support_tool::CHROMEOS_CONNECTED_INPUT_DEVICES,
    support_tool::CHROMEOS_TRAFFIC_COUNTERS,
    support_tool::CHROMEOS_VIRTUAL_KEYBOARD,
    support_tool::CHROMEOS_NETWORK_HEALTH,
    support_tool::CHROMEOS_APP_SERVICE,
    support_tool::CHROMEOS_KIOSK_APP_LEVEL_LOGS,
};

// Data collector types that can only work on if IS_CHROMEOS_WITH_HW_DETAILS
// flag is turned on. IS_CHROMEOS_WITH_HW_DETAILS flag will be turned on for
// Chrome OS Flex devices.
constexpr support_tool::DataCollectorType kDataCollectorsChromeosHwDetails[] = {
    support_tool::CHROMEOS_REVEN};

// Data collector types that can only work on Linux, macOS, and Windows.
constexpr support_tool::DataCollectorType kDataCollectorsLinuxMacWin[] = {
    support_tool::CHROME_UPDATER,
};

}  // namespace

std::unique_ptr<SupportToolHandler> GetSupportToolHandler(
    std::string case_id,
    std::string email_address,
    std::string issue_description,
    std::optional<std::string> upload_id,
    Profile* profile,
    std::set<support_tool::DataCollectorType> included_data_collectors) {
  std::unique_ptr<SupportToolHandler> handler =
      std::make_unique<SupportToolHandler>(case_id, email_address,
                                           issue_description, upload_id);
  for (const auto& data_collector_type : included_data_collectors) {
    switch (data_collector_type) {
      case support_tool::CHROME_INTERNAL:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Fetches internal Chrome logs.",
                std::make_unique<system_logs::ChromeInternalLogSource>()));
        break;
      case support_tool::CRASH_IDS:
        handler->AddDataCollector(std::make_unique<
                                  SystemLogSourceDataCollectorAdaptor>(
            "Extracts the most recent crash IDs (if any) and exports them into "
            "crash_report_ids and all_crash_report_ids files.",
            std::make_unique<system_logs::CrashIdsSource>()));
        break;
      case support_tool::MEMORY_DETAILS:
        handler->AddDataCollector(std::make_unique<
                                  SystemLogSourceDataCollectorAdaptor>(
            "Fetches memory usage details and exports them into mem_usage and "
            "mem_usage_with_title files.",
            std::make_unique<system_logs::MemoryDetailsLogSource>()));
        break;
      case support_tool::POLICIES:
        // TODO: b/252962974 - Collect device policies even when `profile` is
        // nullptr.
        if (profile) {
          handler->AddDataCollector(
              std::make_unique<PolicyDataCollector>(profile));
        }
        break;
      case support_tool::CHROMEOS_DEVICE_EVENT:
        handler->AddDataCollector(std::make_unique<
                                  SystemLogSourceDataCollectorAdaptor>(
            "Fetches entries for 'network_event_log' and 'device_event_log'.",
            std::make_unique<system_logs::DeviceEventLogSource>()));
        break;
      case support_tool::PERFORMANCE:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Gathers performance relevant data such as if high efficiency "
                "or battery saver mode is active and details about current "
                "battery state.",
                std::make_unique<system_logs::PerformanceLogSource>()));
        break;
      case support_tool::SIGN_IN_STATE:
        // Sign-in data is not available when there's no signed-in user.
        if (profile
#if BUILDFLAG(IS_CHROMEOS)
            && user_manager::UserManager::Get()->IsUserLoggedIn()
#endif  // BUILDFLAG(IS_CHROMEOS)
        ) {
          handler->AddDataCollector(
              std::make_unique<SigninDataCollector>(profile));
        }
        break;
#if BUILDFLAG(IS_CHROMEOS)
      case support_tool::CHROMEOS_UI_HIERARCHY:
        handler->AddDataCollector(std::make_unique<UiHierarchyDataCollector>());
        break;
      case support_tool::CHROMEOS_NETWORK_ROUTES:
        handler->AddDataCollector(
            std::make_unique<NetworkRoutesDataCollector>());
        break;
      case support_tool::CHROMEOS_COMMAND_LINE:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Gathers log data from various scripts/programs. Creates and "
                "exports data into these files: alsa controls, cras, "
                "audio_diagnostics, env, disk_usage.",
                std::make_unique<system_logs::CommandLineLogSource>()));
        break;
      case support_tool::CHROMEOS_IWL_WIFI_DUMP:
        handler->AddDataCollector(std::make_unique<
                                  SystemLogSourceDataCollectorAdaptor>(
            "Fetches debug dump information from Intel Wi-Fi NICs that will be "
            "produced when those NICs have issues such as firmware crashes. "
            "Exports the logs into a file named iwlwifi_dump.",
            std::make_unique<system_logs::IwlwifiDumpLogSource>()));
        break;
      case support_tool::CHROMEOS_TOUCH_EVENTS:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Fetches touch events, touchscreen and touchpad logs.",
                std::make_unique<system_logs::TouchLogSource>()));
        break;
      case support_tool::CHROMEOS_DBUS:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Fetches DBus usage statistics. Creates and exports data into "
                "these files: dbus_details, dbus_summary.",
                std::make_unique<system_logs::DBusLogSource>()));
        break;
      case support_tool::CHROMEOS_SHILL:
        handler->AddDataCollector(std::make_unique<ShillDataCollector>());
        break;
      case support_tool::CHROMEOS_SYSTEM_STATE:
        handler->AddDataCollector(std::make_unique<SystemStateDataCollector>());
        break;
      case support_tool::CHROMEOS_SYSTEM_LOGS:
        // Set `requested_logs` as empty to request all log for now.
        handler->AddDataCollector(std::make_unique<SystemLogsDataCollector>(
            /*requested_logs=*/std::set<base::FilePath>()));
        break;
      case support_tool::CHROMEOS_PERIODIC_LOG_UPLOAD_SYSTEM_LOGS: {
        // For periodic log upload only a certain subset of system logs is
        // necessary.
        const std::set<base::FilePath> periodicLogUploadSystemLogs = {
            base::FilePath("bios_info"),
            base::FilePath("chrome_system_log"),
            base::FilePath("chrome_system_log.PREVIOUS"),
            base::FilePath("eventlog"),
            base::FilePath("extensions.log"),
            base::FilePath("netlog"),
            base::FilePath("syslog"),
            base::FilePath("ui_log"),
            base::FilePath("update_engine.log"),
            // TODO(b/352028256): the following log is not currently read by
            // debugd. Don't forget to add them during the periodic log
            // upload migration.
            base::FilePath("extensions.1.log"),
        };
        handler->AddDataCollector(std::make_unique<SystemLogsDataCollector>(
            periodicLogUploadSystemLogs));
        break;
      }
      case support_tool::CHROMEOS_CHROME_USER_LOGS:
        // User session must be active to read user data from Cryptohome.
        if (ash::IsUserBrowserContext(profile)) {
          handler->AddDataCollector(
              std::make_unique<ChromeUserLogsDataCollector>());
        }
        break;
      case support_tool::CHROMEOS_BLUETOOTH_FLOSS:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Fetches the Bluetooth Floss enabled or disabled information "
                "on the device.",
                std::make_unique<system_logs::BluetoothLogSource>()));
        break;
      case support_tool::CHROMEOS_CONNECTED_INPUT_DEVICES:
        handler->AddDataCollector(std::make_unique<
                                  SystemLogSourceDataCollectorAdaptor>(
            "Fetches the information about connected input devices.",
            std::make_unique<system_logs::ConnectedInputDevicesLogSource>()));
        break;
      case support_tool::CHROMEOS_TRAFFIC_COUNTERS:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Fetches traffic counters information.",
                std::make_unique<system_logs::TrafficCountersLogSource>()));
        break;
      case support_tool::CHROMEOS_VIRTUAL_KEYBOARD:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Fetches the virtual keyboard information.",
                std::make_unique<system_logs::VirtualKeyboardLogSource>()));
        break;
      case support_tool::CHROMEOS_NETWORK_HEALTH:
        handler->AddDataCollector(
            std::make_unique<NetworkHealthDataCollector>());
        break;
      case support_tool::CHROMEOS_APP_SERVICE:
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Gathers information from app service about installed and "
                "running apps.",
                std::make_unique<system_logs::AppServiceLogSource>()));
        break;
      case support_tool::CHROMEOS_KIOSK_APP_LEVEL_LOGS: {
        if (chromeos::IsKioskSession()) {
          const std::set<base::FilePath> kioskAppLevelSystemLogs = {
              base::FilePath("kiosk_apps.log"),
              base::FilePath("kiosk_apps.1.log"),
          };
          handler->AddDataCollector(std::make_unique<SystemLogsDataCollector>(
              kioskAppLevelSystemLogs));
        }
        break;
      }
      case support_tool::CHROMEOS_REVEN:
#if BUILDFLAG(IS_CHROMEOS_WITH_HW_DETAILS)
        handler->AddDataCollector(
            std::make_unique<SystemLogSourceDataCollectorAdaptor>(
                "Collect Hardware data for ChromeOS Flex devices via "
                "cros_healthd calls.",
                std::make_unique<system_logs::RevenLogSource>()));
#endif  // BUILDFLAG(IS_CHROMEOS_WITH_HW_DETAILS)
        break;
#endif  // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
      case support_tool::CHROME_UPDATER:
        handler->AddDataCollector(std::make_unique<UpdaterDataCollector>());
        break;
#endif
      default:
        break;
    }
  }
  return handler;
}

std::vector<support_tool::DataCollectorType> GetAllDataCollectors() {
  std::vector<support_tool::DataCollectorType> data_collectors;
  for (const auto& type : kDataCollectors) {
    data_collectors.push_back(type);
  }
  for (const auto& type : kDataCollectorsChromeosAsh) {
    data_collectors.push_back(type);
  }
  for (const auto& type : kDataCollectorsChromeosHwDetails) {
    data_collectors.push_back(type);
  }
  for (const auto& type : kDataCollectorsLinuxMacWin) {
    data_collectors.push_back(type);
  }
  return data_collectors;
}

std::vector<support_tool::DataCollectorType>
GetAllAvailableDataCollectorsOnDevice() {
  std::vector<support_tool::DataCollectorType> data_collectors;
  for (const auto& type : kDataCollectors) {
    data_collectors.push_back(type);
  }
#if BUILDFLAG(IS_CHROMEOS)
  for (const auto& type : kDataCollectorsChromeosAsh) {
    data_collectors.push_back(type);
  }
#if BUILDFLAG(IS_CHROMEOS_WITH_HW_DETAILS)
  for (const auto& type : kDataCollectorsChromeosHwDetails) {
    data_collectors.push_back(type);
  }
#endif  // BUILDFLAG(IS_CHROMEOS_WITH_HW_DETAILS)
#endif  // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
  for (const auto& type : kDataCollectorsLinuxMacWin) {
    data_collectors.push_back(type);
  }
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
  return data_collectors;
}

base::FilePath GetFilepathToExport(base::FilePath target_directory,
                                   const std::string& filename_prefix,
                                   const std::string& case_id,
                                   base::Time timestamp) {
  std::string filename = filename_prefix + "_";
  if (!case_id.empty()) {
    filename += case_id + "_";
  }
  base::Time::Exploded exploded;
  timestamp.UTCExplode(&exploded);
  std::string timestamp_str = base::StringPrintf(
      "UTC%04d%02d%02d_%02d%02d", exploded.year, exploded.month,
      exploded.day_of_month, exploded.hour, exploded.minute);
  return target_directory.AppendASCII(filename + timestamp_str);
}

std::string SupportToolErrorsToString(
    const std::set<SupportToolError>& errors) {
  std::vector<std::string_view> error_messages;
  error_messages.reserve(errors.size());
  for (const auto& error : errors) {
    error_messages.push_back(error.error_message);
  }
  return base::JoinString(error_messages, ", ");
}
