/*
 * Copyright 2019 Bloomberg Finance LP
 *
 * 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.
 */

#include <buildboxcommon_protos.h>
#include <buildboxcommon_requestmetadata.h>

#include <google/protobuf/util/message_differencer.h>
#include <gtest/gtest.h>

using namespace buildboxcommon;

const std::string tool_name = "testing-tool";
const std::string tool_version = "v1.2.3";
const std::string request_metadata_header_name =
    "build.bazel.remote.execution.v2.requestmetadata-bin";

class EmptyRequestMetadataFixture : public RequestMetadataGenerator,
                                    public ::testing::Test {
  protected:
    EmptyRequestMetadataFixture() : RequestMetadataGenerator() {}
};

TEST_F(EmptyRequestMetadataFixture, TestDefaultConstructor)
{
    ASSERT_TRUE(this->getToolDetails().tool_name().empty());
    ASSERT_TRUE(this->getToolDetails().tool_version().empty());
    ASSERT_TRUE(this->getActionId().empty());
    ASSERT_TRUE(this->getToolInvocationId().empty());
    ASSERT_TRUE(this->getCorrelatedInvocationsId().empty());
}

class RequestMetadataFixture : public RequestMetadataGenerator,
                               public ::testing::Test {
  protected:
    RequestMetadataFixture()
        : RequestMetadataGenerator(tool_name, tool_version)
    {
    }
};

TEST_F(RequestMetadataFixture, TestToolDetailsConstructor)
{
    ASSERT_EQ(this->getToolDetails().tool_name(), tool_name);
    ASSERT_EQ(this->getToolDetails().tool_version(), tool_version);

    ASSERT_TRUE(this->getActionId().empty());
    ASSERT_TRUE(this->getToolInvocationId().empty());
    ASSERT_TRUE(this->getCorrelatedInvocationsId().empty());
}

TEST_F(RequestMetadataFixture, RequestMetadataKey)
{
    ASSERT_EQ(this->HEADER_NAME,
              "build.bazel.remote.execution.v2.requestmetadata-bin");
}

TEST_F(RequestMetadataFixture, TestSetters)
{
    this->set_tool_details("new testing tool", "0.1");
    this->set_action_id("action1");
    this->set_tool_invocation_id("invocation2");
    this->set_correlated_invocations_id("correlation3");
    this->set_action_mnemonic("test1");
    this->set_target_id("target1");
    this->set_configuration_id("configuration1");

    ASSERT_EQ(this->getToolDetails().tool_name(), "new testing tool");
    ASSERT_EQ(this->getToolDetails().tool_version(), "0.1");
    ASSERT_EQ(this->getActionId(), "action1");
    ASSERT_EQ(this->getToolInvocationId(), "invocation2");
    ASSERT_EQ(this->getCorrelatedInvocationsId(), "correlation3");
    ASSERT_EQ(this->getActionMnemonic(), "test1");
    ASSERT_EQ(this->getTargetId(), "target1");
    ASSERT_EQ(this->getConfigurationId(), "configuration1");
}

TEST_F(RequestMetadataFixture, TestGeneratedMetadata)
{
    const std::string action_id = "action-alpha";
    const std::string tool_invocation_id = "invocation-india";
    const std::string correlated_invocations_id = "correlated-charlie";
    const std::string actionMnemonic = "test";
    const std::string targetId = "target-tango";
    const std::string configurationId = "configuration-charlie";

    const RequestMetadata metadata = this->generate_request_metadata(
        action_id, tool_invocation_id, correlated_invocations_id,
        actionMnemonic, targetId, configurationId);

    ASSERT_EQ(metadata.tool_details().tool_name(), tool_name);
    ASSERT_EQ(metadata.tool_details().tool_version(), tool_version);

    ASSERT_EQ(metadata.action_id(), action_id);
    ASSERT_EQ(metadata.tool_invocation_id(), tool_invocation_id);
    ASSERT_EQ(metadata.correlated_invocations_id(), correlated_invocations_id);
    ASSERT_EQ(metadata.action_mnemonic(), actionMnemonic);
    ASSERT_EQ(metadata.target_id(), targetId);
    ASSERT_EQ(metadata.configuration_id(), configurationId);
}

TEST(ParseRequestMetadata, TestParseRequestMetadata)
{
    RequestMetadata testMetadata;
    const std::string test_action_id = "test-action";
    const std::string test_invocations_id = "test-correlated-invocations-id";
    testMetadata.set_action_id(test_action_id);
    testMetadata.set_correlated_invocations_id(test_invocations_id);

    std::multimap<grpc::string_ref, grpc::string_ref> testMap;
    std::string testMetadataString = testMetadata.SerializeAsString();
    testMap.emplace(request_metadata_header_name, testMetadataString);

    const RequestMetadata newRequestMetadata =
        RequestMetadataGenerator::parse_request_metadata(testMap);
    ASSERT_EQ(newRequestMetadata.action_id(), test_action_id);
    ASSERT_EQ(newRequestMetadata.correlated_invocations_id(),
              test_invocations_id);
}

// No metadata passed should result in an empty RequestMetadata proto
TEST(ParseRequestMetadata, TestParseRequestMetadataNoMetadata)
{
    std::multimap<grpc::string_ref, grpc::string_ref> testMap;
    testMap.emplace("Some-other-key", "some-other-value");
    testMap.emplace("testkey", "testvalue");

    const RequestMetadata emptyRequestMetadata;
    const RequestMetadata newRequestMetadata =
        RequestMetadataGenerator::parse_request_metadata(testMap);
    ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
        emptyRequestMetadata, newRequestMetadata));
}

TEST(ParseRequestMetadata, TestParseRequestMetadataInvalidMetadata)
{

    std::multimap<grpc::string_ref, grpc::string_ref> testMap;
    testMap.emplace(request_metadata_header_name, "InvalidRequestMetadata");
    ASSERT_THROW(RequestMetadataGenerator::parse_request_metadata(testMap),
                 std::invalid_argument);
}