// Copyright 2019 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 "discovery/mdns/mdns_probe.h"

#include <memory>
#include <utility>

#include "discovery/common/config.h"
#include "discovery/mdns/mdns_probe_manager.h"
#include "discovery/mdns/mdns_querier.h"
#include "discovery/mdns/mdns_random.h"
#include "discovery/mdns/mdns_receiver.h"
#include "discovery/mdns/mdns_sender.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "platform/test/fake_clock.h"
#include "platform/test/fake_task_runner.h"
#include "platform/test/fake_udp_socket.h"

using testing::_;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;

namespace openscreen {
namespace discovery {

class MockMdnsSender : public MdnsSender {
 public:
  explicit MockMdnsSender(UdpSocket* socket) : MdnsSender(socket) {}
  MOCK_METHOD1(SendMulticast, Error(const MdnsMessage& message));
  MOCK_METHOD2(SendMessage,
               Error(const MdnsMessage& message, const IPEndpoint& endpoint));
};

class MockObserver : public MdnsProbeImpl::Observer {
 public:
  MOCK_METHOD1(OnProbeSuccess, void(MdnsProbe*));
  MOCK_METHOD1(OnProbeFailure, void(MdnsProbe*));
};

class MdnsProbeTests : public testing::Test {
 public:
  MdnsProbeTests()
      : clock_(Clock::now()),
        task_runner_(&clock_),
        socket_(&task_runner_),
        sender_(&socket_),
        receiver_(config_) {
    EXPECT_EQ(task_runner_.delayed_task_count(), 0);
    probe_ = CreateProbe();
    EXPECT_EQ(task_runner_.delayed_task_count(), 1);
  }

 protected:
  std::unique_ptr<MdnsProbeImpl> CreateProbe() {
    return std::make_unique<MdnsProbeImpl>(&sender_, &receiver_, &random_,
                                           &task_runner_, FakeClock::now,
                                           &observer_, name_, address_v4_);
  }

  MdnsMessage CreateMessage(const DomainName& domain) {
    MdnsMessage message(0, MessageType::Response);
    SrvRecordRdata rdata(0, 0, 80, domain);
    MdnsRecord record(std::move(domain), DnsType::kSRV, DnsClass::kIN,
                      RecordType::kUnique, std::chrono::seconds(1),
                      std::move(rdata));
    message.AddAnswer(record);
    return message;
  }

  void OnMessageReceived(const MdnsMessage& message) {
    probe_->OnMessageReceived(message);
  }

  Config config_;
  FakeClock clock_;
  FakeTaskRunner task_runner_;
  FakeUdpSocket socket_;
  StrictMock<MockMdnsSender> sender_;
  MdnsReceiver receiver_;
  MdnsRandom random_;
  StrictMock<MockObserver> observer_;

  std::unique_ptr<MdnsProbeImpl> probe_;

  const DomainName name_{"test", "_googlecast", "_tcp", "local"};
  const DomainName name2_{"test2", "_googlecast", "_tcp", "local"};

  const IPAddress address_v4_{192, 168, 0, 0};
  const IPEndpoint endpoint_v4_{address_v4_, 80};
};

TEST_F(MdnsProbeTests, TestNoCancelationFlow) {
  EXPECT_CALL(sender_, SendMulticast(_));
  clock_.Advance(kDelayBetweenProbeQueries);
  EXPECT_EQ(task_runner_.delayed_task_count(), 1);
  testing::Mock::VerifyAndClearExpectations(&sender_);

  EXPECT_CALL(sender_, SendMulticast(_));
  clock_.Advance(kDelayBetweenProbeQueries);
  EXPECT_EQ(task_runner_.delayed_task_count(), 1);
  testing::Mock::VerifyAndClearExpectations(&sender_);

  EXPECT_CALL(sender_, SendMulticast(_));
  clock_.Advance(kDelayBetweenProbeQueries);
  EXPECT_EQ(task_runner_.delayed_task_count(), 1);
  testing::Mock::VerifyAndClearExpectations(&sender_);

  EXPECT_CALL(observer_, OnProbeSuccess(probe_.get())).Times(1);
  clock_.Advance(kDelayBetweenProbeQueries);
  EXPECT_EQ(task_runner_.delayed_task_count(), 0);
}

TEST_F(MdnsProbeTests, CancelationWhenMatchingMessageReceived) {
  EXPECT_CALL(observer_, OnProbeFailure(probe_.get())).Times(1);
  OnMessageReceived(CreateMessage(name_));
}

TEST_F(MdnsProbeTests, TestNoCancelationOnUnrelatedMessages) {
  OnMessageReceived(CreateMessage(name2_));

  EXPECT_CALL(sender_, SendMulticast(_));
  clock_.Advance(kDelayBetweenProbeQueries);
  EXPECT_EQ(task_runner_.delayed_task_count(), 1);
  testing::Mock::VerifyAndClearExpectations(&sender_);
}

}  // namespace discovery
}  // namespace openscreen
