// 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 "chrome/browser/ash/printing/enterprise/managed_printer_translator.h"

#include <cstdint>
#include <limits>
#include <optional>
#include <string>

#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/ash/printing/enterprise/managed_printer_configuration.pb.h"
#include "chrome/browser/ash/printing/enterprise/print_job_options_translator.h"
#include "url/gurl.h"

namespace chromeos {

namespace {

// Top-level field names.
const char kGuid[] = "guid";
const char kDisplayName[] = "display_name";
const char kDescription[] = "description";
const char kUri[] = "uri";
const char kUsbDeviceId[] = "usb_device_id";
const char kPpdResource[] = "ppd_resource";
const char kPrintJobOptions[] = "print_job_options";

// PpdResource field names.
const char kUserSuppliedPpdUri[] = "user_supplied_ppd_uri";
const char kEffectiveModel[] = "effective_model";
const char kAutoconf[] = "autoconf";

// UsbDeviceId field names.
const char kVendorId[] = "vendor_id";
const char kProductId[] = "product_id";
const char kUsbProtocol[] = "usb_protocol";

std::optional<ManagedPrinterConfiguration::PpdResource> PpdResourceFromDict(
    const base::DictValue& ppd_resource) {
  std::optional<bool> autoconf = ppd_resource.FindBool(kAutoconf);
  const std::string* effective_model = ppd_resource.FindString(kEffectiveModel);
  const std::string* user_supplied_ppd_uri =
      ppd_resource.FindString(kUserSuppliedPpdUri);

  bool has_autoconf = autoconf.has_value();
  bool has_effective_model = (effective_model != nullptr);
  bool has_user_supplied_ppd_uri = (user_supplied_ppd_uri != nullptr);

  bool has_one_ppd_resource =
      (has_autoconf + has_effective_model + has_user_supplied_ppd_uri) == 1;
  if (!has_one_ppd_resource) {
    LOG(WARNING) << base::StringPrintf(
        "Could not convert a dictionary to PpdResource: multiple values set "
        "for the 'resource' oneof field: %s",
        ppd_resource.DebugString().c_str());
    return std::nullopt;
  }

  ManagedPrinterConfiguration::PpdResource result;
  if (autoconf.has_value()) {
    result.set_autoconf(autoconf.value());
  }
  if (effective_model) {
    result.set_effective_model(*effective_model);
  }
  if (user_supplied_ppd_uri) {
    result.set_user_supplied_ppd_uri(*user_supplied_ppd_uri);
  }

  return result;
}

std::optional<Printer::PpdReference> ManagedPpdResourceToPpdReference(
    const ManagedPrinterConfiguration::PpdResource& ppd_resource) {
  Printer::PpdReference ppd_reference;
  switch (ppd_resource.resource_case()) {
    case ManagedPrinterConfiguration::PpdResource::kAutoconf: {
      if (!ppd_resource.autoconf()) {
        LOG(WARNING) << base::StringPrintf(
            "Invalid PPD resource - autoconf must either be unset or set to "
            "true: %s",
            ppd_resource.SerializeAsString().c_str());
        return std::nullopt;
      }
      ppd_reference.autoconf = ppd_resource.autoconf();
      break;
    }
    case ManagedPrinterConfiguration::PpdResource::kUserSuppliedPpdUri: {
      GURL url(ppd_resource.user_supplied_ppd_uri());
      if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
        LOG(WARNING) << base::StringPrintf(
            "Invalid PPD resource - invalid user_supplied_ppd_url resource: %s",
            ppd_resource.SerializeAsString().c_str());
        return std::nullopt;
      }
      ppd_reference.user_supplied_ppd_url =
          ppd_resource.user_supplied_ppd_uri();
      break;
    }
    case ManagedPrinterConfiguration::PpdResource::kEffectiveModel: {
      ppd_reference.effective_make_and_model = ppd_resource.effective_model();
      break;
    }
    case ManagedPrinterConfiguration::PpdResource::RESOURCE_NOT_SET: {
      LOG(WARNING) << "Invalid PPD resource - resource is not set";
      return std::nullopt;
    }
  }
  return ppd_reference;
}

std::optional<Printer::UsbDeviceId> UsbDeviceIdFromInts(int vendor_id,
                                                        int product_id) {
  // Verify values are in the uint16 range.
  if (vendor_id < std::numeric_limits<uint16_t>::min() ||
      vendor_id > std::numeric_limits<uint16_t>::max() ||
      product_id < std::numeric_limits<uint16_t>::min() ||
      product_id > std::numeric_limits<uint16_t>::max()) {
    LOG(WARNING) << "vendor_id or product_id out of range: " << vendor_id
                 << ", " << product_id;
    return std::nullopt;
  }

  return Printer::UsbDeviceId(static_cast<uint16_t>(vendor_id),
                              static_cast<uint16_t>(product_id));
}

std::optional<ManagedPrinterConfiguration::UsbDeviceId>
UsbDeviceIdProtoFromDict(const base::DictValue& dict) {
  std::optional<int> vendor_id = dict.FindInt(kVendorId);
  std::optional<int> product_id = dict.FindInt(kProductId);
  std::optional<int> usb_protocol = dict.FindInt(kUsbProtocol);

  // Verify values exist and are integers.
  if (!vendor_id.has_value() || !product_id.has_value()) {
    LOG(WARNING) << "vendor_id or product_id missing or not an int: "
                 << dict.DebugString();
    return std::nullopt;
  }

  // Verify usb_protocol exists and is an integer.
  if (!usb_protocol.has_value()) {
    LOG(WARNING) << "usb_protocol missing or not an int: "
                 << dict.DebugString();
    return std::nullopt;
  }

  auto usb_device_id =
      UsbDeviceIdFromInts(vendor_id.value(), product_id.value());
  if (!usb_device_id.has_value()) {
    return std::nullopt;
  }

  if (!ManagedPrinterConfiguration_UsbProtocol_IsValid(usb_protocol.value()) ||
      usb_protocol.value() ==
          ManagedPrinterConfiguration_UsbProtocol::
              ManagedPrinterConfiguration_UsbProtocol_USB_PROTOCOL_UNSPECIFIED) {
    LOG(WARNING) << "UsbProtocol value invalid: " << usb_protocol.value();
    return std::nullopt;
  }

  auto usb_device_id_proto = ManagedPrinterConfiguration::UsbDeviceId();
  usb_device_id_proto.set_vendor_id(usb_device_id.value().vendor_id);
  usb_device_id_proto.set_product_id(usb_device_id.value().product_id);
  usb_device_id_proto.set_usb_protocol(
      static_cast<ManagedPrinterConfiguration_UsbProtocol>(
          usb_protocol.value()));

  return usb_device_id_proto;
}

}  // namespace

std::optional<ManagedPrinterConfiguration> ManagedPrinterConfigFromDict(
    const base::DictValue& config) {
  const std::string* guid = config.FindString(kGuid);
  const std::string* display_name = config.FindString(kDisplayName);
  const std::string* description = config.FindString(kDescription);
  const std::string* uri = config.FindString(kUri);
  const base::DictValue* usb_device_id_dict = config.FindDict(kUsbDeviceId);
  const base::DictValue* ppd_resource = config.FindDict(kPpdResource);
  const base::DictValue* print_job_options = config.FindDict(kPrintJobOptions);

  ManagedPrinterConfiguration result;
  if (guid) {
    result.set_guid(*guid);
  }
  if (display_name) {
    result.set_display_name(*display_name);
  }
  if (uri) {
    if (usb_device_id_dict) {
      LOG(WARNING) << base::StringPrintf(
          "Could not convert a dictionary to ManagedPrinterConfiguration: "
          "multiple values set for the 'connection_type' oneof field: %s",
          config.DebugString().c_str());
      return std::nullopt;
    }
    result.set_uri(*uri);
  }
  if (usb_device_id_dict) {
    auto usb_device_id = UsbDeviceIdProtoFromDict(*usb_device_id_dict);
    if (!usb_device_id.has_value()) {
      LOG(WARNING) << base::StringPrintf(
          "Could not convert a dictionary to UsbDeviceId: "
          "invalid 'usb_device_id' field: %s",
          usb_device_id_dict->DebugString().c_str());
      return std::nullopt;
    }
    *result.mutable_usb_device_id() = usb_device_id.value();
  }
  if (description) {
    result.set_description(*description);
  }
  if (ppd_resource) {
    auto ppd_resource_opt = PpdResourceFromDict(*ppd_resource);
    if (!ppd_resource_opt) {
      LOG(WARNING) << base::StringPrintf(
          "Could not convert a dictionary to ManagedPrinterConfiguration: "
          "invalid 'ppd_resource' field: %s",
          config.DebugString().c_str());
      return std::nullopt;
    }
    *result.mutable_ppd_resource() = *ppd_resource_opt;
  }
  if (print_job_options) {
    *result.mutable_print_job_options() =
        ManagedPrintOptionsProtoFromDict(*print_job_options);
  }
  return result;
}

std::optional<Printer> PrinterFromManagedPrinterConfig(
    const ManagedPrinterConfiguration& managed_printer) {
  static auto LogRequiredFieldMissing = [](std::string_view field) {
    LOG(WARNING) << "Managed printer is missing required field: " << field;
  };

  if (!managed_printer.has_guid()) {
    LogRequiredFieldMissing(kGuid);
    return std::nullopt;
  }
  if (!managed_printer.has_display_name()) {
    LogRequiredFieldMissing(kDisplayName);
    return std::nullopt;
  }
  if (!managed_printer.has_ppd_resource()) {
    LogRequiredFieldMissing(kPpdResource);
    return std::nullopt;
  }

  Printer printer(managed_printer.guid());
  printer.set_source(Printer::SRC_POLICY);
  printer.set_display_name(managed_printer.display_name());

  if (managed_printer.has_uri()) {
    std::string set_uri_error_message;
    if (!printer.SetUri(managed_printer.uri(), &set_uri_error_message)) {
      LOG(WARNING) << base::StringPrintf(
          "Managed printer '%s' has invalid %s value: %s, error: %s",
          managed_printer.display_name().c_str(), kUri,
          managed_printer.uri().c_str(), set_uri_error_message.c_str());
      return std::nullopt;
    }
  } else if (managed_printer.has_usb_device_id()) {
    std::optional<Printer::UsbDeviceId> usb_device_id =
        UsbDeviceIdFromInts(managed_printer.usb_device_id().vendor_id(),
                            managed_printer.usb_device_id().product_id());
    if (!usb_device_id.has_value()) {
      LOG(WARNING) << base::StringPrintf(
          "Managed printer '%s' has invalid %s value: %d, %d",
          managed_printer.display_name().c_str(), kUsbDeviceId,
          managed_printer.usb_device_id().vendor_id(),
          managed_printer.usb_device_id().product_id());
      return std::nullopt;
    }
    ManagedPrinterConfiguration_UsbProtocol usb_protocol =
        managed_printer.usb_device_id().usb_protocol();
    if (!ManagedPrinterConfiguration_UsbProtocol_IsValid(usb_protocol) ||
        usb_protocol ==
            ManagedPrinterConfiguration_UsbProtocol::
                ManagedPrinterConfiguration_UsbProtocol_USB_PROTOCOL_UNSPECIFIED) {
      LOG(WARNING) << base::StringPrintf(
          "Managed printer '%s' has invalid %s value: %d",
          managed_printer.display_name().c_str(), kUsbProtocol, usb_protocol);
      return std::nullopt;
    }
    printer.set_usb_device_id(usb_device_id.value());
    // Also set the URI since it's needed by CUPS and displayed in the UI.
    if (usb_protocol ==
        ManagedPrinterConfiguration_UsbProtocol::
            ManagedPrinterConfiguration_UsbProtocol_USB_PROTOCOL_LEGACY_USB) {
      printer.SetUri(base::StringPrintf("usb://%04x/%04x?serial",
                                        usb_device_id.value().vendor_id,
                                        usb_device_id.value().product_id));
    } else {
      printer.SetUri(base::StringPrintf("ippusb://%04x_%04x/ipp/print",
                                        usb_device_id.value().vendor_id,
                                        usb_device_id.value().product_id));
    }
  } else {
    LOG(WARNING) << "Managed printer is missing oneof field: " << kUri << " or "
                 << kUsbDeviceId;
    return std::nullopt;
  }

  auto ppd_reference =
      ManagedPpdResourceToPpdReference(managed_printer.ppd_resource());
  if (!ppd_reference.has_value()) {
    LOG(WARNING) << base::StringPrintf(
        "Managed printer '%s' has invalid %s value: %s",
        managed_printer.display_name().c_str(), kPpdResource,
        managed_printer.ppd_resource().SerializeAsString().c_str());
    return std::nullopt;
  }
  *printer.mutable_ppd_reference() = *ppd_reference;

  if (managed_printer.has_description()) {
    printer.set_description(managed_printer.description());
  }

  if (managed_printer.has_print_job_options()) {
    std::optional<Printer::ManagedPrintOptions> print_job_options =
        ChromeOsPrintOptionsFromManagedPrintOptions(
            managed_printer.print_job_options());
    if (!print_job_options) {
      LOG(WARNING) << base::StringPrintf(
          "Managed printer '%s' has invalid %s value: %s",
          managed_printer.display_name().c_str(), kPrintJobOptions,
          managed_printer.print_job_options().SerializeAsString().c_str());
      return std::nullopt;
    }
    printer.set_print_job_options(print_job_options.value());
  }

  return printer;
}

}  // namespace chromeos
