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

#include "ash/scanner/scanner_feedback.h"

#include <cstddef>
#include <optional>
#include <string>
#include <utility>

#include "base/containers/span.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "components/manta/proto/scanner.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace {

using ::base::test::IsJson;

TEST(ScannerFeedbackTest, UnsetActionToDict) {
  base::DictValue dict = ScannerActionToDict(manta::proto::ScannerAction());

  EXPECT_THAT(dict, IsJson("{}"));
}

TEST(ScannerFeedbackTest, NewEventActionToDict) {
  manta::proto::ScannerAction action;
  manta::proto::NewEventAction& new_event = *action.mutable_new_event();
  new_event.set_title("🌏");
  new_event.set_description("formerly \"Geo Sync\"");
  new_event.set_dates("20241014T160000/20241014T161500");
  new_event.set_location("Wonderland");

  base::DictValue dict = ScannerActionToDict(std::move(action));

  EXPECT_THAT(dict, IsJson(R"json({
    "new_event": {
      "title": "🌏",
      "description": "formerly \"Geo Sync\"",
      "dates": "20241014T160000/20241014T161500",
      "location": "Wonderland",
    }
  })json"));
}

TEST(ScannerFeedbackTest, NewContactActionToDict) {
  manta::proto::ScannerAction action;
  manta::proto::NewContactAction& new_contact = *action.mutable_new_contact();
  new_contact.set_given_name("André");
  new_contact.set_family_name("François");
  new_contact.set_email("afrancois@example.com");
  new_contact.set_phone("+61400000000");
  manta::proto::NewContactAction::EmailAddress& home_email =
      *new_contact.add_email_addresses();
  home_email.set_value("afrancois@example.com");
  home_email.set_type("home");
  manta::proto::NewContactAction::EmailAddress& work_email =
      *new_contact.add_email_addresses();
  work_email.set_value("afrancois@work.example.com");
  work_email.set_type("work");
  manta::proto::NewContactAction::PhoneNumber& mobile_number =
      *new_contact.add_phone_numbers();
  mobile_number.set_value("+61400000000");
  mobile_number.set_type("mobile");
  manta::proto::NewContactAction::PhoneNumber& home_number =
      *new_contact.add_phone_numbers();
  home_number.set_value("+61390000000");
  home_number.set_type("home");

  base::DictValue dict = ScannerActionToDict(std::move(action));

  EXPECT_THAT(dict, IsJson(R"json({
    "new_contact": {
      "given_name": "André",
      "family_name": "François",
      "email": "afrancois@example.com",
      "phone": "+61400000000",
      "email_addresses": [
        {
          "value": "afrancois@example.com",
          "type": "home",
        },
        {
          "value": "afrancois@work.example.com",
          "type": "work",
        },
      ],
      "phone_numbers": [
        {
          "value": "+61400000000",
          "type": "mobile",
        },
        {
          "value": "+61390000000",
          "type": "home",
        },
      ],
    }
  })json"));
}

TEST(ScannerFeedbackTest, NewGoogleDocActionToDict) {
  manta::proto::ScannerAction action;
  manta::proto::NewGoogleDocAction& new_google_doc =
      *action.mutable_new_google_doc();
  new_google_doc.set_title("Doc Title");
  new_google_doc.set_html_contents("<span>Contents</span>");

  base::DictValue dict = ScannerActionToDict(std::move(action));

  EXPECT_THAT(dict, IsJson(R"json({
    "new_google_doc": {
      "title": "Doc Title",
      "html_contents": "<span>Contents</span>",
    }
  })json"));
}

TEST(ScannerFeedbackTest, NewGoogleSheetActionToDict) {
  manta::proto::ScannerAction action;
  manta::proto::NewGoogleSheetAction& new_google_sheet =
      *action.mutable_new_google_sheet();
  new_google_sheet.set_title("Sheet Title");
  new_google_sheet.set_csv_contents("a,b\n1,2");

  base::DictValue dict = ScannerActionToDict(std::move(action));

  EXPECT_THAT(dict, IsJson(R"json({
    "new_google_sheet": {
      "title": "Sheet Title",
      "csv_contents": "a,b\n1,2",
    }
  })json"));
}

TEST(ScannerFeedbackTest, CopyToClipboardActionToDict) {
  manta::proto::ScannerAction action;
  manta::proto::CopyToClipboardAction& copy_to_clipboard =
      *action.mutable_copy_to_clipboard();
  copy_to_clipboard.set_plain_text("Hello");
  copy_to_clipboard.set_html_text("<b>Hello</b>");

  base::DictValue dict = ScannerActionToDict(std::move(action));

  EXPECT_THAT(dict, IsJson(R"json({
    "copy_to_clipboard": {
      "plain_text": "Hello",
      "html_text": "<b>Hello</b>",
    }
  })json"));
}

struct ValueToUserFacingStringTestCase {
  std::string name;
  std::string json_string;
  std::optional<std::string> user_string;
  size_t depth_limit = 20;
  size_t output_limit = 1000;
};

class ScannerValueToUserFacingStringTest
    : public ::testing::TestWithParam<ValueToUserFacingStringTestCase> {};

// These tests assume that `Dict`s are iterated through in sorted order. If this
// ever changes, these tests should be updated to use gMock's
// `UnorderedElementsAre` matcher over the lines of the user-facing string.
INSTANTIATE_TEST_SUITE_P(
    ,
    ScannerValueToUserFacingStringTest,
    ::testing::ValuesIn({
        ValueToUserFacingStringTestCase{
            .name = "Basic",
            .json_string =
                R"json({
                  "new_event": {
                    "title": "🌏",
                    "description": "formerly \"Geo Sync\"",
                    "dates": "20241014T160000/20241014T161500",
                    "location": "Wonderland",
                  }
                })json",
            .user_string =
                R"user(new_event.dates: 20241014T160000/20241014T161500
new_event.description: formerly "Geo Sync"
new_event.location: Wonderland
new_event.title: 🌏
)user",
        },
        ValueToUserFacingStringTestCase{
            .name = "Complex",
            .json_string =
                R"json({
                  "new_contact": {
                    "given_name": "André",
                    "family_name": "François",
                    "email": "afrancois@example.com",
                    "phone": "+61400000000",
                    "email_addresses": [
                      {
                        "value": "afrancois@example.com",
                        "type": "home",
                      },
                      {
                        "value": "afrancois@work.example.com",
                        "type": "work",
                      },
                    ],
                    "phone_numbers": [
                      {
                        "value": "+61400000000",
                        "type": "mobile",
                      },
                      {
                        "value": "+61390000000",
                        "type": "home",
                      },
                    ],
                  }
                })json",
            .user_string =
                R"user(new_contact.email: afrancois@example.com
new_contact.email_addresses.0.type: home
new_contact.email_addresses.0.value: afrancois@example.com
new_contact.email_addresses.1.type: work
new_contact.email_addresses.1.value: afrancois@work.example.com
new_contact.family_name: François
new_contact.given_name: André
new_contact.phone: +61400000000
new_contact.phone_numbers.0.type: mobile
new_contact.phone_numbers.0.value: +61400000000
new_contact.phone_numbers.1.type: home
new_contact.phone_numbers.1.value: +61390000000
)user",
        },
        ValueToUserFacingStringTestCase{
            .name = "DoesNotHtmlEscapeStrings",
            .json_string =
                R"json({
                  "copy_to_clipboard": {
                    "plain_text": "Hello",
                    "html_text": "\u003Cb>Hello\u003C/b>",
                  }
                })json",
            .user_string =
                R"user(copy_to_clipboard.html_text: <b>Hello</b>
copy_to_clipboard.plain_text: Hello
)user",
        },
        ValueToUserFacingStringTestCase{
            .name = "PlainFalse",
            .json_string = "false",
            .user_string = "false\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "PlainTrue",
            .json_string = "true",
            .user_string = "true\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "PlainNull",
            .json_string = "null",
            .user_string = "null\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "PlainNumber",
            .json_string = "123",
            .user_string = "123\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "PlainString",
            .json_string = "\"string\"",
            .user_string = "string\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "NestedDict",
            .json_string =
                R"json({
                  "a": {
                    "b": {
                      "c": 1,
                    },
                    "d": 2,
                  },
                  "e": 3,
                })json",
            .user_string = "a.b.c: 1\na.d: 2\ne: 3\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "NestedList",
            .json_string =
                R"json([
                  [
                    [1],
                    2,
                  ],
                  3,
                ])json",
            .user_string = "0.0.0: 1\n0.1: 2\n1: 3\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "DictInList",
            .json_string =
                R"json([
                  {
                    "a": 1,
                  },
                  {
                    "b": 2,
                  },
                ])json",
            .user_string = "0.a: 1\n1.b: 2\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "ListInDict",
            .json_string =
                R"json({
                  "a": [1, 2],
                  "b": [3, 4],
                })json",
            .user_string = "a.0: 1\na.1: 2\nb.0: 3\nb.1: 4\n",
        },
        ValueToUserFacingStringTestCase{
            .name = "HitsDepthLimitWithDictionary",
            .json_string = R"json({"a":{"b":{"c": 1}}})json",
            .user_string = "a.b.c: 1\n",
            .depth_limit = 3,
        },
        ValueToUserFacingStringTestCase{
            .name = "ExceedsDepthLimitWithDictionary",
            .json_string = R"json({"a":{"b":{"c": 1}}})json",
            .user_string = std::nullopt,
            .depth_limit = 2,
        },
        ValueToUserFacingStringTestCase{
            .name = "HitsDepthLimitWithList",
            .json_string = R"json([[[1]]])json",
            .user_string = "0.0.0: 1\n",
            .depth_limit = 3,
        },
        ValueToUserFacingStringTestCase{
            .name = "ExceedsDepthLimitWithList",
            .json_string = R"json([[[1]]])json",
            .user_string = std::nullopt,
            .depth_limit = 2,
        },
        ValueToUserFacingStringTestCase{
            .name = "HitsDepthLimitWithDictionaryAndList",
            .json_string = R"json([[{"a": 1}]])json",
            .user_string = "0.0.a: 1\n",
            .depth_limit = 3,
        },
        ValueToUserFacingStringTestCase{
            .name = "ExceedsDepthLimitWithDictionaryAndList",
            .json_string = R"json([[{"a": 1}]])json",
            .user_string = std::nullopt,
            .depth_limit = 2,
        },
        ValueToUserFacingStringTestCase{
            .name = "HitsOutputLimit",
            .json_string = "123456789",
            .user_string = "123456789\n",
            .output_limit = 10,
        },
        ValueToUserFacingStringTestCase{
            .name = "ExceedsOutputLimit",
            .json_string = "123456789",
            .user_string = std::nullopt,
            .output_limit = 9,
        },
    }),
    [](const ::testing::TestParamInfo<ValueToUserFacingStringTestCase>& info) {
      return info.param.name;
    });

TEST_P(ScannerValueToUserFacingStringTest,
       ValueToUserFacingStringMatchesExpected) {
  base::Value value = base::test::ParseJson(GetParam().json_string);

  std::optional<std::string> user_string = ValueToUserFacingString(
      value, GetParam().depth_limit, GetParam().output_limit);

  EXPECT_EQ(GetParam().user_string, user_string);
}

TEST(ScannerFeedbackTest, ValueToUserFacingStringReturnsNulloptWithBinary) {
  base::Value binary_value(base::as_byte_span("binaryvalue"));

  std::optional<std::string> user_string = ValueToUserFacingString(
      binary_value, /*depth_limit=*/20, /*output_limit=*/1000);

  EXPECT_EQ(user_string, std::nullopt);
}

TEST(ScannerFeedbackTest,
     ValueToUserFacingStringReturnsNulloptWithNestedBinary) {
  base::DictValue nested_value;
  nested_value.EnsureDict("a")->EnsureList("b")->Append(
      base::Value(base::as_byte_span("binaryvalue")));

  std::optional<std::string> user_string = ValueToUserFacingString(
      nested_value, /*depth_limit=*/20, /*output_limit=*/1000);

  EXPECT_EQ(user_string, std::nullopt);
}

}  // namespace
}  // namespace ash
