// Copyright 2022 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/system/unified/power_button.h"

#include "ash/constants/quick_settings_catalogs.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/shell.h"
#include "ash/shutdown_controller_impl.h"
#include "ash/style/icon_button.h"
#include "ash/system/tray/tray_detailed_view.h"
#include "ash/system/unified/quick_settings_footer.h"
#include "ash/system/unified/quick_settings_view.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/system/unified/user_chooser_view.h"
#include "ash/test/ash_test_base.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/user_manager/user_type.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/skia_util.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"

namespace ash {

// Tests for `PowerButton`, which is inited with no user session to test the
// non-login state.
class PowerButtonTest : public NoSessionAshTestBase {
 public:
  PowerButtonTest() = default;
  PowerButtonTest(const PowerButtonTest&) = delete;
  PowerButtonTest& operator=(const PowerButtonTest&) = delete;
  ~PowerButtonTest() override = default;

  void SetUp() override {
    NoSessionAshTestBase::SetUp();

    // Test with the real system tray bubble so that the power button has a real
    // UnifiedSystemTrayController to test clicking on the email item.
    ShowSystemTrayBubble();
  }

 protected:
  void ShowSystemTrayBubble() {
    auto* system_tray = GetPrimaryUnifiedSystemTray();
    system_tray->ShowBubble();
  }

  void BlockUserSession(UserSessionBlockReason block_reason) {
    NoSessionAshTestBase::BlockUserSession(block_reason);
  }

  views::MenuItemView* GetMenuView() {
    return GetPowerButton()->GetMenuViewForTesting();
  }

  bool IsMenuShowing() { return GetPowerButton()->IsMenuShowing(); }

  views::MenuItemView* GetEmailButton() {
    if (!IsMenuShowing()) {
      return nullptr;
    }
    return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_EMAIL_MENU_BUTTON);
  }

  views::View* GetRestartButton() {
    if (!IsMenuShowing()) {
      return nullptr;
    }
    return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_RESTART_MENU_BUTTON);
  }

  views::View* GetPowerOffButton() {
    if (!IsMenuShowing()) {
      return nullptr;
    }
    return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_OFF_MENU_BUTTON);
  }

  views::View* GetSignOutButton() {
    if (!IsMenuShowing()) {
      return nullptr;
    }
    return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_SIGNOUT_MENU_BUTTON);
  }

  views::View* GetLockButton() {
    if (!IsMenuShowing()) {
      return nullptr;
    }
    return GetMenuView()->GetMenuItemByID(VIEW_ID_QS_POWER_LOCK_MENU_BUTTON);
  }

  PowerButton* GetPowerButton() {
    return GetPrimaryUnifiedSystemTray()
        ->bubble()
        ->quick_settings_view()
        ->footer_for_testing()
        ->power_button_for_testing();
  }

  views::ImageView* GetPowerChevronIcon() {
    auto* icon = GetPowerButton()->button_content_->GetViewByID(
        VIEW_ID_QS_POWER_BUTTON_CHEVRON_ICON);
    CHECK(icon);
    return static_cast<views::ImageView*>(icon);
  }

  ui::Layer* GetBackgroundLayer() {
    return GetPowerButton()->background_view_->layer();
  }

  // Simulates mouse press event on the power button.
  void SimulatePowerButtonPress() {
    LeftClickOn(GetPowerButton()->button_content_);
  }

  ui::ImageModel GetExpectedChevronImageModel(bool use_up_chevron) {
    auto icon_color_id = use_up_chevron
                             ? cros_tokens::kCrosSysSystemOnPrimaryContainer
                             : cros_tokens::kCrosSysOnSurface;

    return ui::ImageModel::FromVectorIcon(
        use_up_chevron ? kChevronUpSmallIcon : kChevronDownSmallIcon,
        icon_color_id);
  }

  bool ChevronIconsMatch(bool use_up_chevron) {
    const auto* power_chevron_icon = GetPowerChevronIcon();

    return gfx::BitmapsAreEqual(
        *power_chevron_icon->GetImage().bitmap(),
        *GetExpectedChevronImageModel(use_up_chevron)
             .Rasterize(power_chevron_icon->GetColorProvider())
             .bitmap());
  }

  bool IsUpChevron() { return ChevronIconsMatch(/*use_up_chevron=*/true); }

  bool IsDownChevron() { return ChevronIconsMatch(/*use_up_chevron=*/false); }

  base::HistogramTester histogram_tester_;
};

// `PowerButton` should be with the correct view id and have the UMA tracking
// with the correct catalog name.
TEST_F(PowerButtonTest, PowerButtonHasCorrectViewIdAndUma) {
  SimulateUserLogin(kRegularUserLoginInfo);

  // No metrics logged before clicking on any buttons.
  histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/0);

  // The power button is visible and with the corresponding id.
  EXPECT_TRUE(GetPowerButton()->GetVisible());
  EXPECT_EQ(VIEW_ID_QS_POWER_BUTTON, GetPowerButton()->GetID());

  // Clicks on the power button.
  SimulatePowerButtonPress();
  EXPECT_TRUE(IsMenuShowing());

  histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/1);
  histogram_tester_.ExpectBucketCount("Ash.QuickSettings.Button.Activated",
                                      QsButtonCatalogName::kPowerButton,
                                      /*expected_count=*/1);
}

TEST_F(PowerButtonTest, LockMenuButtonRecordsUma) {
  SimulateUserLogin(kRegularUserLoginInfo);
  // TOOD(crbug.com/383442863): Move this to SimulateUserLogin.
  GetSessionControllerClient()->SetCanLockScreen(true);
  SimulatePowerButtonPress();

  LeftClickOn(GetLockButton());

  // Expect a count of 2 because the power button was activated above.
  histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/2);
  histogram_tester_.ExpectBucketCount("Ash.QuickSettings.Button.Activated",
                                      QsButtonCatalogName::kPowerLockMenuButton,
                                      /*expected_count=*/1);
}

TEST_F(PowerButtonTest, SignOutMenuButtonRecordsUma) {
  SimulateUserLogin(kRegularUserLoginInfo);
  SimulatePowerButtonPress();

  LeftClickOn(GetSignOutButton());

  // Expect a count of 2 because the power button was activated above.
  histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/2);
  histogram_tester_.ExpectBucketCount(
      "Ash.QuickSettings.Button.Activated",
      QsButtonCatalogName::kPowerSignoutMenuButton,
      /*expected_count=*/1);
}

TEST_F(PowerButtonTest, RestartMenuButtonRecordsUma) {
  SimulateUserLogin(kRegularUserLoginInfo);
  SimulatePowerButtonPress();

  LeftClickOn(GetRestartButton());

  // Expect a count of 2 because the power button was activated above.
  histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/2);
  histogram_tester_.ExpectBucketCount(
      "Ash.QuickSettings.Button.Activated",
      QsButtonCatalogName::kPowerRestartMenuButton,
      /*expected_count=*/1);
}

TEST_F(PowerButtonTest, PowerOffMenuButtonRecordsUma) {
  SimulateUserLogin(kRegularUserLoginInfo);
  SimulatePowerButtonPress();

  LeftClickOn(GetPowerOffButton());

  // Expect a count of 2 because the power button was activated above.
  histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/2);
  histogram_tester_.ExpectBucketCount("Ash.QuickSettings.Button.Activated",
                                      QsButtonCatalogName::kPowerOffMenuButton,
                                      /*expected_count=*/1);
}

TEST_F(PowerButtonTest, EmailMenuButtonRecordsUma) {
  SimulateUserLogin(kRegularUserLoginInfo);
  SimulatePowerButtonPress();

  LeftClickOn(GetEmailButton());

  // Expect a count of 2 because the power button was activated above.
  histogram_tester_.ExpectTotalCount("Ash.QuickSettings.Button.Activated",
                                     /*count=*/2);
  histogram_tester_.ExpectBucketCount(
      "Ash.QuickSettings.Button.Activated",
      QsButtonCatalogName::kPowerEmailMenuButton,
      /*expected_count=*/1);
}

// No lock and sign out buttons in the menu before login.
TEST_F(PowerButtonTest, ButtonStatesNotLoggedIn) {
  EXPECT_TRUE(GetPowerButton()->GetVisible());

  // No menu buttons are visible before showing the menu.
  EXPECT_FALSE(IsMenuShowing());
  EXPECT_EQ(nullptr, GetEmailButton());
  EXPECT_EQ(nullptr, GetRestartButton());
  EXPECT_EQ(nullptr, GetSignOutButton());
  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_EQ(nullptr, GetPowerOffButton());

  // Clicks on the power button.
  SimulatePowerButtonPress();

  EXPECT_TRUE(IsMenuShowing());

  // Only show power off and restart buttons.
  EXPECT_EQ(nullptr, GetEmailButton());
  EXPECT_EQ(nullptr, GetSignOutButton());
  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_TRUE(GetPowerOffButton()->GetVisible());
  EXPECT_TRUE(GetRestartButton()->GetVisible());
}

// All buttons are shown after login.
TEST_F(PowerButtonTest, ButtonStatesLoggedIn) {
  SimulateUserLogin(kRegularUserLoginInfo);
  // TOOD(crbug.com/383442863): Move this to SimulateUserLogin.
  GetSessionControllerClient()->SetCanLockScreen(true);

  EXPECT_TRUE(GetPowerButton()->GetVisible());

  // No menu buttons are visible before showing the menu.
  EXPECT_FALSE(IsMenuShowing());

  EXPECT_EQ(nullptr, GetEmailButton());
  EXPECT_EQ(nullptr, GetRestartButton());
  EXPECT_EQ(nullptr, GetSignOutButton());
  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_EQ(nullptr, GetPowerOffButton());

  // Clicks on the power button.
  SimulatePowerButtonPress();

  EXPECT_TRUE(IsMenuShowing());

  // Show all buttons in the menu.
  EXPECT_TRUE(GetEmailButton()->GetVisible());
  EXPECT_TRUE(GetLockButton()->GetVisible());
  EXPECT_TRUE(GetSignOutButton()->GetVisible());
  EXPECT_TRUE(GetPowerOffButton()->GetVisible());
  EXPECT_TRUE(GetRestartButton()->GetVisible());
}

// The lock button are hidden at the lock screen.
TEST_F(PowerButtonTest, ButtonStatesLockScreen) {
  SimulateUserLogin(kRegularUserLoginInfo);
  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);

  // Changes in lock state close the system tray bubble, so re-show it.
  ShowSystemTrayBubble();

  EXPECT_TRUE(GetPowerButton()->GetVisible());

  // No menu buttons are visible before showing the menu.
  EXPECT_FALSE(IsMenuShowing());

  EXPECT_EQ(nullptr, GetEmailButton());
  EXPECT_EQ(nullptr, GetRestartButton());
  EXPECT_EQ(nullptr, GetSignOutButton());
  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_EQ(nullptr, GetPowerOffButton());

  // Clicks on the power button.
  SimulatePowerButtonPress();

  EXPECT_TRUE(IsMenuShowing());

  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_TRUE(GetEmailButton()->GetVisible());
  EXPECT_TRUE(GetSignOutButton()->GetVisible());
  EXPECT_TRUE(GetPowerOffButton()->GetVisible());
  EXPECT_TRUE(GetRestartButton()->GetVisible());

  // The multi-profile user chooser is disabled at the lock screen.
  EXPECT_FALSE(GetEmailButton()->GetEnabled());
}

// The lock button is hidden when adding a second multiprofile user.
TEST_F(PowerButtonTest, ButtonStatesAddingUser) {
  SimulateUserLogin(kRegularUserLoginInfo);
  SetUserAddingScreenRunning(true);

  EXPECT_TRUE(GetPowerButton()->GetVisible());

  // No menu buttons are visible before showing the menu.
  EXPECT_FALSE(IsMenuShowing());

  EXPECT_EQ(nullptr, GetEmailButton());
  EXPECT_EQ(nullptr, GetRestartButton());
  EXPECT_EQ(nullptr, GetSignOutButton());
  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_EQ(nullptr, GetPowerOffButton());

  // Clicks on the power button.
  SimulatePowerButtonPress();

  EXPECT_TRUE(IsMenuShowing());
  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_TRUE(GetEmailButton()->GetVisible());
  EXPECT_TRUE(GetSignOutButton()->GetVisible());
  EXPECT_TRUE(GetPowerOffButton()->GetVisible());
  EXPECT_TRUE(GetRestartButton()->GetVisible());

  // The multi-profile user chooser is disabled at the lock screen.
  EXPECT_FALSE(GetEmailButton()->GetEnabled());
}

TEST_F(PowerButtonTest, ButtonStatesGuestMode) {
  SimulateGuestLogin();
  SimulatePowerButtonPress();
  EXPECT_TRUE(IsMenuShowing());
  EXPECT_EQ(nullptr, GetEmailButton());
  EXPECT_EQ(nullptr, GetLockButton());
  EXPECT_TRUE(GetSignOutButton()->GetVisible());
  EXPECT_TRUE(GetPowerOffButton()->GetVisible());
  EXPECT_TRUE(GetRestartButton()->GetVisible());
}

TEST_F(PowerButtonTest, EmailIsShownForRegularAccount) {
  SimulateUserLogin({"user@gmail.com", user_manager::UserType::kRegular});
  SimulatePowerButtonPress();
  EXPECT_TRUE(GetEmailButton()->GetVisible());
  EXPECT_TRUE(GetEmailButton()->GetEnabled());
  EXPECT_EQ(u"user@gmail.com", GetEmailButton()->title());
}

TEST_F(PowerButtonTest, EmailIsShownForChildAccount) {
  SimulateUserLogin({"child@gmail.com", user_manager::UserType::kChild});
  SimulatePowerButtonPress();
  EXPECT_TRUE(GetEmailButton()->GetVisible());
  // The multi-profile user chooser is disabled for child accounts.
  EXPECT_FALSE(GetEmailButton()->GetEnabled());
  EXPECT_EQ(u"child@gmail.com", GetEmailButton()->title());
}

TEST_F(PowerButtonTest, EmailIsNotShownForPublicAccount) {
  SimulateUserLogin({"test@test.com", user_manager::UserType::kPublicAccount});
  SimulatePowerButtonPress();
  EXPECT_EQ(nullptr, GetEmailButton());
}

// NOTE: Kiosk user types are not tested because quick settings cannot be
// accessed in kiosk mode.

TEST_F(PowerButtonTest, ClickingEmailShowsUserChooserView) {
  SimulateUserLogin({"user@gmail.com", user_manager::UserType::kRegular});
  SimulatePowerButtonPress();
  LeftClickOn(GetEmailButton());

  QuickSettingsView* quick_settings_view =
      GetPrimaryUnifiedSystemTray()->bubble()->quick_settings_view();
  EXPECT_TRUE(quick_settings_view->IsDetailedViewShown());
  EXPECT_TRUE(quick_settings_view->GetDetailedViewForTest<UserChooserView>());
}

TEST_F(PowerButtonTest, UserItemButtonTooltipText) {
  SimulateUserLogin({"user@gmail.com", user_manager::UserType::kRegular});
  SimulatePowerButtonPress();
  LeftClickOn(GetEmailButton());

  QuickSettingsView* quick_settings_view =
      GetPrimaryUnifiedSystemTray()->bubble()->quick_settings_view();
  EXPECT_TRUE(quick_settings_view->IsDetailedViewShown());

  auto* user_chooser_view =
      quick_settings_view->GetDetailedViewForTest<UserChooserView>();

  ui::AXNodeData data;
  gfx::Size zero_size;

  for (const auto& button : user_chooser_view->user_item_buttons_) {
    EXPECT_EQ(button->GetRenderedTooltipText(gfx::Point()),
              user_chooser_view->GetUserItemAccessibleStringForTesting(
                  button->user_index_for_testing()));
    data = ui::AXNodeData();
    button->GetViewAccessibility().GetAccessibleNodeData(&data);
    EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
              button->GetRenderedTooltipText(gfx::Point()));
    EXPECT_NE(
        data.GetString16Attribute(ax::mojom::StringAttribute::kDescription),
        button->GetRenderedTooltipText(gfx::Point()));

    button->SetBoundsRect(gfx::Rect(zero_size));

    EXPECT_EQ(button->GetRenderedTooltipText(gfx::Point()), u"");
    data = ui::AXNodeData();
    button->GetViewAccessibility().GetAccessibleNodeData(&data);
    EXPECT_NE(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
              button->GetRenderedTooltipText(gfx::Point()));
    EXPECT_EQ(
        data.GetString16Attribute(ax::mojom::StringAttribute::kDescription),
        button->GetRenderedTooltipText(gfx::Point()));
  }
}

// Power button's rounded radii should change correctly when switching between
// active/inactive.
TEST_F(PowerButtonTest, ButtonRoundedRadii) {
  SimulateUserLogin(kRegularUserLoginInfo);

  // Sets a LTR locale.
  base::i18n::SetICUDefaultLocale("en_US");

  EXPECT_TRUE(GetPowerButton()->GetVisible());

  EXPECT_EQ(gfx::RoundedCornersF(16, 16, 16, 16),
            GetBackgroundLayer()->rounded_corner_radii());

  // Clicks on the power button.
  SimulatePowerButtonPress();

  EXPECT_EQ(gfx::RoundedCornersF(4, 16, 16, 16),
            GetBackgroundLayer()->rounded_corner_radii());

  // Click the power button again to close the menu.
  SimulatePowerButtonPress();

  // Sets a RTL locale.
  base::i18n::SetICUDefaultLocale("ar");

  EXPECT_EQ(gfx::RoundedCornersF(16, 16, 16, 16),
            GetBackgroundLayer()->rounded_corner_radii());

  // Clicks on the power button.
  SimulatePowerButtonPress();

  EXPECT_EQ(gfx::RoundedCornersF(16, 4, 16, 16),
            GetBackgroundLayer()->rounded_corner_radii());
}

TEST_F(PowerButtonTest, DeviceRebootOnShutdownPolicyHidesPowerOffButton) {
  SimulateUserLogin(kRegularUserLoginInfo);
  // Simulate DeviceRebootOnShutdownPolicy is enabled.
  Shell::Get()->shutdown_controller()->SetRebootOnShutdown(true);

  SimulatePowerButtonPress();

  EXPECT_FALSE(GetPowerOffButton());
  EXPECT_TRUE(GetRestartButton());
}

TEST_F(PowerButtonTest, ChevronFlipsWhenMenuIsShowing) {
  SimulateUserLogin(kRegularUserLoginInfo);

  EXPECT_TRUE(GetPowerButton()->GetVisible());
  EXPECT_FALSE(IsMenuShowing());
  EXPECT_TRUE(IsDownChevron());

  SimulatePowerButtonPress();

  EXPECT_TRUE(IsMenuShowing());
  EXPECT_TRUE(IsUpChevron());

  // Click the power button again to close the menu.
  SimulatePowerButtonPress();

  EXPECT_FALSE(IsMenuShowing());
  EXPECT_TRUE(IsDownChevron());
}

}  // namespace ash
