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

#import <Foundation/Foundation.h>

#import <memory>

#import "base/i18n/language_tag.h"
#import "base/i18n/tag_converters.h"
#import "base/strings/sys_string_conversions.h"
#import "components/language/core/browser/language_prefs.h"
#import "components/language/core/browser/pref_names.h"
#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
#import "components/language_detection/core/language_detection_model.h"
#import "components/prefs/pref_registry_simple.h"
#import "components/prefs/testing_pref_service.h"
#import "components/translate/core/browser/mock_translate_ranker.h"
#import "components/translate/core/browser/translate_pref_names.h"
#import "components/translate/core/browser/translate_prefs.h"
#import "components/translate/core/common/language_detection_details.h"
#import "components/translate/core/language_detection/language_detection_model.h"
#import "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/scoped_testing_web_client.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_client.h"
#import "ios/web_view/internal/translate/cwv_translation_controller_internal.h"
#import "ios/web_view/internal/translate/cwv_translation_language_detection_details_internal.h"
#import "ios/web_view/internal/translate/cwv_translation_language_internal.h"
#import "ios/web_view/internal/translate/web_view_translate_client.h"
#import "ios/web_view/internal/web_view_browser_state.h"
#import "ios/web_view/public/cwv_translation_controller_delegate.h"
#import "ios/web_view/public/cwv_translation_policy.h"
#import "ios/web_view/test/test_with_locale_and_resources.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

namespace ios_web_view {

using testing::_;
using testing::Return;

namespace {
NSString* const kTestFromLangCode = @"ja";
NSString* const kTestToLangCode = @"en";
NSString* const kTestPageHost = @"www.chromium.org";
}  // namespace

class MockWebViewTranslateClient : public WebViewTranslateClient {
 public:
  MockWebViewTranslateClient(
      PrefService* pref_service,
      translate::TranslateRanker* translate_ranker,
      language::LanguageModel* language_model,
      language::UrlLanguageHistogram* url_language_histogram,
      web::WebState* web_state,
      language::AcceptLanguagesService* accept_languages)
      : WebViewTranslateClient(pref_service,
                               translate_ranker,
                               language_model,
                               url_language_histogram,
                               web_state,
                               accept_languages) {}
  MOCK_METHOD3(TranslatePage,
               void(const std::string&, const std::string&, bool));
  MOCK_METHOD0(RevertTranslation, void());
  MOCK_METHOD0(RequestTranslationOffer, bool());
};

class TestLanguageModel : public language::LanguageModel {
  std::vector<LanguageDetails> GetLanguages() override { return {}; }
};

class CWVTranslationControllerTest : public TestWithLocaleAndResources {
 public:
  void OnLanguageDetermined(translate::LanguageDetectionDetails& details) {
    translate_client_->OnLanguageDetermined(details);
  }

 protected:
  CWVTranslationControllerTest()
      : language_detection_model_(
            std::make_unique<language_detection::LanguageDetectionModel>()) {
    web::WebState::CreateParams params(&browser_state_);
    web_state_ = web::WebState::Create(params);
    web_state_->SetKeepRenderProcessAlive(true);

    pref_service_.registry()->RegisterStringPref(
        language::prefs::kAcceptLanguages, "en");
    pref_service_.registry()->RegisterStringPref(
        language::prefs::kSelectedLanguages, "");
    pref_service_.registry()->RegisterListPref(
        language::prefs::kForcedLanguages);
    pref_service_.registry()->RegisterListPref(
        translate::prefs::kBlockedLanguages,
        translate::TranslatePrefs::GetDefaultBlockedLanguages());
    pref_service_.registry()->RegisterBooleanPref(
        translate::prefs::kOfferTranslateEnabled, true);
    pref_service_.registry()->RegisterListPref(
        translate::TranslatePrefs::kPrefNeverPromptSitesDeprecated);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::prefs::kPrefNeverPromptSitesWithTime);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::prefs::kPrefAlwaysTranslateList);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::TranslatePrefs::kPrefAlwaysTranslateListDeprecated);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::TranslatePrefs::kPrefTranslateDeniedCount);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::TranslatePrefs::kPrefTranslateIgnoredCount);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::TranslatePrefs::kPrefTranslateAcceptedCount);
    pref_service_.registry()->RegisterStringPref(
        translate::prefs::kPrefTranslateRecentTarget, "");
    // Using string literal here because kForceTriggerTranslateCount is private
    // in translate::TranslatePrefs.
    pref_service_.registry()->RegisterIntegerPref(
        "translate_force_trigger_on_english_count_for_backoff_1", 0);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::TranslatePrefs::kPrefTranslateAutoAlwaysCount);
    pref_service_.registry()->RegisterDictionaryPref(
        translate::TranslatePrefs::kPrefTranslateAutoNeverCount);

    accept_languages_ = std::make_unique<language::AcceptLanguagesService>(
        &pref_service_, language::prefs::kAcceptLanguages);

    language::IOSLanguageDetectionTabHelper::CreateForWebState(
        web_state_.get(),
        /*url_language_histogram=*/nullptr, &language_detection_model_,
        &pref_service_);

    auto translate_client = std::make_unique<MockWebViewTranslateClient>(
        &pref_service_, &translate_ranker_, &language_model_,
        /*url_language_histogram=*/nullptr, web_state_.get(),
        accept_languages_.get());
    translate_client_ = translate_client.get();

    translation_controller_ = [[CWVTranslationController alloc]
        initWithWebState:web_state_.get()
         translateClient:std::move(translate_client)];

    translate_prefs_ = translate_client_->GetTranslatePrefs();
    translate_prefs_->ResetToDefaults();
  }

  ~CWVTranslationControllerTest() override {
    translate_prefs_->ResetToDefaults();
  }

  // Checks if |lang_code| matches the OCMArg's CWVTranslationLanguage.
  [[nodiscard]] id CheckLanguageCode(NSString* lang_code) {
    return [OCMArg checkWithBlock:^BOOL(CWVTranslationLanguage* lang) {
      return [lang.languageCode isEqualToString:lang_code];
    }];
  }

  web::WebTaskEnvironment task_environment_;
  translate::testing::MockTranslateRanker translate_ranker_;
  TestLanguageModel language_model_;
  TestingPrefServiceSimple pref_service_;
  std::unique_ptr<language::AcceptLanguagesService> accept_languages_;
  web::FakeBrowserState browser_state_;
  std::unique_ptr<web::WebState> web_state_;
  MockWebViewTranslateClient* translate_client_;
  CWVTranslationController* translation_controller_;
  std::unique_ptr<translate::TranslatePrefs> translate_prefs_;
  translate::LanguageDetectionModel language_detection_model_;
};

// Tests CWVTranslationController invokes can offer delegate method.
TEST_F(CWVTranslationControllerTest, CanOfferCallback) {
  id delegate = OCMProtocolMock(@protocol(CWVTranslationControllerDelegate));
  translation_controller_.delegate = delegate;

  OCMExpect([delegate
                translationController:translation_controller_
      canOfferTranslationFromLanguage:CheckLanguageCode(kTestFromLangCode)
                           toLanguage:CheckLanguageCode(kTestToLangCode)]);
  translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
                                     base::SysNSStringToUTF8(kTestFromLangCode),
                                     base::SysNSStringToUTF8(kTestToLangCode),
                                     translate::TranslateErrors::NONE,
                                     /*triggered_from_menu=*/false);

  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests CWVTranslationController invokes did start delegate method.
TEST_F(CWVTranslationControllerTest, DidStartCallback) {
  id delegate = OCMProtocolMock(@protocol(CWVTranslationControllerDelegate));
  translation_controller_.delegate = delegate;

  OCMExpect([delegate translationController:translation_controller_
            didStartTranslationFromLanguage:CheckLanguageCode(kTestFromLangCode)
                                 toLanguage:CheckLanguageCode(kTestToLangCode)
                              userInitiated:YES]);
  translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_TRANSLATING,
                                     base::SysNSStringToUTF8(kTestFromLangCode),
                                     base::SysNSStringToUTF8(kTestToLangCode),
                                     translate::TranslateErrors::NONE,
                                     /*triggered_from_menu=*/true);

  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests CWVTranslationController invokes did finish delegate method.
TEST_F(CWVTranslationControllerTest, DidFinishCallback) {
  id delegate = OCMProtocolMock(@protocol(CWVTranslationControllerDelegate));
  translation_controller_.delegate = delegate;

  id check_error_code = [OCMArg checkWithBlock:^BOOL(NSError* error) {
    return error.code == CWVTranslationErrorInitializationError;
  }];
  OCMExpect([delegate translationController:translation_controller_
           didFinishTranslationFromLanguage:CheckLanguageCode(kTestFromLangCode)
                                 toLanguage:CheckLanguageCode(kTestToLangCode)
                                      error:check_error_code]);
  translate_client_->ShowTranslateUI(
      translate::TRANSLATE_STEP_AFTER_TRANSLATE,
      base::SysNSStringToUTF8(kTestFromLangCode),
      base::SysNSStringToUTF8(kTestToLangCode),
      translate::TranslateErrors::INITIALIZATION_ERROR,
      /*triggered_from_menu=*/false);

  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests CWVTranslationController has at least one supported language.
TEST_F(CWVTranslationControllerTest, HasSupportedLanguages) {
  EXPECT_LT(0ul, translation_controller_.supportedLanguages.count);
}

// Tests CWVTranslationController properly sets language policies.
TEST_F(CWVTranslationControllerTest, SetLanguagePolicy) {
  CWVTranslationLanguage* lang =
      [translation_controller_.supportedLanguages anyObject];
  std::string lang_code = base::SysNSStringToUTF8(lang.languageCode);
  CWVTranslationPolicy* policy = [CWVTranslationPolicy translationPolicyNever];
  [translation_controller_ setTranslationPolicy:policy forPageLanguage:lang];
  EXPECT_TRUE(translate_prefs_->IsBlockedLanguage(lang_code));
}

// Tests CWVTranslationController properly reads language policies.
TEST_F(CWVTranslationControllerTest, ReadLanguagePolicy) {
  CWVTranslationLanguage* lang =
      [translation_controller_.supportedLanguages anyObject];
  std::string lang_code = base::SysNSStringToUTF8(lang.languageCode);
  if (std::optional<base::i18n::LanguageTag> parsed_tag =
          base::i18n::LanguageTagConverter::GetInstance().FromString(
              lang_code)) {
    translate_prefs_->AddToLanguageList(*parsed_tag, /*force_blocked=*/true);
  }
  CWVTranslationPolicy* policy =
      [translation_controller_ translationPolicyForPageLanguage:lang];
  EXPECT_EQ(CWVTranslationPolicyNever, policy.type);
  EXPECT_NSEQ(nil, policy.language);
}

// Tests CWVTranslationController properly sets page host policies.
TEST_F(CWVTranslationControllerTest, PageHostPolicy) {
  CWVTranslationPolicy* policy = [CWVTranslationPolicy translationPolicyNever];
  [translation_controller_ setTranslationPolicy:policy
                                    forPageHost:kTestPageHost];
  EXPECT_TRUE(translate_prefs_->IsSiteOnNeverPromptList(
      base::SysNSStringToUTF8(kTestPageHost)));
}

// Tests CWVTranslationController properly reads page host policies.
TEST_F(CWVTranslationControllerTest, ReadPageHostPolicy) {
  translate_prefs_->AddSiteToNeverPromptList(
      base::SysNSStringToUTF8(kTestPageHost));
  CWVTranslationPolicy* policy =
      [translation_controller_ translationPolicyForPageHost:kTestPageHost];
  EXPECT_EQ(CWVTranslationPolicyNever, policy.type);
  EXPECT_NSEQ(nil, policy.language);
}

// Tests CWVTranslationController translate page method.
TEST_F(CWVTranslationControllerTest, TranslatePage) {
  NSArray* langs = translation_controller_.supportedLanguages.allObjects;
  CWVTranslationLanguage* from_lang = langs.firstObject;
  CWVTranslationLanguage* to_lang = langs.lastObject;
  std::string from_code = base::SysNSStringToUTF8(from_lang.languageCode);
  std::string to_code = base::SysNSStringToUTF8(to_lang.languageCode);

  EXPECT_CALL(*translate_client_, TranslatePage(from_code, to_code, true));

  [translation_controller_ translatePageFromLanguage:from_lang
                                          toLanguage:to_lang
                                       userInitiated:YES];
}

// Tests CWVTranslationController revert method.
TEST_F(CWVTranslationControllerTest, Revert) {
  EXPECT_CALL(*translate_client_, RevertTranslation);
  [translation_controller_ revertTranslation];
}

// Tests CWVTranslationController request translation offer method.
TEST_F(CWVTranslationControllerTest, RequestTranslationOffer) {
  EXPECT_CALL(*translate_client_, RequestTranslationOffer)
      .WillOnce(Return(true));
  EXPECT_TRUE([translation_controller_ requestTranslationOffer]);
}

// Tests the CWVTranslationController invokes the
// didDeterminePageLanguageDetectionDetails delegate method.
TEST_F(CWVTranslationControllerTest, OnLanguageDetermined) {
  std::string language_code =
      std::string(base::SysNSStringToUTF8(kTestFromLangCode));

  translate::LanguageDetectionDetails details;
  details.has_notranslate = NO;
  details.content_language = language_code;
  details.model_detected_language = language_code;
  details.is_model_reliable = YES;
  details.html_root_language = language_code;
  details.adopted_language = language_code;

  CWVTranslationLanguageDetectionDetails* language_detection_details =
      [CWVTranslationLanguageDetectionDetails
          languageDetectionDetailsFrom:details];

  id delegate = OCMProtocolMock(@protocol(CWVTranslationControllerDelegate));
  translation_controller_.delegate = delegate;
  OCMExpect([delegate translationController:translation_controller_
      didDeterminePageLanguageDetectionDetails:language_detection_details]);

  OnLanguageDetermined(details);

  EXPECT_OCMOCK_VERIFY(delegate);
  EXPECT_TRUE([translation_controller_.languageDetectionDetails
      isEqual:language_detection_details]);
}

}  // namespace ios_web_view
