// Copyright 2019 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/ash/eol/eol_notification.h"

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "chrome/browser/ash/extended_updates/test/mock_extended_updates_controller.h"
#include "chrome/browser/ash/extended_updates/test/scoped_extended_updates_controller.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/update_engine/fake_update_engine_client.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/message_center/message_center.h"

namespace ash {

namespace {
constexpr char kEolNotificationId[] = "chrome://product_eol";
}  // namespace

class EolNotificationTest : public BrowserWithTestWindowTest {
 public:
  EolNotificationTest() = default;
  ~EolNotificationTest() override = default;

  void SetUp() override {
    fake_update_engine_client_ = UpdateEngineClient::InitializeFakeForTest();
    ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
    BrowserWithTestWindowTest::SetUp();
    eol_notification_ = std::make_unique<EolNotification>(
        ash::ProfileHelper::Get()->GetUserByProfile(profile()));
    clock_ = std::make_unique<base::SimpleTestClock>();
    eol_notification_->clock_ = clock_.get();
  }

  void CheckEolInfo() {
    // The callback passed from |eol_notification_| to
    // |fake_update_engine_client_| should be invoked before a notification can
    // appear.
    eol_notification_->CheckEolInfo();
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override {
    eol_notification_.reset();
    fake_update_engine_client_ = nullptr;
    BrowserWithTestWindowTest::TearDown();
    ConciergeClient::Shutdown();
    UpdateEngineClient::Shutdown();
  }

  void DismissNotification() {
    message_center::MessageCenter::Get()->ClickOnNotificationButton(
        kEolNotificationId, EolNotification::ButtonIndex::BUTTON_DISMISS);
  }

  void SetCurrentTimeToUtc(const char* utc_date_string) {
    base::Time utc_time;
    ASSERT_TRUE(base::Time::FromUTCString(utc_date_string, &utc_time));
    clock_->SetNow(utc_time);
  }

  void SetEolDateUtc(const char* utc_date_string) {
    base::Time utc_date;
    ASSERT_TRUE(base::Time::FromUTCString(utc_date_string, &utc_date));
    fake_update_engine_client_->set_eol_date(utc_date);
  }

  message_center::Notification* GetNotification() {
    return message_center::MessageCenter::Get()->FindVisibleNotificationById(
        kEolNotificationId);
  }

 protected:
  raw_ptr<FakeUpdateEngineClient> fake_update_engine_client_;
  std::unique_ptr<EolNotification> eol_notification_;
  std::unique_ptr<base::SimpleTestClock> clock_;
};

TEST_F(EolNotificationTest, TestNoNotifciationBeforeEol) {
  SetCurrentTimeToUtc("1 January 2019");
  SetEolDateUtc("1 December 2019");

  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestFirstWarningNotification) {
  SetCurrentTimeToUtc("1 August 2019");
  SetEolDateUtc("1 December 2019");

  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_FALSE(notification);

  SetCurrentTimeToUtc("15 August 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestSecondWarningNotification) {
  SetCurrentTimeToUtc("1 November 2019");
  SetEolDateUtc("1 December 2019");

  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_TRUE(notification);

  std::u16string expected_title = u"Updates end December 2019";
  std::u16string expected_message =
      u"You'll still be able to use this Chrome device after that time, but it "
      u"will no longer get automatic software and security updates";
  EXPECT_EQ(notification->title(), expected_title);
  EXPECT_EQ(notification->message(), expected_message);

  SetCurrentTimeToUtc("1 October 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_TRUE(notification);

  DismissNotification();

  SetCurrentTimeToUtc("15 November 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestFinalEolNotification) {
  SetEolDateUtc("1 December 2019");
  SetCurrentTimeToUtc("2 December 2019");

  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_TRUE(notification);

  std::u16string expected_title = u"Final software update";
  std::u16string expected_message =
      u"This is the last automatic software and security update for this "
      u"Chrome device. To get future updates, upgrade to a newer model.";
  EXPECT_EQ(notification->title(), expected_title);
  EXPECT_EQ(notification->message(), expected_message);

  DismissNotification();

  SetCurrentTimeToUtc("15 December 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestOnEolDateChangeBeforeFirstWarning) {
  SetCurrentTimeToUtc("1 January 2019");
  SetEolDateUtc("1 December 2019");
  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_FALSE(notification);

  SetCurrentTimeToUtc("1 January 2019");
  SetEolDateUtc("1 November 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestOnEolDateChangeBeforeSecondWarning) {
  SetCurrentTimeToUtc("1 August 2019");
  SetEolDateUtc("1 December 2019");
  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_FALSE(notification);

  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);

  // In practice, such a small change in date should not happen.
  SetEolDateUtc("2 December 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestOnEolDateChangeBeforeFinalWarning) {
  SetCurrentTimeToUtc("1 November 2019");
  SetEolDateUtc("1 December 2019");
  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_TRUE(notification);

  // Dismiss first warning notification.
  DismissNotification();
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);

  // In practice, such a small change in date should not happen.
  SetEolDateUtc("2 December 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_TRUE(notification);
}

TEST_F(EolNotificationTest, TestOnEolDateChangedAfterFinalWarning) {
  SetEolDateUtc("1 December 2019");
  SetCurrentTimeToUtc("3 December 2019");
  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_TRUE(notification);

  // Dismiss first warning notification.
  DismissNotification();
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);

  // Refuse to show notification as eol date is still in the past.
  SetEolDateUtc("2 December 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_FALSE(notification);

  // Show as eol date is in the future and within first warning range.
  SetEolDateUtc("4 December 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_TRUE(notification);
}

TEST_F(EolNotificationTest, TestNotificationUpdatesProperlyWithoutDismissal) {
  SetCurrentTimeToUtc("1 August 2019");
  SetEolDateUtc("1 December 2019");
  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_FALSE(notification);

  // EOL date arrives and the user has not dismissed the notification.
  SetCurrentTimeToUtc("1 December 2019");
  CheckEolInfo();
  notification = GetNotification();
  ASSERT_TRUE(notification);
  std::u16string expected_title = u"Final software update";
  std::u16string expected_message =
      u"This is the last automatic software and security update for this "
      u"Chrome device. To get future updates, upgrade to a newer model.";
  EXPECT_EQ(notification->title(), expected_title);
  EXPECT_EQ(notification->message(), expected_message);

  DismissNotification();
  notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestBackwardsCompatibilityFinalUpdateAlreadyShown) {
  SetEolDateUtc("1 December 2019");
  SetCurrentTimeToUtc("2 December 2019");

  // User dismissed Final Update notification prior to the addition of
  // first and second warning notifications.
  profile()->GetPrefs()->SetBoolean(ash::prefs::kEolNotificationDismissed,
                                    true);

  CheckEolInfo();
  auto* notification = GetNotification();
  ASSERT_FALSE(notification);
}

TEST_F(EolNotificationTest, TestOnEolInfoCallsExtendedUpdatesController) {
  SetCurrentTimeToUtc("10 October 2024");

  base::Time eol_date, extended_date;
  ASSERT_TRUE(base::Time::FromUTCString("25 December 2025", &eol_date));
  ASSERT_TRUE(base::Time::FromUTCString("8 June 2024", &extended_date));
  const UpdateEngineClient::EolInfo eol_info{
      .eol_date = eol_date,
      .extended_date = extended_date,
      .extended_opt_in_required = true,
  };
  fake_update_engine_client_->set_eol_info(eol_info);

  ::testing::StrictMock<MockExtendedUpdatesController> mock_controller;
  EXPECT_CALL(mock_controller, OnEolInfo(profile(), eol_info)).Times(1);

  ScopedExtendedUpdatesController scoped_controller(&mock_controller);

  CheckEolInfo();
  auto* notification = GetNotification();
  EXPECT_FALSE(notification);
}

}  // namespace ash
