// Copyright 2020 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/ui/webui/settings/about_handler.h"

#include <memory>

#include "base/memory/raw_ptr.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/extended_updates/extended_updates_controller.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/ownership/fake_owner_settings_service.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.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 "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_web_ui.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::HasSubstr;
using ::testing::NotNull;
using ::testing::Return;

namespace chromeos {
namespace settings {

namespace {

constexpr std::string_view kExtendedUpdatesSettingChangedMessage =
    "extended-updates-setting-changed";

class TestAboutHandler : public ::settings::AboutHandler {
 public:
  explicit TestAboutHandler(Profile* profile) : AboutHandler(profile) {}
  ~TestAboutHandler() override = default;

  // Make public for testing.
  using AboutHandler::set_clock;

  // Make public for testing.
  using AboutHandler::set_web_ui;
};

class AboutHandlerTest : public testing::Test {
 public:
  AboutHandlerTest() = default;
  ~AboutHandlerTest() override = default;
  AboutHandlerTest(const AboutHandlerTest&) = delete;
  AboutHandlerTest& operator=(const AboutHandlerTest&) = delete;

  void SetUp() override {
    fake_update_engine_client_ =
        ash::UpdateEngineClient::InitializeFakeForTest();
    ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);

    subscription_ = ash::FakeOwnerSettingsService::SetUpTestingFactory(
        test_cros_settings_.device_settings(),
        ash::OwnerSettingsServiceAshFactory::GetInstance()->GetOwnerKeyUtil());

    profile_ = std::make_unique<TestingProfile>();

    handler_ = std::make_unique<TestAboutHandler>(profile_.get());
    handler_->set_web_ui(&web_ui_);
    handler_->RegisterMessages();
    handler_->AllowJavascriptForTesting();

    clock_ = std::make_unique<base::SimpleTestClock>();
    handler_->set_clock(clock_.get());
  }

  void TearDown() override {
    handler_.reset();
    fake_update_engine_client_ = nullptr;

    profile_.reset();
    subscription_ = {};

    ash::ConciergeClient::Shutdown();
    ash::UpdateEngineClient::Shutdown();
  }

  const base::Value& CallWebUIMessage(const std::string& message,
                                      base::ListValue args = {}) {
    size_t initial_call_count = web_ui_.call_data().size();

    base::ListValue message_args;
    message_args.Append("handlerFunctionName");
    for (base::Value& arg : args) {
      message_args.Append(std::move(arg));
    }
    web_ui_.HandleReceivedMessage(message, message_args);
    task_environment_.RunUntilIdle();

    EXPECT_EQ(initial_call_count + 1u, web_ui_.call_data().size());

    const content::TestWebUI::CallData& call_data =
        *web_ui_.call_data()[initial_call_count];
    EXPECT_EQ("cr.webUIResponse", call_data.function_name());
    EXPECT_EQ("handlerFunctionName", call_data.arg1()->GetString());
    return *call_data.arg3();
  }

  std::string CallGetEndOfLifeInfoAndReturnString(bool has_eol_passed) {
    const auto& response = CallWebUIMessage("getEndOfLifeInfo").GetDict();
    EXPECT_EQ(has_eol_passed, *response.FindBool("hasEndOfLife"));
    return *response.FindString("aboutPageEndOfLifeMessage");
  }

  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);
  }

  void RestartJavascript() {
    handler_->DisallowJavascript();
    handler_->AllowJavascriptForTesting();
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  ash::ScopedTestingCrosSettings test_cros_settings_;
  ash::ScopedStubInstallAttributes test_install_attributes_;
  base::CallbackListSubscription subscription_;
  std::unique_ptr<TestingProfile> profile_;
  content::TestWebUI web_ui_;
  std::unique_ptr<TestAboutHandler> handler_;
  raw_ptr<ash::FakeUpdateEngineClient> fake_update_engine_client_;
  std::unique_ptr<base::SimpleTestClock> clock_;
};

TEST_F(AboutHandlerTest, EndOfLifeMessageInAboutDetailsSubpage) {
  SetCurrentTimeToUtc("15 March 2020");

  SetEolDateUtc("15 November 2017");
  EXPECT_EQ(
      "This device stopped getting automatic software and security "
      "updates in November 2017. <a target=\"_blank\" href=\"https:"
      "//www.google.com/chromebook/older/\">Learn more</a>",
      CallGetEndOfLifeInfoAndReturnString(true /*=has_eol_passed*/));

  SetEolDateUtc("15 May 2023");
  EXPECT_EQ(
      "This device will get automatic software and security updates "
      "until May 2023. <a target=\"_blank\" href=\"https://support.google"
      ".com/chrome/a?p=auto-update-policy\">Learn more</a>",
      CallGetEndOfLifeInfoAndReturnString(false /*=has_eol_passed*/));

  const base::Time null_time = base::Time();
  fake_update_engine_client_->set_eol_date(null_time);
  EXPECT_EQ("", CallGetEndOfLifeInfoAndReturnString(false /*=has_eol_passed*/));
}

TEST_F(AboutHandlerTest, DeferredUpdateMessageInAboutPage) {
  update_engine::StatusResult status;
  status.set_current_operation(update_engine::Operation::UPDATED_BUT_DEFERRED);
  fake_update_engine_client_->set_default_status(status);
  fake_update_engine_client_->NotifyObserversThatStatusChanged(status);

  EXPECT_EQ(0,
            fake_update_engine_client_->apply_deferred_update_advanced_count());
  web_ui_.HandleReceivedMessage("applyDeferredUpdateAdvanced",
                                base::ListValue());
  EXPECT_EQ(1,
            fake_update_engine_client_->apply_deferred_update_advanced_count());
}

TEST_F(AboutHandlerTest, GetEndOfLifeInfoWithoutExtendedUpdatesDate) {
  SetCurrentTimeToUtc("15 March 2020");
  SetEolDateUtc("30 Oct 2023");

  const auto& response = CallWebUIMessage("getEndOfLifeInfo").GetDict();
  EXPECT_FALSE(*response.FindBool("isExtendedUpdatesDatePassed"));
  EXPECT_FALSE(*response.FindBool("isExtendedUpdatesOptInRequired"));
}

TEST_F(AboutHandlerTest, GetEndOfLifeInfoWithExtendedUpdatesDatePassed) {
  SetCurrentTimeToUtc("15 March 2020");
  base::Time eol_date, extended_date;
  ASSERT_TRUE(base::Time::FromUTCString("30 Oct 2023", &eol_date));
  ASSERT_TRUE(base::Time::FromUTCString("4 June 2019", &extended_date));
  fake_update_engine_client_->set_eol_info({
      .eol_date = eol_date,
      .extended_date = extended_date,
      .extended_opt_in_required = true,
  });

  const auto& response = CallWebUIMessage("getEndOfLifeInfo").GetDict();
  EXPECT_TRUE(*response.FindBool("isExtendedUpdatesDatePassed"));
  EXPECT_TRUE(*response.FindBool("isExtendedUpdatesOptInRequired"));
  EXPECT_THAT(*response.FindString("aboutPageEndOfLifeMessage"),
              HasSubstr("June 2019"));
}

TEST_F(AboutHandlerTest, GetEndOfLifeInfoWithExtendedUpdatesDateNotPassed) {
  SetCurrentTimeToUtc("15 March 2020");
  base::Time eol_date, extended_date;
  ASSERT_TRUE(base::Time::FromUTCString("30 Oct 2023", &eol_date));
  ASSERT_TRUE(base::Time::FromUTCString("4 June 2021", &extended_date));
  fake_update_engine_client_->set_eol_info({
      .eol_date = eol_date,
      .extended_date = extended_date,
      .extended_opt_in_required = true,
  });

  const auto& response = CallWebUIMessage("getEndOfLifeInfo").GetDict();
  EXPECT_FALSE(*response.FindBool("isExtendedUpdatesDatePassed"));
  EXPECT_TRUE(*response.FindBool("isExtendedUpdatesOptInRequired"));
  EXPECT_THAT(*response.FindString("aboutPageEndOfLifeMessage"),
              HasSubstr("June 2021"));
}

TEST_F(AboutHandlerTest, GetEndOfLifeInfoWithExtendedUpdatesOptedIn) {
  SetCurrentTimeToUtc("15 March 2020");
  base::Time eol_date, extended_date;
  ASSERT_TRUE(base::Time::FromUTCString("30 Oct 2023", &eol_date));
  ASSERT_TRUE(base::Time::FromUTCString("4 June 2021", &extended_date));
  fake_update_engine_client_->set_eol_info({
      .eol_date = eol_date,
      .extended_date = extended_date,
      .extended_opt_in_required = true,
  });

  ash::MockExtendedUpdatesController mock_controller;
  ash::ScopedExtendedUpdatesController scoped_controller(&mock_controller);

  EXPECT_CALL(mock_controller, IsOptedIn()).WillOnce(Return(true));

  const auto& response = CallWebUIMessage("getEndOfLifeInfo").GetDict();
  EXPECT_FALSE(*response.FindBool("isExtendedUpdatesDatePassed"));
  EXPECT_TRUE(*response.FindBool("isExtendedUpdatesOptInRequired"));
  EXPECT_THAT(*response.FindString("aboutPageEndOfLifeMessage"),
              HasSubstr("October 2023"));
}

TEST_F(AboutHandlerTest, HandleIsExtendedUpdatesOptInEligible) {
  ash::ExtendedUpdatesController::Params params{
      .eol_passed = false,
      .extended_date_passed = true,
      .opt_in_required = true,
  };

  ash::MockExtendedUpdatesController mock_controller;
  ash::ScopedExtendedUpdatesController scoped_controller(&mock_controller);

  EXPECT_CALL(mock_controller, IsOptInEligible(NotNull(), params))
      .WillOnce(Return(true));

  bool eligible = CallWebUIMessage("isExtendedUpdatesOptInEligible",
                                   base::ListValue()
                                       .Append(params.eol_passed)
                                       .Append(params.extended_date_passed)
                                       .Append(params.opt_in_required))
                      .GetBool();
  EXPECT_TRUE(eligible);
}

TEST_F(AboutHandlerTest, ObservesExtendedUpdatesSettingChanges) {
  ash::MockExtendedUpdatesController mock_controller;
  ash::ScopedExtendedUpdatesController scoped_controller(&mock_controller);

  EXPECT_CALL(mock_controller, HasOptInAbility(NotNull()))
      .WillOnce(Return(true));

  EXPECT_EQ(web_ui_.call_data().size(), 0u);
  EXPECT_TRUE(mock_controller.OptIn(profile_.get()));

  ASSERT_EQ(web_ui_.call_data().size(), 1u);
  const auto& call_data = web_ui_.call_data()[0];
  ASSERT_EQ(call_data->args().size(), 1u);
  EXPECT_EQ(call_data->args()[0].GetString(),
            kExtendedUpdatesSettingChangedMessage);
}

TEST_F(AboutHandlerTest, ObservesExtendedUpdatesSettingChangesAfterRefresh) {
  // Simulate the disallowing and allowing of Javascript during a page refresh.
  RestartJavascript();

  ash::MockExtendedUpdatesController mock_controller;
  ash::ScopedExtendedUpdatesController scoped_controller(&mock_controller);

  EXPECT_CALL(mock_controller, HasOptInAbility(NotNull()))
      .WillOnce(Return(true));

  EXPECT_EQ(web_ui_.call_data().size(), 0u);
  EXPECT_TRUE(mock_controller.OptIn(profile_.get()));

  ASSERT_EQ(web_ui_.call_data().size(), 1u);
  const auto& call_data = web_ui_.call_data()[0];
  ASSERT_EQ(call_data->args().size(), 1u);
  EXPECT_EQ(call_data->args()[0].GetString(),
            kExtendedUpdatesSettingChangedMessage);
}

}  // namespace

}  // namespace settings
}  // namespace chromeos
