// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/message_loop.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/protector/histograms.h"
#include "chrome/browser/protector/mock_protector_service.h"
#include "chrome/browser/protector/protector_service_factory.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "googleurl/src/gurl.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"

using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;

namespace protector {

namespace {

// Keyword names and URLs used for testing.

const string16 example_info = ASCIIToUTF16("Info");
const string16 example_info_long = ASCIIToUTF16("Example.info");
const std::string http_example_info = "http://example.info/%s";
const string16 example_com = ASCIIToUTF16("Com");
const string16 example_com_long = ASCIIToUTF16("Example.com");
const std::string http_example_com = "http://example.com/%s";
const string16 example_net = ASCIIToUTF16("Net");
const std::string http_example_net = "http://example.net/%s";

};

class DefaultSearchProviderChangeTest : public InProcessBrowserTest {
 public:
  virtual void SetUpOnMainThread() OVERRIDE {
    mock_protector_service_ =
        MockProtectorService::BuildForProfile(browser()->profile());

    // Ensure that TemplateURLService is loaded.
    turl_service_ =
        TemplateURLServiceFactory::GetForProfile(browser()->profile());
    ui_test_utils::WaitForTemplateURLServiceToLoad(turl_service_);

    prepopulated_url_.reset(
        TemplateURLPrepopulateData::GetPrepopulatedDefaultSearch(NULL));
  }

  virtual void CleanUpOnMainThread() OVERRIDE {
    EXPECT_CALL(*mock_protector_service_, Shutdown());
  }

  TemplateURL* MakeTemplateURL(const string16& short_name,
                               const string16& keyword,
                               const std::string& search_url) {
    TemplateURL* url = new TemplateURL;
    url->set_short_name(short_name);
    if (keyword.empty())
      url->set_autogenerate_keyword(true);
    else
      url->set_keyword(keyword);
    url->SetURL(search_url, 0, 0);
    return url;
  }

  const TemplateURL* FindTemplateURL(const std::string& search_url) {
    TemplateURLService::TemplateURLVector urls =
        turl_service_->GetTemplateURLs();
    for (TemplateURLService::TemplateURLVector::const_iterator
         it = urls.begin(); it != urls.end(); ++it) {
      if ((*it)->url()->url() == search_url)
        return *it;
    }
    return NULL;
  }

  // Adds a copy of |turl| that will be owned by TemplateURLService.
  void AddCopy(TemplateURL* turl) {
    TemplateURL* turl_copy = new TemplateURL(*turl);
    turl_service_->Add(turl_copy);
  }

  void AddAndSetDefault(TemplateURL* turl) {
    turl_service_->Add(turl);
    turl_service_->SetDefaultSearchProvider(turl);
  }

  string16 GetBubbleMessage(const string16& short_name = string16()) {
    return short_name.empty() ?
        l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_CHANGE_MESSAGE) :
        l10n_util::GetStringFUTF16(IDS_SEARCH_ENGINE_CHANGE_NO_BACKUP_MESSAGE,
                                   short_name);
  }

  string16 GetChangeSearchButtonText(const string16& short_name = string16()) {
    return short_name.empty() ?
        l10n_util::GetStringUTF16(IDS_CHANGE_SEARCH_ENGINE_NO_NAME) :
        l10n_util::GetStringFUTF16(IDS_CHANGE_SEARCH_ENGINE, short_name);
  }

  string16 GetKeepSearchButtonText(const string16& short_name = string16()) {
    return short_name.empty() ?
        l10n_util::GetStringUTF16(IDS_KEEP_SETTING) :
        l10n_util::GetStringFUTF16(IDS_KEEP_SEARCH_ENGINE, short_name);
  }

  string16 GetOpenSettingsButtonText() {
    return l10n_util::GetStringUTF16(IDS_SELECT_SEARCH_ENGINE);
  }

  void ExpectSettingsOpened(const std::string& subpage) {
    GURL settings_url(chrome::kChromeUISettingsURL + subpage);
    EXPECT_CALL(*mock_protector_service_, OpenTab(settings_url, browser()));
  }

  void ExpectHistogramCount(const std::string& name,
                            size_t bucket,
                            base::Histogram::Count count) {
    base::Histogram* histogram;
    EXPECT_TRUE(base::StatisticsRecorder::FindHistogram(name, &histogram));
    base::Histogram::SampleSet sample;
    histogram->SnapshotSample(&sample);
    EXPECT_EQ(count, sample.counts(bucket)) <<
        "Invalid " << name << " value for bucket " << bucket;
  }

 protected:
  MockProtectorService* mock_protector_service_;
  TemplateURLService* turl_service_;
  scoped_ptr<TemplateURL> prepopulated_url_;
};

// Tests below call both Apply and Discard on a single change instance.
// This is test-only and should not be happening in real code.

// |backup_url| in further test cases is owned by DefaultSearchProviderChange
// instance, while other TemplateURLs are owned by TemplateURLService.

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest, BackupValid) {
  // Most common case: current default search provider exists, backup is valid,
  // they are different.
  TemplateURL* backup_url =
      MakeTemplateURL(example_info, ASCIIToUTF16("a"), http_example_info);
  TemplateURL* current_url =
      MakeTemplateURL(example_com, ASCIIToUTF16("b"), http_example_com);

  AddCopy(backup_url);
  AddAndSetDefault(current_url);

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(current_url, backup_url));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that backup is active.
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());

  // Verify histograms.
  ExpectHistogramCount(kProtectorHistogramSearchProviderHijacked,
                       SEARCH_ENGINE_OTHER, 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderRestored,
                       SEARCH_ENGINE_OTHER, 1);

  // Verify text messages.
  EXPECT_EQ(GetBubbleMessage(), change->GetBubbleMessage());
  EXPECT_EQ(GetChangeSearchButtonText(example_com),
            change->GetApplyButtonText());
  EXPECT_EQ(GetKeepSearchButtonText(example_info),
            change->GetDiscardButtonText());

  // Discard does nothing - backup was already active.
  change->Discard(browser());
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());
  ExpectHistogramCount(kProtectorHistogramSearchProviderDiscarded,
                       SEARCH_ENGINE_OTHER, 1);

  // Verify that Apply switches back to |current_url|.
  change->Apply(browser());
  EXPECT_EQ(FindTemplateURL(http_example_com),
            turl_service_->GetDefaultSearchProvider());
  ExpectHistogramCount(kProtectorHistogramSearchProviderApplied,
                       SEARCH_ENGINE_OTHER, 1);
}

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest, BackupValidLongNames) {
  // Verify that search provider names that are too long are not displayed.
  TemplateURL* backup_url =
      MakeTemplateURL(example_info, ASCIIToUTF16("a"), http_example_info);
  TemplateURL* backup_url_long =
      MakeTemplateURL(example_info_long, ASCIIToUTF16("a"), http_example_info);
  TemplateURL* current_url =
      MakeTemplateURL(example_com, ASCIIToUTF16("b"), http_example_com);
  TemplateURL* current_url_long =
      MakeTemplateURL(example_com_long, ASCIIToUTF16("b"), http_example_com);

  {
    // Backup name too long.
    AddCopy(backup_url_long);
    AddAndSetDefault(current_url);

    scoped_ptr<BaseSettingChange> change(
        CreateDefaultSearchProviderChange(current_url, backup_url_long));
    ASSERT_TRUE(change.get());
    ASSERT_TRUE(change->Init(browser()->profile()));

    // Verify text messages.
    EXPECT_EQ(GetBubbleMessage(), change->GetBubbleMessage());
    EXPECT_EQ(GetChangeSearchButtonText(example_com),
              change->GetApplyButtonText());
    EXPECT_EQ(GetKeepSearchButtonText(), change->GetDiscardButtonText());
  }

  {
    // Current name too long.
    AddCopy(backup_url);
    AddAndSetDefault(current_url_long);

    scoped_ptr<BaseSettingChange> change(
        CreateDefaultSearchProviderChange(current_url_long, backup_url));
    ASSERT_TRUE(change.get());
    ASSERT_TRUE(change->Init(browser()->profile()));

    // Verify text messages.
    EXPECT_EQ(GetBubbleMessage(), change->GetBubbleMessage());
    EXPECT_EQ(GetChangeSearchButtonText(), change->GetApplyButtonText());
    EXPECT_EQ(GetKeepSearchButtonText(example_info),
              change->GetDiscardButtonText());
  }
}

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest, BackupInvalid) {
  // Backup is invalid, current search provider exists, fallback to the
  // prepopulated default search, which exists among keywords.
  TemplateURL* current_url =
      MakeTemplateURL(example_com, ASCIIToUTF16("b"), http_example_com);

  AddAndSetDefault(current_url);

  // Prepopulated default search must exist.
  ASSERT_TRUE(FindTemplateURL(prepopulated_url_->url()->url()));

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(current_url, NULL));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that the prepopulated default search is active.
  EXPECT_EQ(FindTemplateURL(prepopulated_url_->url()->url()),
            turl_service_->GetDefaultSearchProvider());

  // Verify histograms.
  ExpectHistogramCount(kProtectorHistogramSearchProviderCorrupt,
                       SEARCH_ENGINE_OTHER, 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderRestored,
                       prepopulated_url_->search_engine_type(), 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderFallback,
                       prepopulated_url_->search_engine_type(), 1);

  // Verify text messages.
  EXPECT_EQ(GetBubbleMessage(prepopulated_url_->short_name()),
            change->GetBubbleMessage());
  EXPECT_EQ(GetChangeSearchButtonText(example_com),
            change->GetApplyButtonText());
  EXPECT_EQ(GetOpenSettingsButtonText(), change->GetDiscardButtonText());

  // Verify that search engine settings are opened by Discard.
  ExpectSettingsOpened(chrome::kSearchEnginesSubPage);
  change->Discard(browser());
  EXPECT_EQ(FindTemplateURL(prepopulated_url_->url()->url()),
            turl_service_->GetDefaultSearchProvider());

  // Verify that Apply switches back to |current_url|.
  change->Apply(browser());
  EXPECT_EQ(FindTemplateURL(http_example_com),
            turl_service_->GetDefaultSearchProvider());
}

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest,
                       BackupInvalidFallbackRemoved) {
  // Backup is invalid, current search provider exists, fallback to the
  // prepopulated default search, which was removed from keywords (will be
  // added).
  TemplateURL* current_url =
      MakeTemplateURL(example_com, ASCIIToUTF16("b"), http_example_com);

  AddAndSetDefault(current_url);

  const TemplateURL* prepopulated_default =
      FindTemplateURL(prepopulated_url_->url()->url());
  // Prepopulated default search must exist, remove it.
  ASSERT_TRUE(prepopulated_default);
  turl_service_->Remove(prepopulated_default);

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(current_url, NULL));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that the prepopulated default search is active.
  EXPECT_EQ(FindTemplateURL(prepopulated_url_->url()->url()),
            turl_service_->GetDefaultSearchProvider());

  // Verify histograms.
  ExpectHistogramCount(kProtectorHistogramSearchProviderCorrupt,
                       SEARCH_ENGINE_OTHER, 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderRestored,
                       prepopulated_url_->search_engine_type(), 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderFallback,
                       prepopulated_url_->search_engine_type(), 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderMissing,
                       prepopulated_url_->search_engine_type(), 1);

  // Verify text messages.
  EXPECT_EQ(GetBubbleMessage(prepopulated_url_->short_name()),
            change->GetBubbleMessage());
  EXPECT_EQ(GetChangeSearchButtonText(example_com),
            change->GetApplyButtonText());
  EXPECT_EQ(GetOpenSettingsButtonText(), change->GetDiscardButtonText());

  // Verify that search engine settings are opened by Discard.
  ExpectSettingsOpened(chrome::kSearchEnginesSubPage);
  change->Discard(browser());
  EXPECT_EQ(FindTemplateURL(prepopulated_url_->url()->url()),
            turl_service_->GetDefaultSearchProvider());

  // Verify that Apply switches back to |current_url|.
  change->Apply(browser());
  EXPECT_EQ(FindTemplateURL(http_example_com),
            turl_service_->GetDefaultSearchProvider());
}

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest,
                       BackupValidCurrentRemoved) {
  // Backup is valid, no current search provider.
  TemplateURL* backup_url =
      MakeTemplateURL(example_info, ASCIIToUTF16("a"), http_example_info);

  AddCopy(backup_url);
  turl_service_->SetDefaultSearchProvider(NULL);

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(NULL, backup_url));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that backup is active.
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());

  // Verify histograms.
  ExpectHistogramCount(kProtectorHistogramSearchProviderHijacked,
                       SEARCH_ENGINE_NONE, 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderRestored,
                       SEARCH_ENGINE_OTHER, 1);

  // Verify text messages.
  EXPECT_EQ(GetBubbleMessage(), change->GetBubbleMessage());
  EXPECT_EQ(GetOpenSettingsButtonText(), change->GetApplyButtonText());
  EXPECT_EQ(GetKeepSearchButtonText(example_info),
            change->GetDiscardButtonText());

  // Discard does nothing - backup was already active.
  change->Discard(browser());
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());

   // Verify that search engine settings are opened by Apply (no other changes).
  ExpectSettingsOpened(chrome::kSearchEnginesSubPage);
  change->Apply(browser());
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());
}

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest,
                       BackupInvalidCurrentRemoved) {
  // Backup is invalid, no current search provider, fallback to the prepopulated
  // default search.
  turl_service_->SetDefaultSearchProvider(NULL);

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(NULL, NULL));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that the prepopulated default search is active.
  EXPECT_EQ(FindTemplateURL(prepopulated_url_->url()->url()),
            turl_service_->GetDefaultSearchProvider());

  // Verify histograms.
  ExpectHistogramCount(kProtectorHistogramSearchProviderCorrupt,
                       SEARCH_ENGINE_NONE, 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderRestored,
                       prepopulated_url_->search_engine_type(), 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderFallback,
                       prepopulated_url_->search_engine_type(), 1);

  // Verify text messages.
  EXPECT_EQ(GetBubbleMessage(prepopulated_url_->short_name()),
            change->GetBubbleMessage());
  EXPECT_EQ(string16(), change->GetApplyButtonText());
  EXPECT_EQ(GetOpenSettingsButtonText(), change->GetDiscardButtonText());

  // Verify that search engine settings are opened by Discard.
  ExpectSettingsOpened(chrome::kSearchEnginesSubPage);
  change->Discard(browser());
  EXPECT_EQ(FindTemplateURL(prepopulated_url_->url()->url()),
            turl_service_->GetDefaultSearchProvider());
}

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest,
                       BackupInvalidFallbackSameAsCurrent) {
  // Backup is invalid, fallback to the prepopulated default search which is
  // same as the current search provider.
  const TemplateURL* current_url = turl_service_->GetDefaultSearchProvider();

  // Verify that current search provider is same as the prepopulated default.
  ASSERT_TRUE(current_url);
  ASSERT_EQ(current_url, FindTemplateURL(prepopulated_url_->url()->url()));

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(current_url, NULL));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that the default search has not changed.
  EXPECT_EQ(current_url, turl_service_->GetDefaultSearchProvider());

  // Verify histograms.
  ExpectHistogramCount(kProtectorHistogramSearchProviderCorrupt,
                       prepopulated_url_->search_engine_type(), 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderRestored,
                       prepopulated_url_->search_engine_type(), 1);
  ExpectHistogramCount(kProtectorHistogramSearchProviderFallback,
                       prepopulated_url_->search_engine_type(), 1);

  // Verify text messages.
  EXPECT_EQ(GetBubbleMessage(prepopulated_url_->short_name()),
            change->GetBubbleMessage());
  EXPECT_EQ(string16(), change->GetApplyButtonText());
  EXPECT_EQ(GetOpenSettingsButtonText(), change->GetDiscardButtonText());

  // Verify that search engine settings are opened by Discard.
  ExpectSettingsOpened(chrome::kSearchEnginesSubPage);
  change->Discard(browser());
  EXPECT_EQ(current_url, turl_service_->GetDefaultSearchProvider());
}


IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest,
                       DefaultSearchProviderChangedByUser) {
  // Default search provider is changed by user while the error is active.
  // Setup is the same as in BackupValid test case.
  TemplateURL* backup_url =
      MakeTemplateURL(example_info, ASCIIToUTF16("a"), http_example_info);
  TemplateURL* current_url =
      MakeTemplateURL(example_com, ASCIIToUTF16("b"), http_example_com);
  TemplateURL* new_url =
      MakeTemplateURL(example_net, ASCIIToUTF16("c"), http_example_net);

  AddCopy(backup_url);
  AddAndSetDefault(current_url);

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(current_url, backup_url));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that backup is active.
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());

  // Verify that changing search provider externally dismissed the change.
  EXPECT_CALL(*mock_protector_service_, DismissChange());
  AddAndSetDefault(new_url);
}

IN_PROC_BROWSER_TEST_F(DefaultSearchProviderChangeTest,
                       CurrentSearchProviderRemovedByUser) {
  // Current search provider is removed by user while the error is active.
  // Setup is the same as in BackupValid test case.
  TemplateURL* backup_url =
      MakeTemplateURL(example_info, ASCIIToUTF16("a"), http_example_info);
  TemplateURL* current_url =
      MakeTemplateURL(example_com, ASCIIToUTF16("b"), http_example_com);

  AddCopy(backup_url);
  AddAndSetDefault(current_url);

  scoped_ptr<BaseSettingChange> change(
      CreateDefaultSearchProviderChange(current_url, backup_url));
  ASSERT_TRUE(change.get());
  ASSERT_TRUE(change->Init(browser()->profile()));

  // Verify that backup is active.
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());

  turl_service_->Remove(current_url);

  // Verify that text messages altered after removing |current_url|.
  EXPECT_EQ(GetBubbleMessage(), change->GetBubbleMessage());
  EXPECT_EQ(GetOpenSettingsButtonText(), change->GetApplyButtonText());
  EXPECT_EQ(GetKeepSearchButtonText(example_info),
            change->GetDiscardButtonText());

  // Verify that search engine settings are opened by Apply.
  ExpectSettingsOpened(chrome::kSearchEnginesSubPage);
  change->Apply(browser());
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());

  // Discard does nothing - backup was already active.
  change->Discard(browser());
  EXPECT_EQ(FindTemplateURL(http_example_info),
            turl_service_->GetDefaultSearchProvider());
}

}  // namespace protector