// Copyright 2015 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/media_router/browser/issue_manager.h"

#include <string>

#include "base/test/task_environment.h"
#include "components/media_router/browser/test/test_helper.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"

using testing::_;
using testing::SaveArg;

namespace media_router {
namespace {

IssueInfo CreateTestIssue(IssueInfo::Severity severity) {
  IssueInfo issue("title", severity, "sinkId1");
  issue.message = "message";
  return issue;
}

}  // namespace

class IssueManagerTest : public ::testing::Test {
 protected:
  IssueManagerTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  ~IssueManagerTest() override = default;

  content::BrowserTaskEnvironment task_environment_;
  IssueManager manager_;
};

TEST_F(IssueManagerTest, AddAndClearIssue) {
  IssueInfo issue_info1 = CreateTestIssue(IssueInfo::Severity::WARNING);

  // Add initial issue.
  manager_.AddIssue(issue_info1);

  Issue issue1 = Issue::CreateIssueWithIssueInfo(IssueInfo());
  MockIssuesObserver observer(&manager_);
  EXPECT_CALL(observer, OnIssue(_)).WillOnce(SaveArg<0>(&issue1));
  observer.Init();
  ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&observer));
  EXPECT_EQ(issue_info1, issue1.info());

  IssueInfo issue_info2 = CreateTestIssue(IssueInfo::Severity::NOTIFICATION);
  Issue issue2 = Issue::CreateIssueWithIssueInfo(IssueInfo());
  manager_.AddIssue(issue_info2);

  // Clear |issue1|. Observer will be notified with |issue2|.
  EXPECT_CALL(observer, OnIssue(_)).WillOnce(SaveArg<0>(&issue2));
  manager_.ClearIssue(issue1.id());
  ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&observer));
  EXPECT_EQ(issue_info2, issue2.info());

  // All issues cleared. Observer will be notified with |nullptr| that there are
  // no more issues.
  EXPECT_CALL(observer, OnIssuesCleared());
  manager_.ClearIssue(issue2.id());
}

TEST_F(IssueManagerTest, AddPermissionIssue) {
  IssueInfo issue_info1 = CreateTestIssue(IssueInfo::Severity::WARNING);

  // Add initial issue.
  Issue issue1 = Issue::CreateIssueWithIssueInfo(IssueInfo());
  MockIssuesObserver observer(&manager_);
  EXPECT_CALL(observer, OnIssue(_)).WillOnce(SaveArg<0>(&issue1));
  manager_.AddIssue(issue_info1);
  observer.Init();
  testing::Mock::VerifyAndClearExpectations(&observer);
  EXPECT_EQ(issue_info1, issue1.info());

  // The permission rejected issue will clear out existing issues and be the top
  // issue.
  Issue issue2 = Issue::CreateIssueWithIssueInfo(IssueInfo());
  EXPECT_CALL(observer, OnIssue(_)).WillOnce(SaveArg<0>(&issue2));
  manager_.AddPermissionRejectedIssue();
  testing::Mock::VerifyAndClearExpectations(&observer);
  EXPECT_TRUE(issue2.is_permission_rejected_issue());
}

TEST_F(IssueManagerTest, IssuesDeletionShouldNotAffectOrder) {
  MockIssuesObserver observer(&manager_);
  observer.Init();

  Issue issue1 = Issue::CreateIssueWithIssueInfo(IssueInfo());
  EXPECT_CALL(observer, OnIssue(_)).WillOnce(SaveArg<0>(&issue1));

  IssueInfo issue_info1 = CreateTestIssue(IssueInfo::Severity::NOTIFICATION);
  manager_.AddIssue(issue_info1);

  IssueInfo issue_info2 = CreateTestIssue(IssueInfo::Severity::WARNING);
  manager_.AddIssue(issue_info2);

  IssueInfo issue_info3 = CreateTestIssue(IssueInfo::Severity::NOTIFICATION);
  manager_.AddIssue(issue_info3);

  ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&observer));
  EXPECT_EQ(issue_info1, issue1.info());

  Issue issue2 = Issue::CreateIssueWithIssueInfo(IssueInfo());
  EXPECT_CALL(observer, OnIssue(_)).WillOnce(SaveArg<0>(&issue2));
  manager_.ClearIssue(issue1.id());
  EXPECT_EQ(issue_info2, issue2.info());

  manager_.ClearAllIssues();
}

TEST_F(IssueManagerTest, AddSameIssueInfoHasNoEffect) {
  IssueInfo issue_info = CreateTestIssue(IssueInfo::Severity::WARNING);

  MockIssuesObserver observer(&manager_);
  observer.Init();

  Issue issue = Issue::CreateIssueWithIssueInfo(IssueInfo());
  EXPECT_CALL(observer, OnIssue(_)).WillOnce(SaveArg<0>(&issue));
  manager_.AddIssue(issue_info);
  ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&observer));
  EXPECT_EQ(issue_info, issue.info());

  // Adding the same IssueInfo has no effect.
  manager_.AddIssue(issue_info);

  EXPECT_CALL(observer, OnIssuesCleared());
  manager_.ClearIssue(issue.id());
}

TEST_F(IssueManagerTest, IssuesGetAutoDismissed) {
  MockIssuesObserver observer(&manager_);
  observer.Init();

  EXPECT_CALL(observer, OnIssue(_)).Times(1);
  IssueInfo issue_info1 = CreateTestIssue(IssueInfo::Severity::NOTIFICATION);
  manager_.AddIssue(issue_info1);

  EXPECT_CALL(observer, OnIssuesCleared()).Times(1);
  base::TimeDelta timeout = IssueManager::GetAutoDismissTimeout(issue_info1);
  EXPECT_FALSE(timeout.is_zero());
  EXPECT_TRUE(task_environment_.MainThreadIsIdle());
  task_environment_.FastForwardBy(timeout);

  EXPECT_CALL(observer, OnIssue(_)).Times(1);
  IssueInfo issue_info2 = CreateTestIssue(IssueInfo::Severity::WARNING);
  manager_.AddIssue(issue_info2);

  EXPECT_CALL(observer, OnIssuesCleared()).Times(1);
  timeout = IssueManager::GetAutoDismissTimeout(issue_info2);
  EXPECT_FALSE(timeout.is_zero());
  EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
  task_environment_.FastForwardBy(timeout);
  EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}

TEST_F(IssueManagerTest, IssueAutoDismissNoopsIfAlreadyCleared) {
  MockIssuesObserver observer(&manager_);
  observer.Init();

  Issue issue1 = Issue::CreateIssueWithIssueInfo(IssueInfo());
  EXPECT_CALL(observer, OnIssue(_)).Times(1).WillOnce(SaveArg<0>(&issue1));
  IssueInfo issue_info1 = CreateTestIssue(IssueInfo::Severity::NOTIFICATION);
  manager_.AddIssue(issue_info1);
  ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&observer));

  EXPECT_CALL(observer, OnIssuesCleared()).Times(1);
  EXPECT_GT(task_environment_.GetPendingMainThreadTaskCount(), 0u);
  manager_.ClearIssue(issue1.id());

  EXPECT_CALL(observer, OnIssuesCleared()).Times(0);
  base::TimeDelta timeout = IssueManager::GetAutoDismissTimeout(issue_info1);
  EXPECT_FALSE(timeout.is_zero());
  task_environment_.FastForwardBy(timeout);
}

TEST_F(IssueManagerTest, ClearAllIssues) {
  MockIssuesObserver observer(&manager_);
  observer.Init();

  EXPECT_CALL(observer, OnIssue(_)).Times(1);
  manager_.AddIssue(CreateTestIssue(IssueInfo::Severity::NOTIFICATION));
  manager_.AddIssue(CreateTestIssue(IssueInfo::Severity::WARNING));

  EXPECT_CALL(observer, OnIssuesCleared()).Times(1);
  manager_.ClearAllIssues();
}

TEST_F(IssueManagerTest, ClearTopIssueForSink) {
  const char sink_id_1[] = "sink_1";
  const char sink_id_2[] = "sink_2";
  IssueInfo issue_info1("title", IssueInfo::Severity::WARNING, sink_id_1);
  IssueInfo issue_info2("title", IssueInfo::Severity::NOTIFICATION, sink_id_2);
  manager_.AddIssue(issue_info1);
  manager_.AddIssue(issue_info2);

  // issue_info1 is the top issue.
  MockIssuesObserver observer(&manager_);
  EXPECT_CALL(observer, OnIssue)
      .WillOnce([&issue_info1](const Issue& received_issue) {
        EXPECT_EQ(issue_info1, received_issue.info());
      });
  observer.Init();
  testing::Mock::VerifyAndClearExpectations(&observer);

  // No-op since the top issue isn't associated with sink_id_2.
  EXPECT_CALL(observer, OnIssue).Times(0);
  manager_.ClearTopIssueForSink(sink_id_2);
  testing::Mock::VerifyAndClearExpectations(&observer);

  // Clear top issue makes issue_info2 the top issue.
  EXPECT_CALL(observer, OnIssue)
      .WillOnce([&issue_info2](const Issue& received_issue) {
        EXPECT_EQ(issue_info2, received_issue.info());
      });
  manager_.ClearTopIssueForSink(sink_id_1);
  testing::Mock::VerifyAndClearExpectations(&observer);

  EXPECT_CALL(observer, OnIssuesCleared);
  manager_.ClearTopIssueForSink(sink_id_2);
  testing::Mock::VerifyAndClearExpectations(&observer);
}

}  // namespace media_router
