/*
 * Copyright 2022 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 <buildboxcasd_casinstance.h>

#include <buildboxcommon_protos.h>

#include <gtest/gtest.h>

using namespace buildboxcasd;

std::vector<std::string> INSTANCE_NAMES = {"", "testinstance",
                                           "test/instance"};

// Set some example resource name parts for use in tests
const std::string DIGEST =
    "58c1707c8bcae4a77a62414536cf3895436345b7a20f1ed7ab2c1f95e62d4200";
const int SIZE_BYTES = 28867;
const std::string UPLOAD_UUID = "3efa1059-4094-4396-b71d-cf05849d08f6";

class TestCasInstance : public CasInstance {
  public:
    static Digest wrapUploadResourceName(const std::string &resource_name,
                                         const std::string &instance_name)
    {
        if (!instance_name.empty()) {
            return digestFromUploadResourceName(
                instance_name + "/" + resource_name, instance_name);
        }
        return digestFromUploadResourceName(resource_name, instance_name);
    }
    static Digest wrapDownloadResourceName(const std::string &resource_name,
                                           const std::string &instance_name)
    {
        if (!instance_name.empty()) {
            return digestFromDownloadResourceName(
                instance_name + "/" + resource_name, instance_name);
        }
        return digestFromDownloadResourceName(resource_name, instance_name);
    }
};

// digestFromDownloadResourceName tests //
TEST(DownloadResourceName, EmptyResourceName)
{
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapDownloadResourceName("", instance),
                     std::invalid_argument);
    }
}

TEST(DownloadResourceName, ResourceNameMissingSize)
{
    std::stringstream resource_ss;
    resource_ss << "blobs/" << DIGEST;
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapDownloadResourceName(
                         resource_ss.str(), instance),
                     std::invalid_argument);
    }
}

TEST(DownloadResourceName, ResourceNameSizeNotANumber)
{
    std::stringstream resource_ss;
    resource_ss << "blobs/" << DIGEST << "/"
                << "five";
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapDownloadResourceName(
                         resource_ss.str(), instance),
                     std::invalid_argument);
    }
}

TEST(DownloadResourceName, ResourceNameNegativeSize)
{
    std::stringstream resource_ss;
    resource_ss << "blobs/" << DIGEST << "/"
                << "-500";
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapDownloadResourceName(
                         resource_ss.str(), instance),
                     std::invalid_argument);
    }
}

// Verify that using an upload resource name for a download fails
TEST(DownloadResourceName, UsingUploadResourceNameFails)
{
    std::stringstream resource_ss;
    resource_ss << "uploads/" << UPLOAD_UUID << "/blobs/" << DIGEST << "/"
                << SIZE_BYTES;
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapDownloadResourceName(
                         resource_ss.str(), instance),
                     std::invalid_argument);
    }
}

TEST(DownloadResourceName, ValidResourceName)
{
    std::stringstream resource_ss;
    resource_ss << "blobs/" << DIGEST << "/" << SIZE_BYTES;
    for (const std::string &instance : INSTANCE_NAMES) {
        Digest testDigest = TestCasInstance::wrapDownloadResourceName(
            resource_ss.str(), instance);
        ASSERT_EQ(testDigest.hash(), DIGEST);
        ASSERT_EQ(testDigest.size_bytes(), SIZE_BYTES);
    }
}

TEST(DownloadResourceName, ValidResourceNameWithTrailingMetadata)
{
    std::stringstream resource_ss;
    resource_ss << "blobs/" << DIGEST << "/" << SIZE_BYTES << "/othermetadata";
    for (const std::string &instance : INSTANCE_NAMES) {
        Digest testDigest = TestCasInstance::wrapDownloadResourceName(
            resource_ss.str(), instance);
        ASSERT_EQ(testDigest.hash(), DIGEST);
        ASSERT_EQ(testDigest.size_bytes(), SIZE_BYTES);
    }
}

// digestFromUploadResourceName tests //
TEST(UploadResourceName, EmptyResourceName)
{
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapUploadResourceName("", instance),
                     std::invalid_argument);
    }
}

TEST(UploadResourceName, ResourceNameMissingSize)
{
    std::stringstream resource_ss;
    resource_ss << "uploads/" << UPLOAD_UUID << "/blobs/" << DIGEST;
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapUploadResourceName(resource_ss.str(),
                                                             instance),
                     std::invalid_argument);
    }
}

TEST(UploadResourceName, ResourceNameSizeNotANumber)
{
    std::stringstream resource_ss;
    resource_ss << "uploads/" << UPLOAD_UUID << "/blobs/" << DIGEST << "/"
                << "five";
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapUploadResourceName(resource_ss.str(),
                                                             instance),
                     std::invalid_argument);
    }
}

TEST(UploadResourceName, ResourceNameNegativeSize)
{
    std::stringstream resource_ss;
    resource_ss << "uploads/" << UPLOAD_UUID << "/blobs/" << DIGEST << "/"
                << "-500";
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapUploadResourceName(resource_ss.str(),
                                                             instance),
                     std::invalid_argument);
    }
}

// Verify that using a download resource name for an upload fails
TEST(UploadResourceName, DownloadResourceNameFails)
{
    std::stringstream resource_ss;
    resource_ss << "blobs/" << DIGEST << "/" << SIZE_BYTES;
    for (const std::string &instance : INSTANCE_NAMES) {
        EXPECT_THROW(TestCasInstance::wrapUploadResourceName(resource_ss.str(),
                                                             instance),
                     std::invalid_argument);
    }
}

TEST(UploadResourceName, ValidResourceName)
{
    std::stringstream resource_ss;
    resource_ss << "uploads/" << UPLOAD_UUID << "/blobs/" << DIGEST << "/"
                << SIZE_BYTES;
    for (const std::string &instance : INSTANCE_NAMES) {
        Digest testDigest = TestCasInstance::wrapUploadResourceName(
            resource_ss.str(), instance);
        ASSERT_EQ(testDigest.hash(), DIGEST);
        ASSERT_EQ(testDigest.size_bytes(), SIZE_BYTES);
    }
}

TEST(UploadResourceName, ValidResourceNameWithTrailingMetadata)
{
    std::stringstream resource_ss;
    resource_ss << "uploads/" << UPLOAD_UUID << "/blobs/" << DIGEST << "/"
                << SIZE_BYTES << "/othermetadata";
    for (const std::string &instance : INSTANCE_NAMES) {
        Digest testDigest = TestCasInstance::wrapUploadResourceName(
            resource_ss.str(), instance);
        ASSERT_EQ(testDigest.hash(), DIGEST);
        ASSERT_EQ(testDigest.size_bytes(), SIZE_BYTES);
    }
}
