// 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/controlled_frame/scoped_test_driver_proxy.h"

#include <optional>
#include <string>

#include "base/check.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/values.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_devtools_protocol_client.h"

namespace controlled_frame {
namespace {

void HandleSetPermission(content::TestDevToolsProtocolClient& devtools_client,
                         const base::DictValue& arguments) {
  const std::string* name = arguments.FindString("name");
  const std::string* state = arguments.FindString("state");
  const std::string* origin = arguments.FindString("origin");
  CHECK(name && state) << "Invalid set_permission arguments: " << arguments;

  base::DictValue args;
  args.Set("permission", base::DictValue().Set("name", *name));
  args.Set("setting", *state);
  if (origin) {
    args.Set("origin", *origin);
  }
  devtools_client.SendCommandSync("Browser.setPermission", std::move(args));
}

}  // namespace

ScopedTestDriverProxy::ScopedTestDriverProxy(
    content::RenderFrameHost* render_frame_host)
    : render_frame_host_(render_frame_host), message_queue_(render_frame_host) {
  devtools_client_.AttachToWebContents(
      content::WebContents::FromRenderFrameHost(render_frame_host));
  HandleMessages();
}

ScopedTestDriverProxy::~ScopedTestDriverProxy() {
  devtools_client_.DetachProtocolClient();
}

// static
const char* ScopedTestDriverProxy::testdriver_override_script_src() {
  return R"(
    (function() {
      function sendMessage(actionName, arguments) {
        const nonce = `cb${Math.random().toString(16).slice(2)}`;
        return new Promise((resolve) => {
          window[nonce] = resolve;
          domAutomationController.send({
            action: actionName,
            callback: `window.${nonce}()`,
            arguments,
          });
        });
      }

      window.test_driver_internal.set_permission =
          function(permission_params, context) {
        return sendMessage('set_permission', {
          name: permission_params.descriptor.name,
          state: permission_params.state,
          origin: context?.origin
        });
      };
    })())";
}

void ScopedTestDriverProxy::HandleMessages() {
  while (message_queue_.HasMessages()) {
    std::string message_json;
    CHECK(message_queue_.PopMessage(&message_json));
    std::optional<base::DictValue> message = base::JSONReader::ReadDict(
        message_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
    if (!message.has_value()) {
      LOG(ERROR) << "Invalid message from domAutomationController: "
                 << message_json;
      continue;
    }
    std::string* action = message->FindString("action");
    std::string* callback = message->FindString("callback");
    base::DictValue* arguments = message->FindDict("arguments");
    if (!action || !callback || !arguments) {
      LOG(ERROR) << "Invalid message from domAutomationController: "
                 << message_json;
      continue;
    }

    if (*action == "set_permission") {
      HandleSetPermission(devtools_client_, *arguments);
    }

    CHECK(content::ExecJs(render_frame_host_, *callback));
  }
  message_queue_.SetOnMessageAvailableCallback(base::BindOnce(
      &ScopedTestDriverProxy::HandleMessages, base::Unretained(this)));
}

}  // namespace controlled_frame
