// 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 "base/strings/stringprintf.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/smart_card/chromeos_smart_card_delegate.h"
#include "chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "chrome/test/interaction/webcontents_interaction_test_util.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/policy/policy_constants.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features_generated.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/views_switches.h"

namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestTab);
constexpr char kFooReader[] = "foo reader";
constexpr char kBarReader[] = "bar reader";

DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(
    ui::test::PollingStateObserver<std::optional<bool>>,
    kPermissionDecision);

class SmartCardPermissionUiTest
    : public InteractiveBrowserTestMixin<policy::PolicyTest> {
 public:
  void TearDown() override { InteractiveBrowserTestMixin::TearDown(); }

  auto SetSmartCardConnectAllowedFor(const GURL& origin_url) {
    return Do([this, origin_url]() {
      SetPolicy(&policies_, policy::key::kSmartCardConnectAllowedForUrls,
                base::Value(base::ListValue().Append(origin_url.spec())));
      UpdateProviderPolicy(policies_);
    });
  }

  auto SetSmartCardConnectBlockedFor(const GURL& origin_url) {
    return Do([this, origin_url]() {
      SetPolicy(&policies_, policy::key::kSmartCardConnectBlockedForUrls,
                base::Value(base::ListValue().Append(origin_url.spec())));
      UpdateProviderPolicy(policies_);
    });
  }

  void OnPermissionDecided(bool granted) {
    CHECK_EQ(permission_decision_.has_value(), false);
    permission_decision_ = granted;
  }

  auto CheckReaderPermission(bool has_permission) {
    return CheckResult(
        [this]() -> bool {
          return GetSmartCardDelegate()->HasReaderPermission(GetMainFrameHost(),
                                                             kFooReader);
        },
        has_permission);
  }

  auto RequestReaderPermission(const std::string& reader_name = kFooReader) {
    return Do([this]() {
      GetSmartCardDelegate()->RequestReaderPermission(
          GetMainFrameHost(), kFooReader,
          base::BindOnce(&SmartCardPermissionUiTest::OnPermissionDecided,
                         base::Unretained(this)));
    });
  }

  auto BlockPermission(const GURL& origin_url) {
    return Do([this, origin_url]() {
      auto* settings_map =
          HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile());
      settings_map->SetContentSettingDefaultScope(
          origin_url, GURL(), ContentSettingsType::SMART_CARD_GUARD,
          ContentSetting::CONTENT_SETTING_BLOCK);
    });
  }

  auto CheckContentSetting(const GURL& origin_url, ContentSetting setting) {
    return CheckResult(
        [this, origin_url]() -> bool {
          return HostContentSettingsMapFactory::GetForProfile(
                     browser()->GetProfile())
              ->GetContentSetting(origin_url, GURL(),
                                  ContentSettingsType::SMART_CARD_GUARD);
        },
        setting,
        base::StringPrintf("Expects SMART_CARD_GUARD to be set to %u",
                           setting));
  }

  auto CheckEmbargo(const GURL& origin_url, bool embargoed_expectation) {
    return CheckResult(
        [this, origin_url]() -> bool {
          return PermissionDecisionAutoBlockerFactory::GetForProfile(
                     browser()->GetProfile())
              ->IsEmbargoed(origin_url, ContentSettingsType::SMART_CARD_GUARD);
        },
        embargoed_expectation,
        base::StringPrintf("Expects SMART_CARD_GUARD embargoed status to be %u",
                           embargoed_expectation));
  }

  auto CheckPermissionBlocked(const GURL& origin_url,
                              bool blocked_expectation) {
    return CheckResult(
        [this, origin_url]() -> bool {
          return GetSmartCardDelegate()->IsPermissionBlocked(
              GetMainFrameHost());
        },
        blocked_expectation,
        base::StringPrintf(
            "Expects smart card permission blocked status to be %u",
            blocked_expectation));
  }

  auto PressButtonAndWaitResult(ui::ElementIdentifier button_id, bool granted) {
    return Steps(PollState(kPermissionDecision,
                           [this]() { return permission_decision(); }),
                 PressButton(button_id),
                 Log("Wait for the prompt to be hidden."),
                 WaitForHide(PermissionPromptBubbleBaseView::kMainViewId),
                 Log("Wait for the permission decision."),
                 WaitForState(kPermissionDecision, granted),
                 StopObservingState(kPermissionDecision),
                 Do([this]() { permission_decision_.reset(); }));
  }

  std::optional<bool> permission_decision() { return permission_decision_; }

  content::SmartCardDelegate* GetSmartCardDelegate() {
    return content::GetContentClientForTesting()
        ->browser()
        ->GetSmartCardDelegate();
  }

  content::RenderFrameHost& GetMainFrameHost() {
    return *browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetPrimaryMainFrame();
  }

 private:
  void SetUpOnMainThread() override {
    InteractiveBrowserTestMixin::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    ASSERT_TRUE(embedded_https_test_server().Start());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    InteractiveBrowserTestMixin::SetUpCommandLine(command_line);
    // Disables the disregarding of potentially unintended input events.
    command_line->AppendSwitch(
        views::switches::kDisableInputEventActivationProtectionForTesting);
  }

  std::optional<bool> permission_decision_;
  base::test::ScopedFeatureList scoped_feature_list_{
      blink::features::kSmartCard};
  policy::PolicyMap policies_;
};

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, AllowOnce) {
  RunTestSequence(
      InstrumentTab(kTestTab),
      NavigateWebContents(kTestTab, embedded_https_test_server().GetURL(
                                        "a.com", "/simple.html")),
      CheckReaderPermission(/*has_permission=*/false),
      RequestReaderPermission(),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kAllowOnceButtonElementId,
          /*granted=*/true),
      CheckReaderPermission(/*has_permission=*/true));
}

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, AllowAlways) {
  RunTestSequence(
      InstrumentTab(kTestTab),
      NavigateWebContents(kTestTab, embedded_https_test_server().GetURL(
                                        "a.com", "/simple.html")),
      CheckReaderPermission(/*has_permission=*/false),
      RequestReaderPermission(),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      CheckViewProperty(
          PermissionPromptBubbleBaseView::kAllowButtonElementId,
          &views::LabelButton::GetText,
          l10n_util::GetStringUTF16(IDS_SMART_CARD_PERMISSION_ALWAYS_ALLOW)),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kAllowButtonElementId,
          /*granted=*/true),
      CheckReaderPermission(/*has_permission=*/true));
}

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, BlockedByPolicy) {
  RunTestSequence(
      InstrumentTab(kTestTab),
      NavigateWebContents(kTestTab, embedded_https_test_server().GetURL(
                                        "a.com", "/simple.html")),
      CheckReaderPermission(/*has_permission=*/false),
      RequestReaderPermission(),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      CheckViewProperty(
          PermissionPromptBubbleBaseView::kAllowButtonElementId,
          &views::LabelButton::GetText,
          l10n_util::GetStringUTF16(IDS_SMART_CARD_PERMISSION_ALWAYS_ALLOW)),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kAllowButtonElementId,
          /*granted=*/true),
      CheckReaderPermission(/*has_permission=*/true),
      SetSmartCardConnectBlockedFor(
          embedded_https_test_server().GetURL("a.com", "/")),
      CheckReaderPermission(false));
}

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, AllowedByPolicy) {
  RunTestSequence(
      InstrumentTab(kTestTab),
      NavigateWebContents(kTestTab, embedded_https_test_server().GetURL(
                                        "a.com", "/simple.html")),
      CheckReaderPermission(/*has_permission=*/false),
      RequestReaderPermission(),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      CheckViewProperty(PermissionPromptBubbleBaseView::kBlockButtonElementId,
                        &views::LabelButton::GetText,
                        l10n_util::GetStringUTF16(IDS_PERMISSION_DONT_ALLOW)),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kBlockButtonElementId,
          /*granted=*/false),
      CheckReaderPermission(/*has_permission=*/false),
      SetSmartCardConnectAllowedFor(
          embedded_https_test_server().GetURL("a.com", "/")),
      CheckReaderPermission(true));
}

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, Deny) {
  RunTestSequence(
      InstrumentTab(kTestTab),
      NavigateWebContents(kTestTab, embedded_https_test_server().GetURL(
                                        "a.com", "/simple.html")),
      CheckReaderPermission(/*has_permission=*/false),
      RequestReaderPermission(),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      CheckViewProperty(PermissionPromptBubbleBaseView::kBlockButtonElementId,
                        &views::LabelButton::GetText,
                        l10n_util::GetStringUTF16(IDS_PERMISSION_DONT_ALLOW)),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kBlockButtonElementId,
          /*granted=*/false),
      CheckReaderPermission(/*has_permission=*/false));
}

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, ThreeConsecutiveDenies) {
  auto simple_url =
      embedded_https_test_server().GetURL("a.com", "/simple.html");
  RunTestSequence(
      InstrumentTab(kTestTab), NavigateWebContents(kTestTab, simple_url),
      CheckContentSetting(simple_url, CONTENT_SETTING_ASK),
      CheckEmbargo(simple_url, false),
      CheckPermissionBlocked(simple_url, false),
      // 1st
      RequestReaderPermission(kFooReader),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kBlockButtonElementId,
          /*granted=*/false),
      CheckEmbargo(simple_url, false),
      CheckPermissionBlocked(simple_url, false),
      // 2nd
      RequestReaderPermission(kBarReader),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kBlockButtonElementId,
          /*granted=*/false),
      CheckEmbargo(simple_url, false),
      CheckPermissionBlocked(simple_url, false),
      // 3rd
      RequestReaderPermission(kFooReader),
      WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
      PressButtonAndWaitResult(
          PermissionPromptBubbleBaseView::kBlockButtonElementId,
          /*granted=*/false),
      CheckEmbargo(simple_url, true), CheckPermissionBlocked(simple_url, true));
}

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, ThreeNonConsecutiveDenies) {
  auto simple_url =
      embedded_https_test_server().GetURL("a.com", "/simple.html");
  RunTestSequence(InstrumentTab(kTestTab),
                  NavigateWebContents(kTestTab, simple_url),
                  CheckContentSetting(simple_url, CONTENT_SETTING_ASK),
                  CheckEmbargo(simple_url, false),
                  CheckPermissionBlocked(simple_url, false),
                  // 1st - deny
                  RequestReaderPermission(kFooReader),
                  WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
                  PressButtonAndWaitResult(
                      PermissionPromptBubbleBaseView::kBlockButtonElementId,
                      /*granted=*/false),
                  CheckEmbargo(simple_url, false),
                  CheckPermissionBlocked(simple_url, false),
                  // 2nd - allow once
                  RequestReaderPermission(kBarReader),
                  WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
                  PressButtonAndWaitResult(
                      PermissionPromptBubbleBaseView::kAllowOnceButtonElementId,
                      /*granted=*/true),
                  CheckEmbargo(simple_url, false),
                  CheckPermissionBlocked(simple_url, false),
                  // 3rd - deny
                  RequestReaderPermission(kFooReader),
                  WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
                  PressButtonAndWaitResult(
                      PermissionPromptBubbleBaseView::kBlockButtonElementId,
                      /*granted=*/false),
                  CheckEmbargo(simple_url, false),
                  CheckPermissionBlocked(simple_url, false),
                  // 4th - deny
                  RequestReaderPermission(kFooReader),
                  WaitForShow(PermissionPromptBubbleBaseView::kMainViewId),
                  PressButtonAndWaitResult(
                      PermissionPromptBubbleBaseView::kBlockButtonElementId,
                      /*granted=*/false),
                  // 3 denies split by allow - guard setting should not change
                  CheckEmbargo(simple_url, false),
                  CheckPermissionBlocked(simple_url, false));
}

IN_PROC_BROWSER_TEST_F(SmartCardPermissionUiTest, Blocked) {
  auto simple_url =
      embedded_https_test_server().GetURL("a.com", "/simple.html");
  RunTestSequence(InstrumentTab(kTestTab),
                  NavigateWebContents(kTestTab, simple_url),
                  CheckReaderPermission(/*has_permission=*/false),
                  BlockPermission(simple_url),
                  PollState(kPermissionDecision,
                            [this]() { return permission_decision(); }),
                  RequestReaderPermission(),
                  WaitForState(kPermissionDecision, /*granted=*/false),
                  CheckReaderPermission(/*has_permission=*/false));
}

}  // anonymous namespace
