// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/language/core/browser/language_prefs.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/i18n/language_tag.h"
#include "base/json/json_reader.h"
#include "base/test/test_timeouts.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/language/core/browser/language_prefs_test_util.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::ElementsAreArray;

namespace language {

class LanguagePrefsTest : public testing::Test {
 protected:
  LanguagePrefsTest()
      : prefs_(new sync_preferences::TestingPrefServiceSyncable()) {
    LanguagePrefs::RegisterProfilePrefs(prefs_->registry());
    language_prefs_ = std::make_unique<language::LanguagePrefs>(prefs_.get());
  }

  void SetUp() override {
    prefs_->SetString(language::prefs::kAcceptLanguages, std::string());
#if BUILDFLAG(IS_CHROMEOS)
    prefs_->SetString(language::prefs::kPreferredLanguages, std::string());
#endif
  }

  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_;
  std::unique_ptr<language::LanguagePrefs> language_prefs_;
};

TEST_F(LanguagePrefsTest, GetFirstLanguageTest) {
  EXPECT_EQ("a", language::GetFirstLanguage("a,b,c"));
  EXPECT_EQ("en-US", language::GetFirstLanguage("en-US,en,en-GB"));
  EXPECT_EQ("en-US", language::GetFirstLanguage("en-US"));
  EXPECT_EQ("", language::GetFirstLanguage(""));
}

TEST_F(LanguagePrefsTest, UpdateLanguageList) {
  language::test::LanguagePrefTester content_languages_tester =
      language::test::LanguagePrefTester(prefs_.get());
  // Empty update.
  std::vector<std::string> languages;
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.ExpectAcceptLanguagePrefs("");

  // One language.
  languages = {"en"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.ExpectAcceptLanguagePrefs("en");

  // More than one language.
  languages = {"en", "ja", "it"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.ExpectAcceptLanguagePrefs("en,ja,it");

  // Locale-specific codes.
  languages = {"en-US", "ja", "en-CA", "fr-CA"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.ExpectAcceptLanguagePrefs("en-US,ja,en-CA,fr-CA");
}

TEST_F(LanguagePrefsTest, UpdateForcedLanguageList) {
  // Only test policy-forced languages on non-Chrome OS platforms.
#if BUILDFLAG(IS_CHROMEOS)
  GTEST_SKIP();
#else
  language::test::LanguagePrefTester content_languages_tester =
      language::test::LanguagePrefTester(prefs_.get());
  // Empty update.
  std::vector<std::string> languages;
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.ExpectAcceptLanguagePrefs("");

  // Forced languages with no duplicates.
  languages = {"fr"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.SetForcedLanguagePrefs({"en", "it"});
  content_languages_tester.ExpectSelectedLanguagePrefs("fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,it,fr");
  content_languages_tester.SetForcedLanguagePrefs({});  // Reset pref

  // Forced languages with some duplicates.
  languages = {"en-US", "en", "fr", "fr-CA"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.SetForcedLanguagePrefs({"en", "it"});
  content_languages_tester.ExpectSelectedLanguagePrefs("en-US,en,fr,fr-CA");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,it,en-US,fr,fr-CA");
  content_languages_tester.SetForcedLanguagePrefs({});  // Reset pref

  // Forced languages with full duplicates.
  languages = {"en", "es", "fr"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.SetForcedLanguagePrefs({"en", "es", "fr"});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,es,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,es,fr");
  content_languages_tester.SetForcedLanguagePrefs({});  // Reset pref

  // Add then remove forced languages with no duplicates.
  languages = {"en", "fr"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.SetForcedLanguagePrefs({"it"});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("it,en,fr");
  // Remove forced languages
  content_languages_tester.SetForcedLanguagePrefs({});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");

  // Add then remove forced languages with some duplicates.
  languages = {"en", "fr"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.SetForcedLanguagePrefs({"en", "it"});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,it,fr");
  // Remove forced languages
  content_languages_tester.SetForcedLanguagePrefs({});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");

  // Add then remove forced languages with full duplicates.
  languages = {"en", "fr"};
  language_prefs_->SetUserSelectedLanguagesList(languages);
  content_languages_tester.SetForcedLanguagePrefs({"en", "fr"});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");
  // Remove forced languages
  content_languages_tester.SetForcedLanguagePrefs({});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");
#endif
}

TEST_F(LanguagePrefsTest, ResetLanguagePrefs) {
  language::test::LanguagePrefTester content_languages_tester =
      language::test::LanguagePrefTester(prefs_.get());

  language_prefs_->SetUserSelectedLanguagesList({"en", "es", "fr"});
  content_languages_tester.ExpectSelectedLanguagePrefs("en,es,fr");
  content_languages_tester.ExpectAcceptLanguagePrefs("en,es,fr");
#if BUILDFLAG(IS_ANDROID)
  language_prefs_->SetULPLanguages({base::i18n::GetKnownLanguageTag("en"),
                                    base::i18n::GetKnownLanguageTag("es"),
                                    base::i18n::GetKnownLanguageTag("fr")});
  EXPECT_THAT(language_prefs_->GetULPLanguages(),
              testing::ElementsAre("en", "es", "fr"));
#endif

  ResetLanguagePrefs(prefs_.get());
#if BUILDFLAG(IS_ANDROID)
  EXPECT_THAT(language_prefs_->GetULPLanguages(), testing::IsEmpty());
#endif
  content_languages_tester.ExpectSelectedLanguagePrefs("");
  // Accept languages pref is reset to the default value, not cleared.
#if BUILDFLAG(IS_CHROMEOS)
  content_languages_tester.ExpectAcceptLanguagePrefs(
      prefs_->GetDefaultPrefValue(language::prefs::kPreferredLanguages)
          ->GetString());
#else   // BUILDFLAG(IS_CHROMEOS)
  content_languages_tester.ExpectAcceptLanguagePrefs(
      prefs_->GetDefaultPrefValue(language::prefs::kAcceptLanguages)
          ->GetString());
#endif  // BUILDFLAG(IS_CHROMEOS)
}

TEST_F(LanguagePrefsTest, ULPLanguagesPref) {
#if BUILDFLAG(IS_ANDROID)
  // ULPLanguagesPref is initially empty.
  EXPECT_THAT(language_prefs_->GetULPLanguages(), testing::IsEmpty());

  // Set ULP Language Preference.
  language_prefs_->SetULPLanguages({base::i18n::GetKnownLanguageTag("en"),
                                    base::i18n::GetKnownLanguageTag("es"),
                                    base::i18n::GetKnownLanguageTag("fr")});
  EXPECT_THAT(language_prefs_->GetULPLanguages(),
              testing::ElementsAre("en", "es", "fr"));

  // Setting ULP languages to a new list clears the old list.
  language_prefs_->SetULPLanguages({base::i18n::GetKnownLanguageTag("de"),
                                    base::i18n::GetKnownLanguageTag("pt"),
                                    base::i18n::GetKnownLanguageTag("zh")});
  EXPECT_THAT(language_prefs_->GetULPLanguages(),
              testing::ElementsAre("de", "pt", "zh"));

  // Setting ULP languages to a an empty list clears it.
  language_prefs_->SetULPLanguages({});
  EXPECT_THAT(language_prefs_->GetULPLanguages(), testing::IsEmpty());
#endif
}
TEST_F(LanguagePrefsTest, GetIncognitoLanguageListTest) {
  // Test mapping from generated map.
  // For example "fr" should map to "fr-FR,fr,en-US,en" based on
  // components_locale_settings_fr.xtb.
  EXPECT_EQ("en-US,en", language::GetIncognitoLanguageList("en,fr"));
  EXPECT_EQ("en-US,en", language::GetIncognitoLanguageList("en-US,fr"));
  EXPECT_EQ("fr-FR,fr,en-US,en",
            language::GetIncognitoLanguageList("fr,en-US"));
  EXPECT_EQ("zh-CN,zh", language::GetIncognitoLanguageList("zh-CN,zh"));

  // Test fallback heuristic for language not in map.
  EXPECT_EQ("as,en-US,en", language::GetIncognitoLanguageList("as,en-US"));
  EXPECT_EQ("zz,en-US,en", language::GetIncognitoLanguageList("zz,en-US"));

  // Test empty or single language cases (should return unchanged).
  EXPECT_EQ("", language::GetIncognitoLanguageList(""));
  EXPECT_EQ("fr", language::GetIncognitoLanguageList("fr"));
  EXPECT_EQ("zh-CN", language::GetIncognitoLanguageList("zh-CN"));
  EXPECT_EQ("en", language::GetIncognitoLanguageList("en"));
  EXPECT_EQ("en-US", language::GetIncognitoLanguageList("en-US"));
}

}  // namespace language
