// Copyright (c) 2019, 2021 by Robert Bosch GmbH. All rights reserved.
// Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

#include "test.hpp"

#include "iceoryx_hoofs/cxx/convert.hpp"
#include "iceoryx_hoofs/cxx/serialization.hpp"
#include "iceoryx_hoofs/cxx/string.hpp"
#include "iceoryx_hoofs/testing/mocks/logger_mock.hpp"
#include "iceoryx_posh/capro/service_description.hpp"
/// @todo #415 replace the service registry include with the new discovery API header
#include "iceoryx_posh/internal/roudi/service_registry.hpp"

#include <cstdint>

namespace
{
using namespace ::testing;

using namespace iox::capro;

class ServiceDescription_test : public Test
{
  public:
    IdString_t testService;
    IdString_t testInstance;
    IdString_t testEvent;
    void SetUp(){};
    void TearDown(){};
};

/// BEGIN CLASSHASH TESTS

TEST_F(ServiceDescription_test, ServiceDescriptionClassHashDefaultCtorCreatesClassHashWithDefaultValues)
{
    ::testing::Test::RecordProperty("TEST_ID", "3b57b18f-cd68-49ee-8fbb-7b1fcc878a16");
    ServiceDescription::ClassHash testHash{};

    EXPECT_EQ(uint32_t(0), testHash[0]);
    EXPECT_EQ(uint32_t(0), testHash[1]);
    EXPECT_EQ(uint32_t(0), testHash[2]);
    EXPECT_EQ(uint32_t(0), testHash[3]);
}

TEST_F(ServiceDescription_test, ServiceDescriptionClassHashCtorCreatesClassHashWithValuesPassedToTheCtor)
{
    ::testing::Test::RecordProperty("TEST_ID", "00d1f6f1-4011-406e-a18e-85af7fa401f4");
    ServiceDescription::ClassHash testHash{1U, 2U, 3U, 4U};

    EXPECT_EQ(uint32_t(1), testHash[0]);
    EXPECT_EQ(uint32_t(2), testHash[1]);
    EXPECT_EQ(uint32_t(3), testHash[2]);
    EXPECT_EQ(uint32_t(4), testHash[3]);
}

TEST_F(ServiceDescription_test, ComparingTwoUnequalClassHashWithEqualityOperatorReturnsFalse)
{
    ::testing::Test::RecordProperty("TEST_ID", "dc2e03b0-d9ac-49fd-8d1d-6e2393ce3d68");
    ServiceDescription::ClassHash testHash1{15U, 25U, 35U, 45U};
    ServiceDescription::ClassHash testHash2{55U, 65U, 75U, 85U};

    EXPECT_FALSE(testHash1 == testHash2);
}

TEST_F(ServiceDescription_test, ComparingTwoUnequalClassHashWithEqualityOperatorReturnsTrue)
{
    ::testing::Test::RecordProperty("TEST_ID", "3423678c-d45e-4b36-bce6-7e6d0a5bc2a6");
    ServiceDescription::ClassHash testHash1{10U, 20U, 30U, 40U};
    ServiceDescription::ClassHash testHash2{10U, 20U, 30U, 40U};

    EXPECT_TRUE(testHash1 == testHash2);
}

TEST_F(ServiceDescription_test, ComparingTwoUnequalClassHashWithNotEqualOperatorReturnsTrue)
{
    ::testing::Test::RecordProperty("TEST_ID", "59d7790f-5d1f-4f1f-9cf6-8f474ae8978f");
    ServiceDescription::ClassHash testHash1{12U, 24U, 36U, 48U};
    ServiceDescription::ClassHash testHash2{60U, 72U, 84U, 96U};

    EXPECT_TRUE(testHash1 != testHash2);
}

TEST_F(ServiceDescription_test, ComparingTwoEqualClassHashWithNotEqualOperatorReturnsFalse)
{
    ::testing::Test::RecordProperty("TEST_ID", "498fa728-7fbb-4e99-8e95-eaf267284f22");
    ServiceDescription::ClassHash testHash1{11U, 22U, 33U, 44U};
    ServiceDescription::ClassHash testHash2{11U, 22U, 33U, 44U};

    EXPECT_FALSE(testHash1 != testHash2);
}

TEST_F(ServiceDescription_test, ClassHashWithValuesAssignedUsingAssignmentOperatorStoresTheValueInTheCorrespondingIndex)
{
    ::testing::Test::RecordProperty("TEST_ID", "2d37a48e-ba08-4fc8-9215-77bac17bd49b");
    ServiceDescription::ClassHash testHash{};

    testHash[0] = 10U;
    testHash[1] = 20U;
    testHash[2] = 30U;
    testHash[3] = 40U;

    EXPECT_THAT(testHash[0], Eq(10U));
    EXPECT_THAT(testHash[1], Eq(20U));
    EXPECT_THAT(testHash[2], Eq(30U));
    EXPECT_THAT(testHash[3], Eq(40U));
}

TEST_F(ServiceDescription_test, ClassHashSubsriptOperatorOutOfBoundsFails)
{
    ::testing::Test::RecordProperty("TEST_ID", "ac4b4cb3-503c-4e39-a549-684176e7557a");
    ServiceDescription::ClassHash testHash{1U, 2U, 3U, 4U};

    testHash[0] = 1U;
    testHash[1] = 2U;
    testHash[2] = 3U;
    testHash[3] = 4U;

    EXPECT_DEATH({ testHash[4] = 5U; }, ".*");
}

/// END CLASSHASH TESTS

/// BEGIN SERVICEDESCRIPTION TESTS

/// @attention The purpose of the Serialization is not to be an alternative Constructor. It is intended to send/receive
/// the ServiceDescription over communication protocols which transfers strings like the MessageQueue. The testcase is
/// only intended to check the functionality by injecting the valus directly.
TEST_F(ServiceDescription_test, ServiceDescriptionSerializationCreatesServiceDescriptionWithValuesPassedToTheCtor)
{
    ::testing::Test::RecordProperty("TEST_ID", "0bda1264-f1b0-41d5-b1c4-f8e7f2a5806a");
    ServiceDescription::ClassHash testHash = {11U, 21U, 31U, 41U};
    testService = "Service";
    testInstance = "Instance";
    testEvent = "Event";
    Scope testScope = Scope::LOCAL;
    Interfaces testInterfaceSource = Interfaces::INTERNAL;
    auto serialObj = iox::cxx::Serialization::create(testService.c_str(),
                                                     testInstance.c_str(),
                                                     testEvent.c_str(),
                                                     testHash[0],
                                                     testHash[1],
                                                     testHash[2],
                                                     testHash[3],
                                                     static_cast<uint16_t>(testScope),
                                                     static_cast<uint16_t>(testInterfaceSource));


    ServiceDescription::deserialize(serialObj)
        .and_then([&](const auto& service) {
            EXPECT_THAT(service.getServiceIDString(), Eq(testService));
            EXPECT_THAT(service.getInstanceIDString(), Eq(testInstance));
            EXPECT_THAT(service.getEventIDString(), Eq(testEvent));
            EXPECT_THAT((service.getClassHash())[0], Eq(testHash[0]));
            EXPECT_THAT((service.getClassHash())[1], Eq(testHash[1]));
            EXPECT_THAT((service.getClassHash())[2], Eq(testHash[2]));
            EXPECT_THAT((service.getClassHash())[3], Eq(testHash[3]));
            EXPECT_THAT(service.getScope(), Eq(Scope::LOCAL));
            EXPECT_THAT(service.getSourceInterface(), Eq(Interfaces::INTERNAL));
        })
        .or_else([](const auto& error) {
            FAIL() << "Deserialization should not fail but failed with: " << static_cast<uint32_t>(error);
        });
}

/// @attention The purpose of the Serialization is not to be an alternative Constructor. It is intended to send/receive
/// the ServiceDescription over communication protocols which transfers strings like the MessageQueue. The testcase is
/// only intended to check the functionality by injecting the valus directly.
TEST_F(ServiceDescription_test,
       ServiceDescriptionObjectInitialisationWithOutOfBoundaryScopeLeadsToInvalidDeserialization)
{
    ::testing::Test::RecordProperty("TEST_ID", "0a94b000-54ac-415a-a7c7-6f1348676f03");
    ServiceDescription::ClassHash testHash = {14U, 28U, 42U, 56U};
    testService = "Service";
    testInstance = "Instance";
    testEvent = "Event";
    uint16_t invalidScope = 3U;
    auto serialObj = iox::cxx::Serialization::create(testService.c_str(),
                                                     testInstance.c_str(),
                                                     testEvent.c_str(),
                                                     testHash[0],
                                                     testHash[1],
                                                     testHash[2],
                                                     testHash[3],
                                                     invalidScope);

    auto deserializationResult = ServiceDescription::deserialize(serialObj);

    ASSERT_TRUE(deserializationResult.has_error());
    EXPECT_THAT(deserializationResult.get_error(), Eq(iox::cxx::Serialization::Error::DESERIALIZATION_FAILED));
}

/// @attention The purpose of the Serialization is not to be an alternative Constructor. It is intended to send/receive
/// the ServiceDescription over communication protocols which transfers strings like the MessageQueue. The testcase is
/// only intended to check the functionality by injecting the valus directly.
TEST_F(ServiceDescription_test,
       ServiceDescriptionObjectInitialisationWithOutOfBoundaryInterfaceSourceLeadsToInvalidDeserialization)
{
    ::testing::Test::RecordProperty("TEST_ID", "29fac03f-a845-4180-89b7-8367a203646e");
    ServiceDescription::ClassHash testHash = {17U, 34U, 51U, 68U};
    testService = "Service";
    testInstance = "Instance";
    testEvent = "Event";
    uint16_t testScope = 2U;
    uint16_t invalidInterfaceSource = 10U;
    auto serialObj = iox::cxx::Serialization::create(testService.c_str(),
                                                     testInstance.c_str(),
                                                     testEvent.c_str(),
                                                     testHash[0],
                                                     testHash[1],
                                                     testHash[2],
                                                     testHash[3],
                                                     static_cast<uint16_t>(testScope),
                                                     invalidInterfaceSource);

    auto deserializationResult = ServiceDescription::deserialize(serialObj);

    ASSERT_TRUE(deserializationResult.has_error());
    EXPECT_THAT(deserializationResult.get_error(), Eq(iox::cxx::Serialization::Error::DESERIALIZATION_FAILED));
}

TEST_F(ServiceDescription_test, ServiceDescriptionObjectInitialisationWithEmptyStringLeadsToInvalidDeserialization)
{
    ::testing::Test::RecordProperty("TEST_ID", "4607d73d-d27d-4694-833d-2e28162589cd");
    std::string emptyString;
    iox::cxx::Serialization invalidSerialObj{emptyString};

    auto deserializationResult = ServiceDescription::deserialize(invalidSerialObj);

    ASSERT_TRUE(deserializationResult.has_error());
    EXPECT_THAT(deserializationResult.get_error(), Eq(iox::cxx::Serialization::Error::DESERIALIZATION_FAILED));
}

TEST_F(ServiceDescription_test, ServiceDescriptionDefaultCtorInitializesStringsToEmptyString)
{
    ::testing::Test::RecordProperty("TEST_ID", "707156f8-8145-4710-b6ac-3e94dbac7237");
    ServiceDescription serviceDescription1 = ServiceDescription();

    EXPECT_THAT(serviceDescription1.getServiceIDString(), StrEq(""));
    EXPECT_THAT(serviceDescription1.getEventIDString(), StrEq(""));
    EXPECT_THAT(serviceDescription1.getInstanceIDString(), StrEq(""));
}

TEST_F(ServiceDescription_test, ServiceDescriptionDefaultCtorInitializesTheScopeToWorldWide)
{
    ::testing::Test::RecordProperty("TEST_ID", "8e6b26b1-3363-45d8-abad-3b4c1ec122af");
    ServiceDescription serviceDescription1 = ServiceDescription();

    EXPECT_THAT(serviceDescription1.getScope(), Eq(Scope::WORLDWIDE));
}

TEST_F(ServiceDescription_test, ServiceDescriptionDefaultCtorInitializesTheInterfaceToLocal)
{
    ::testing::Test::RecordProperty("TEST_ID", "87c50b2a-d771-4985-8fdd-497a5f97dc35");
    ServiceDescription serviceDescription1 = ServiceDescription();

    EXPECT_THAT(serviceDescription1.getSourceInterface(), Eq(Interfaces::INTERNAL));
}

TEST_F(ServiceDescription_test, ServiceDescriptionStringCtorCreatesServiceDescriptionWithValuesPassedToTheCtor)
{
    ::testing::Test::RecordProperty("TEST_ID", "560685b0-780c-420e-8f9d-bbfe2460d15f");
    testService = "1";
    testInstance = "2";
    testEvent = "3";
    ServiceDescription::ClassHash testHash = {12U, 23U, 34U, 45U};

    ServiceDescription serviceDescription1 = ServiceDescription(testService, testInstance, testEvent, testHash);

    EXPECT_THAT(serviceDescription1.getServiceIDString(), StrEq("1"));
    EXPECT_THAT(serviceDescription1.getInstanceIDString(), StrEq("2"));
    EXPECT_THAT(serviceDescription1.getEventIDString(), StrEq("3"));
    EXPECT_EQ(uint32_t(12), serviceDescription1.getClassHash()[0]);
    EXPECT_EQ(uint32_t(23), serviceDescription1.getClassHash()[1]);
    EXPECT_EQ(uint32_t(34), serviceDescription1.getClassHash()[2]);
    EXPECT_EQ(uint32_t(45), serviceDescription1.getClassHash()[3]);
}

TEST_F(ServiceDescription_test, TwoServiceDescriptionsWithDifferentButValidServicesAreNotEqual)
{
    ::testing::Test::RecordProperty("TEST_ID", "42329498-78b4-4cef-8629-918ca2783529");
    IdString_t testService1 = "1";
    IdString_t testEvent1 = "2";
    IdString_t testInstance1 = "3";
    IdString_t testService2 = "4";
    ServiceDescription serviceDescription1 = ServiceDescription(testService1, testEvent1, testInstance1);
    ServiceDescription serviceDescription2 = ServiceDescription(testService2, testEvent1, testInstance1);

    EXPECT_FALSE(serviceDescription1 == serviceDescription2);
}

TEST_F(ServiceDescription_test, TwoServiceDescriptionsWithDifferentButValidEventsAreNotEqual)
{
    ::testing::Test::RecordProperty("TEST_ID", "8a06cd60-af12-4bf8-abb7-ad42b301d879");
    IdString_t testService1 = "1";
    IdString_t testEvent1 = "2";
    IdString_t testInstance1 = "3";
    IdString_t testEvent2 = "4";
    ServiceDescription serviceDescription1 = ServiceDescription(testService1, testEvent1, testInstance1);
    ServiceDescription serviceDescription2 = ServiceDescription(testService1, testEvent2, testInstance1);

    EXPECT_FALSE(serviceDescription1 == serviceDescription2);
}

TEST_F(ServiceDescription_test, TwoServiceDescriptionsWithDifferentButValidInstancesAreNotEqual)
{
    ::testing::Test::RecordProperty("TEST_ID", "f1e13385-89b0-4aa0-9b97-8f39d5f5c0ae");
    IdString_t testService1 = "1";
    IdString_t testEvent1 = "2";
    IdString_t testInstance1 = "3";
    IdString_t testInstance2 = "4";
    ServiceDescription serviceDescription1 = ServiceDescription(testService1, testEvent1, testInstance1);
    ServiceDescription serviceDescription2 = ServiceDescription(testService1, testEvent1, testInstance2);

    EXPECT_FALSE(serviceDescription1 == serviceDescription2);
}

TEST_F(ServiceDescription_test, TwoServiceDescriptionsWithDifferentAndValidServiceInstanceEventsAreNotEqual)
{
    ::testing::Test::RecordProperty("TEST_ID", "b0ab9583-802b-4d9f-b114-08e44be74e44");
    IdString_t testService1 = "1";
    IdString_t testEvent1 = "2";
    IdString_t testInstance1 = "3";
    IdString_t testService2 = "4";
    IdString_t testEvent2 = "5";
    IdString_t testInstance2 = "6";
    ServiceDescription serviceDescription1 = ServiceDescription(testService1, testEvent1, testInstance1);
    ServiceDescription serviceDescription2 = ServiceDescription(testService2, testEvent2, testInstance2);

    EXPECT_FALSE(serviceDescription1 == serviceDescription2);
}

TEST_F(ServiceDescription_test, TwoServiceDescriptionsWithSameStringsComparedWithInequalityOperatorReturnsFalse)
{
    ::testing::Test::RecordProperty("TEST_ID", "1623a8a8-b892-45ce-a54a-ff13491069b7");
    IdString_t testService = "1";
    IdString_t testEvent = "2";
    IdString_t testInstance = "3";
    ServiceDescription serviceDescription1 = ServiceDescription(testService, testEvent, testInstance);
    ServiceDescription serviceDescription2 = ServiceDescription(testService, testEvent, testInstance);

    EXPECT_FALSE(serviceDescription1 != serviceDescription2);
}

TEST_F(ServiceDescription_test, ServiceMatchMethodReturnsTrueIfTheServiceStringIsSame)
{
    ::testing::Test::RecordProperty("TEST_ID", "47bb698b-bb13-4885-afab-b5a975b67715");
    IdString_t sameService = "1";
    ServiceDescription description1 = ServiceDescription(sameService, "instance1", "event1");
    ServiceDescription description2 = ServiceDescription(sameService, "instance2", "event2");

    EXPECT_TRUE(iox::capro::serviceMatch(description1, description2));
}

TEST_F(ServiceDescription_test, ServiceMatchMethodReturnsFalseIfTheServiceIDsAreDifferent)
{
    ::testing::Test::RecordProperty("TEST_ID", "9ccd5f69-aca9-4e3d-9ba7-83581abde0f3");
    IdString_t serviceID1 = "1";
    IdString_t serviceID2 = "2";
    ServiceDescription description1 = ServiceDescription(serviceID1, "instance", "event");
    ServiceDescription description2 = ServiceDescription(serviceID2, "instance", "event");

    EXPECT_FALSE(iox::capro::serviceMatch(description1, description2));
}

TEST_F(ServiceDescription_test, IsLocalMethodReturnsTrueWhenTheScopeIsSetToLocal)
{
    ::testing::Test::RecordProperty("TEST_ID", "fc611c5d-484f-43c7-899e-12085d3e6018");
    IdString_t testService = "1";
    IdString_t testEvent = "2";
    IdString_t testInstance = "3";
    ServiceDescription serviceDescription1 = ServiceDescription(testService, testEvent, testInstance);

    serviceDescription1.setLocal();

    EXPECT_TRUE(serviceDescription1.isLocal());
}

TEST_F(ServiceDescription_test, GetScopeMethodReturnsTheCorrespondingValueOfScope)
{
    ::testing::Test::RecordProperty("TEST_ID", "ddc13a6b-a2aa-4271-b479-f4d4177d048e");
    IdString_t testService = "1";
    IdString_t testEvent = "2";
    IdString_t testInstance = "3";
    ServiceDescription serviceDescription1 = ServiceDescription(testService, testEvent, testInstance);

    serviceDescription1.setLocal();

    EXPECT_EQ(serviceDescription1.getScope(), Scope::LOCAL);
}

TEST_F(ServiceDescription_test, LessThanOperatorReturnsFalseIfServiceStringOfFirstServiceDescriptionIsLessThanSecond)
{
    ::testing::Test::RecordProperty("TEST_ID", "4fe380cc-fa94-48e9-99dd-ec2e220eff16");
    ServiceDescription serviceDescription1("TestService1", "TestInstance", "TestEvent");
    ServiceDescription serviceDescription2("TestService2", "TestInstance", "TestEvent");

    EXPECT_FALSE(serviceDescription1 < serviceDescription2);
}

TEST_F(ServiceDescription_test, LessThanOperatorReturnsFalseIfInstanceStringOfFirstServiceDescriptionIsLessThanSecond)
{
    ::testing::Test::RecordProperty("TEST_ID", "d5b0053e-9e7d-4176-80e1-52f057978c42");
    ServiceDescription serviceDescription1("TestService", "TestInstance1", "TestEvent");
    ServiceDescription serviceDescription2("TestService", "TestInstance2", "TestEvent");

    EXPECT_FALSE(serviceDescription1 < serviceDescription2);
}

TEST_F(ServiceDescription_test, LessThanOperatorReturnsFalseIfEventStringOfFirstServiceDescriptionIsLessThanSecond)
{
    ::testing::Test::RecordProperty("TEST_ID", "8ab96b9a-5464-4b60-9d15-f31b5e3b4ee9");
    ServiceDescription serviceDescription1("TestService", "TestInstance", "TestEvent1");
    ServiceDescription serviceDescription2("TestService", "TestInstance", "TestEvent2");

    EXPECT_FALSE(serviceDescription1 < serviceDescription2);
}

TEST_F(ServiceDescription_test, LogStreamConvertsServiceDescriptionToString)
{
    ::testing::Test::RecordProperty("TEST_ID", "42bc3f21-d9f4-4cc3-a37e-6508e1f981c1");
    Logger_Mock loggerMock;

    const IdString_t SERVICE_ID{"all"};
    const IdString_t INSTANCE_ID{"glory"};
    const IdString_t EVENT_ID{"hypnotoad"};
    const std::string SERVICE_DESCRIPTION_AS_STRING{"Service: all, Instance: glory, Event: hypnotoad"};
    auto sut = ServiceDescription{SERVICE_ID, INSTANCE_ID, EVENT_ID};

    {
        auto logstream = iox::log::LogStream(loggerMock);
        logstream << sut;
    }

    ASSERT_THAT(loggerMock.m_logs.size(), Eq(1U));
    EXPECT_THAT(loggerMock.m_logs[0].message, StrEq(SERVICE_DESCRIPTION_AS_STRING));
}

/// END SERVICEDESCRIPTION TESTS

} // namespace
