// 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/test/chromedriver/prompt_behavior.h"

#include <string>
#include <tuple>

#include "base/json/json_writer.h"
#include "base/test/values_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
static const std::unordered_map<std::string, PromptHandlerConfiguration>
    kPromptBehaviorToConfigurationMap = {
        {prompt_behavior::kAccept, {PromptHandlerType::kAccept, false}},
        {prompt_behavior::kAcceptAndNotify, {PromptHandlerType::kAccept, true}},
        {prompt_behavior::kDismiss, {PromptHandlerType::kDismiss, false}},
        {prompt_behavior::kDismissAndNotify,
         {PromptHandlerType::kDismiss, true}},
        {prompt_behavior::kIgnore, {PromptHandlerType::kIgnore, true}},
};
}  // namespace

void AssertPromptBehavior(
    PromptBehavior* prompt_behavior,
    std::vector<std::tuple<std::string, PromptHandlerType, bool>>
        expectations) {
  for (auto [dialog_type, expected_handler_type, expected_notify] :
       expectations) {
    PromptHandlerConfiguration actual_handler_configuration;
    Status status = prompt_behavior->GetConfiguration(
        dialog_type, actual_handler_configuration);
    ASSERT_EQ(kOk, status.code());
    ASSERT_EQ(expected_handler_type, actual_handler_configuration.type);
    ASSERT_EQ(expected_notify, actual_handler_configuration.notify);
  }
}

void AssertJsonEqual(const std::string& expected_str,
                     base::Value actual_value) {
  std::string expected_normalized_str, actual_normalized_str;
  ASSERT_TRUE(base::JSONWriter::Write(
      base::Value(base::test::ParseJsonDict(expected_str)),
      &expected_normalized_str));
  ASSERT_TRUE(base::JSONWriter::Write(actual_value, &actual_normalized_str));
  ASSERT_EQ(expected_normalized_str, actual_normalized_str);
}

void ExpectInvalidPromptBehavior(const base::Value& value) {
  PromptBehavior prompt_behavior;
  EXPECT_EQ(kInvalidArgument,
            PromptBehavior::Create(true, value, prompt_behavior).code());
}

TEST(PromptBehaviorTest, StrEmpty) {
  PromptBehavior prompt_behavior;
  Status status =
      PromptBehavior::Create(true, base::Value(""), prompt_behavior);
  ASSERT_TRUE(status.IsError());
}

TEST(PromptBehaviorTest, InvalidType) {
  PromptBehavior prompt_behavior;
  Status status =
      PromptBehavior::Create(true, base::Value(123), prompt_behavior);
  ASSERT_TRUE(status.IsError());
}

TEST(PromptBehaviorTest, StrInvalid) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(
      true, base::Value("some invalid string"), prompt_behavior);
  ASSERT_TRUE(status.IsError());
}

TEST(PromptBehaviorTest, StrAcceptLegacy) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(
      false, base::Value(prompt_behavior::kAccept), prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  AssertPromptBehavior(
      &prompt_behavior,
      {
          {dialog_types::kAlert, PromptHandlerType::kAccept, true},
          {dialog_types::kBeforeUnload, PromptHandlerType::kAccept, false},
          {dialog_types::kConfirm, PromptHandlerType::kAccept, true},
          {dialog_types::kPrompt, PromptHandlerType::kAccept, true},
      });
}

TEST(PromptBehaviorTest, StrAccept) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(
      true, base::Value(prompt_behavior::kAccept), prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  AssertPromptBehavior(
      &prompt_behavior,
      {
          {dialog_types::kAlert, PromptHandlerType::kAccept, false},
          {dialog_types::kBeforeUnload, PromptHandlerType::kAccept, false},
          {dialog_types::kConfirm, PromptHandlerType::kAccept, false},
          {dialog_types::kPrompt, PromptHandlerType::kAccept, false},
      });
}

TEST(PromptBehaviorTest, StrAcceptAndNotify) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(
      true, base::Value(prompt_behavior::kAcceptAndNotify), prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  AssertPromptBehavior(
      &prompt_behavior,
      {
          {dialog_types::kAlert, PromptHandlerType::kAccept, true},
          {dialog_types::kBeforeUnload, PromptHandlerType::kAccept, false},
          {dialog_types::kConfirm, PromptHandlerType::kAccept, true},
          {dialog_types::kPrompt, PromptHandlerType::kAccept, true},
      });
}

TEST(PromptBehaviorTest, StrDismiss) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(
      true, base::Value(prompt_behavior::kDismiss), prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  AssertPromptBehavior(
      &prompt_behavior,
      {
          {dialog_types::kAlert, PromptHandlerType::kDismiss, false},
          {dialog_types::kBeforeUnload, PromptHandlerType::kAccept, false},
          {dialog_types::kConfirm, PromptHandlerType::kDismiss, false},
          {dialog_types::kPrompt, PromptHandlerType::kDismiss, false},
      });
}

TEST(PromptBehaviorTest, StrDismissAndNotify) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(
      true, base::Value(prompt_behavior::kDismissAndNotify), prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  AssertPromptBehavior(
      &prompt_behavior,
      {
          {dialog_types::kAlert, PromptHandlerType::kDismiss, true},
          {dialog_types::kBeforeUnload, PromptHandlerType::kAccept, false},
          {dialog_types::kConfirm, PromptHandlerType::kDismiss, true},
          {dialog_types::kPrompt, PromptHandlerType::kDismiss, true},
      });
}

TEST(PromptBehaviorTest, StrIgnore) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(
      true, base::Value(prompt_behavior::kIgnore), prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  AssertPromptBehavior(
      &prompt_behavior,
      {
          {dialog_types::kAlert, PromptHandlerType::kIgnore, true},
          {dialog_types::kBeforeUnload, PromptHandlerType::kAccept, false},
          {dialog_types::kConfirm, PromptHandlerType::kIgnore, true},
          {dialog_types::kPrompt, PromptHandlerType::kIgnore, true},
      });
}

TEST(PromptBehaviorTest, EmptyDict) {
  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(true, base::Value(base::DictValue()),
                                         prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  AssertPromptBehavior(
      &prompt_behavior,
      {
          {dialog_types::kAlert, PromptHandlerType::kDismiss, true},
          {dialog_types::kBeforeUnload, PromptHandlerType::kAccept, false},
          {dialog_types::kConfirm, PromptHandlerType::kDismiss, true},
          {dialog_types::kPrompt, PromptHandlerType::kDismiss, true},
      });
}

TEST(PromptBehaviorTest, DictUnknownKey) {
  ExpectInvalidPromptBehavior(
      base::Value(base::test::ParseJsonDict(R"({"foo": "accept"})")));
}

TEST(PromptBehaviorTest, DictMisspelledKey) {
  ExpectInvalidPromptBehavior(
      base::Value(base::test::ParseJsonDict(R"({"beforeunload": "bar"})")));
}

TEST(PromptBehaviorTest, DictAllValidKeys) {
  constexpr char kCapabilityJson[] = R"({
    "alert": "accept",
    "beforeUnload": "accept",
    "confirm": "dismiss",
    "default": "ignore",
    "file": "dismiss",
    "prompt": "ignore"
  })";
  PromptBehavior prompt_behavior;

  Status status = PromptBehavior::Create(
      true, base::Value(base::test::ParseJsonDict(kCapabilityJson)),
      prompt_behavior);

  ASSERT_TRUE(status.IsOk()) << status.message();
  AssertJsonEqual(kCapabilityJson, prompt_behavior.CapabilityView());
}

TEST(PromptBehaviorTest, DictInvalidFileHandler) {
  ExpectInvalidPromptBehavior(
      base::Value(base::test::ParseJsonDict(R"({"file": false})")));
}

class PromptBehaviorCreateDictInvariantTest
    : public testing::TestWithParam<
          std::tuple<std::string, std::string, std::string>> {};

INSTANTIATE_TEST_SUITE_P(
    CreateDict,
    PromptBehaviorCreateDictInvariantTest,
    testing::Combine(
        // prompt_type_in_capability
        testing::Values(dialog_types::kAlert,
                        dialog_types::kBeforeUnload,
                        dialog_types::kConfirm,
                        dialog_types::kPrompt),
        // prompt_type_to_check
        testing::Values(dialog_types::kAlert,
                        dialog_types::kBeforeUnload,
                        dialog_types::kConfirm,
                        dialog_types::kPrompt),
        // handler_type_str
        testing::Values(prompt_behavior::kAccept,
                        prompt_behavior::kDismiss,
                        prompt_behavior::kIgnore,
                        prompt_behavior::kAcceptAndNotify,
                        prompt_behavior::kDismissAndNotify)));

TEST_P(PromptBehaviorCreateDictInvariantTest, CreateDictWithDefault) {
  auto [prompt_type_in_capability, prompt_type_to_check, handler_type_str] =
      GetParam();
  std::string default_prompt_type = handler_type_str == prompt_behavior::kAccept
                                        ? prompt_behavior::kDismiss
                                        : prompt_behavior::kAccept;
  base::DictValue requested_capability;
  requested_capability.Set("default", default_prompt_type);
  requested_capability.Set(prompt_type_in_capability, handler_type_str);

  PromptBehavior prompt_behavior;

  Status status = PromptBehavior::Create(
      true, base::Value(std::move(requested_capability)), prompt_behavior);
  ASSERT_TRUE(status.IsOk());

  std::vector<std::tuple<std::string, PromptHandlerType, bool>> expectations;
  PromptHandlerConfiguration expected_configuration =
      prompt_type_to_check == prompt_type_in_capability
          ? kPromptBehaviorToConfigurationMap.at(handler_type_str)
          : kPromptBehaviorToConfigurationMap.at(default_prompt_type);
  expectations.emplace_back(prompt_type_to_check, expected_configuration.type,
                            expected_configuration.notify);

  AssertPromptBehavior(&prompt_behavior, expectations);
}

TEST_P(PromptBehaviorCreateDictInvariantTest, CreateDictWithoutDefault) {
  auto [prompt_type_in_capability, prompt_type_to_check, handler_type_str] =
      GetParam();
  std::string expected_behavior =
      prompt_type_to_check == prompt_type_in_capability ? handler_type_str
      : prompt_type_to_check == dialog_types::kBeforeUnload
          // beforeUnload is accepted be default.
          ? prompt_behavior::kAccept
          // other prompts are dismissed by default.
          : prompt_behavior::kDismissAndNotify;

  base::DictValue requested_capability;
  requested_capability.Set(prompt_type_in_capability, handler_type_str);

  PromptBehavior prompt_behavior;

  Status status = PromptBehavior::Create(
      true, base::Value(std::move(requested_capability)), prompt_behavior);
  ASSERT_TRUE(status.IsOk());

  std::vector<std::tuple<std::string, PromptHandlerType, bool>> expectations;
  PromptHandlerConfiguration expected_configuration =
      kPromptBehaviorToConfigurationMap.at(expected_behavior);
  expectations.emplace_back(prompt_type_to_check, expected_configuration.type,
                            expected_configuration.notify);

  AssertPromptBehavior(&prompt_behavior, expectations);
}

class PromptBehaviorStrCapabilityViewTest
    : public testing::TestWithParam<std::string> {};

INSTANTIATE_TEST_SUITE_P(StrCapabilityView,
                         PromptBehaviorStrCapabilityViewTest,
                         // capability_str
                         testing::Values(prompt_behavior::kAccept,
                                         prompt_behavior::kDismiss,
                                         prompt_behavior::kIgnore,
                                         prompt_behavior::kAcceptAndNotify,
                                         prompt_behavior::kDismissAndNotify));

TEST_P(PromptBehaviorStrCapabilityViewTest, StrCapabilityView) {
  std::string capability_str = GetParam();

  PromptBehavior prompt_behavior;
  Status status = PromptBehavior::Create(true, base::Value(capability_str),
                                         prompt_behavior);
  ASSERT_TRUE(status.IsOk());
  ASSERT_EQ(capability_str, prompt_behavior.CapabilityView().GetString());
}

class PromptBehaviorDictCapabilityViewInvariantTest
    : public testing::TestWithParam<std::string> {};

INSTANTIATE_TEST_SUITE_P(DictCapabilityView,
                         PromptBehaviorDictCapabilityViewInvariantTest,
                         // capability_json
                         testing::Values("{}",
                                         R"({
                                           "default":"ignore",
                                           "alert":"dismiss",
                                           "beforeUnload":"accept",
                                           "confirm":"accept and notify",
                                           "prompt":"dismiss and notify"
                                         })"));

TEST_P(PromptBehaviorDictCapabilityViewInvariantTest, DictCapabilityView) {
  std::string capability_json = GetParam();

  PromptBehavior prompt_behavior;

  Status status = PromptBehavior::Create(
      true, base::Value(base::test::ParseJsonDict(capability_json)),
      prompt_behavior);
  ASSERT_TRUE(status.IsOk());

  AssertJsonEqual(capability_json, prompt_behavior.CapabilityView());
}

class PromptBehaviorStrMapperOptionsViewInvariantTest
    : public testing::TestWithParam<std::tuple<std::string, std::string>> {};

INSTANTIATE_TEST_SUITE_P(
    StrMapperOptionsView,
    PromptBehaviorStrMapperOptionsViewInvariantTest,
    // {capability_str, expected_capability_json}
    testing::Values(std::make_tuple(prompt_behavior::kAccept,
                                    R"({
                                      "alert":"accept",
                                      "beforeUnload":"accept",
                                      "confirm":"accept",
                                      "prompt":"accept"
                                    })"),
                    std::make_tuple(prompt_behavior::kAcceptAndNotify,
                                    R"({
                                      "alert":"accept",
                                      "beforeUnload":"accept",
                                      "confirm":"accept",
                                      "prompt":"accept"
                                    })"),
                    std::make_tuple(prompt_behavior::kDismiss,
                                    R"({
                                      "alert":"dismiss",
                                      "beforeUnload":"accept",
                                      "confirm":"dismiss",
                                      "prompt":"dismiss"
                                    })"),
                    std::make_tuple(prompt_behavior::kDismissAndNotify,
                                    R"({
                                      "alert":"dismiss",
                                      "beforeUnload":"accept",
                                      "confirm":"dismiss",
                                      "prompt":"dismiss"
                                    })"),
                    std::make_tuple(prompt_behavior::kIgnore,
                                    R"({
                                      "alert":"ignore",
                                      "beforeUnload":"accept",
                                      "confirm":"ignore",
                                      "prompt":"ignore"
                                    })")));

TEST_P(PromptBehaviorStrMapperOptionsViewInvariantTest, StrMapperOptionsView) {
  auto [capability_str, expected_capability_json] = GetParam();
  //  for (auto capability_json : test_cases) {
  //    auto [capability_str, expected_capability_str] = capability_json;
  PromptBehavior prompt_behavior;

  Status status = PromptBehavior::Create(true, base::Value(capability_str),
                                         prompt_behavior);
  ASSERT_TRUE(status.IsOk());

  AssertJsonEqual(expected_capability_json,
                  prompt_behavior.MapperOptionsView());
  //  }
}

class PromptBehaviorDictMapperOptionsViewInvariantTest
    : public testing::TestWithParam<std::tuple<std::string, std::string>> {};

INSTANTIATE_TEST_SUITE_P(
    DictMapperOptionsView,
    PromptBehaviorDictMapperOptionsViewInvariantTest,
    // {capability_json, expected_capability_json}
    testing::Values(std::make_tuple(R"({"default":"accept"})",
                                    R"({
                                      "alert":"accept",
                                      "beforeUnload":"accept",
                                      "confirm":"accept",
                                      "prompt":"accept"
                                    })"),
                    std::make_tuple(R"({"default":"dismiss"})",
                                    R"({
                                      "alert":"dismiss",
                                      "beforeUnload":"dismiss",
                                      "confirm":"dismiss",
                                      "prompt":"dismiss"
                                    })"),
                    std::make_tuple(R"({"default":"ignore"})",
                                    R"({
                                      "alert":"ignore",
                                      "beforeUnload":"ignore",
                                      "confirm":"ignore",
                                      "prompt":"ignore"
                                    })"),
                    std::make_tuple(R"({
                                      "default":"ignore",
                                      "alert":"accept",
                                    })",
                                    R"({
                                      "alert":"accept",
                                      "beforeUnload":"ignore",
                                      "confirm":"ignore",
                                      "prompt":"ignore"
                                    })"),
                    std::make_tuple(R"({
                                      "default":"ignore",
                                      "beforeUnload":"accept",
                                    })",
                                    R"({
                                      "alert":"ignore",
                                      "beforeUnload":"accept",
                                      "confirm":"ignore",
                                      "prompt":"ignore"
                                    })"),
                    std::make_tuple(R"({
                                      "default":"ignore",
                                      "confirm":"accept",
                                    })",
                                    R"({
                                      "alert":"ignore",
                                      "beforeUnload":"ignore",
                                      "confirm":"accept",
                                      "prompt":"ignore"
                                    })"),
                    std::make_tuple(R"({
                                      "default":"ignore",
                                      "prompt":"accept",
                                    })",
                                    R"({
                                      "alert":"ignore",
                                      "beforeUnload":"ignore",
                                      "confirm":"ignore",
                                      "prompt":"accept"
                                    })")));
TEST_P(PromptBehaviorDictMapperOptionsViewInvariantTest,
       DictMapperOptionsView) {
  auto [capability_json, expected_capability_json] = GetParam();
  PromptBehavior prompt_behavior;

  Status status = PromptBehavior::Create(
      true, base::Value(base::test::ParseJsonDict(capability_json)),
      prompt_behavior);
  ASSERT_TRUE(status.IsOk());

  AssertJsonEqual(expected_capability_json,
                  prompt_behavior.MapperOptionsView());
}
