/*
 * Copyright 2025 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_ocimanifest.h>
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>

using namespace buildboxcommon;

namespace {
const size_t kLayer1Size = 5678;
const size_t kLayer2Size = 91011;
const size_t kManifestIndexEntry1Size = 1234;
const size_t kManifestIndexEntry2Size = 5678;
} // namespace

TEST(OciManifestLayerTest, SerializeDeserialize)
{
    OciManifestLayer layer{"application/vnd.oci.image.layer.v1.tar+gzip",
                           "sha256:def456", kLayer1Size};
    auto json = layer.toJson();
    auto parsed = OciManifestLayer::fromJson(json);
    EXPECT_EQ(parsed.d_mediaType, layer.d_mediaType);
    EXPECT_EQ(parsed.d_digest, layer.d_digest);
    ASSERT_TRUE(parsed.d_size.has_value());
    if (parsed.d_size.has_value()) {
        EXPECT_EQ(parsed.d_size.value(), layer.d_size.value());
    }
}

TEST(OciManifestLayerTest, MissingFieldsThrows)
{
    nlohmann::json j = {{"digest", "sha256:def456"}};
    EXPECT_THROW(OciManifestLayer::fromJson(j), std::invalid_argument);
}

TEST(OciManifestTest, SerializeDeserialize)
{
    OciManifest manifest;
    manifest.d_schemaVersion = 2;
    manifest.d_mediaType =
        "application/vnd.docker.distribution.manifest.v2+json";
    manifest.d_layers = {{"application/vnd.oci.image.layer.v1.tar+gzip",
                          "sha256:def456", kLayer1Size},
                         {"application/vnd.oci.image.layer.v1.tar+gzip",
                          "sha256:789abc", kLayer2Size}};
    auto json = manifest.toJson();
    auto parsed = OciManifest::fromJson(json);
    EXPECT_EQ(parsed.d_schemaVersion, manifest.d_schemaVersion);
    EXPECT_EQ(parsed.d_mediaType, manifest.d_mediaType);
    EXPECT_EQ(parsed.d_layers.size(), manifest.d_layers.size());
    EXPECT_EQ(parsed.d_layers[1].d_digest, manifest.d_layers[1].d_digest);
}

TEST(OciManifestTest, InvalidJsonThrows)
{
    nlohmann::json j = {{"schemaVersion", 2}};
    EXPECT_THROW(OciManifest::fromJson(j), std::invalid_argument);
}

TEST(OciManifestTest, MissingFieldsThrows)
{
    nlohmann::json j = {
        {"schemaVersion", 2},
        {"mediaType", "application/vnd.docker.distribution.manifest.v2+json"}};
    EXPECT_THROW(OciManifest::fromJson(j), std::invalid_argument);
}

TEST(OciPlatformTest, SerializeDeserialize)
{
    OciPlatform platform{"amd64", "linux"};
    auto json = platform.toJson();
    auto parsed = OciPlatform::fromJson(json);
    EXPECT_EQ(parsed.d_architecture, platform.d_architecture);
    EXPECT_EQ(parsed.d_os, platform.d_os);
}

TEST(OciPlatformTest, MissingFieldsThrows)
{
    nlohmann::json j = {{"architecture", "amd64"}};
    EXPECT_THROW(OciPlatform::fromJson(j), std::invalid_argument);

    nlohmann::json j2 = {{"os", "linux"}};
    EXPECT_THROW(OciPlatform::fromJson(j2), std::invalid_argument);
}

TEST(OciManifestIndexEntryTest, SerializeDeserialize)
{
    OciPlatform platform{"amd64", "linux"};
    OciManifestIndexEntry entry{"application/vnd.oci.image.manifest.v1+json",
                                "sha256:abc123", kManifestIndexEntry1Size,
                                platform};
    auto json = entry.toJson();
    auto parsed = OciManifestIndexEntry::fromJson(json);
    EXPECT_EQ(parsed.d_mediaType, entry.d_mediaType);
    EXPECT_EQ(parsed.d_digest, entry.d_digest);
    ASSERT_TRUE(parsed.d_size.has_value());
    if (parsed.d_size.has_value()) {
        EXPECT_EQ(parsed.d_size.value(), entry.d_size.value());
    }
    EXPECT_EQ(parsed.d_platform.d_architecture,
              entry.d_platform.d_architecture);
    EXPECT_EQ(parsed.d_platform.d_os, entry.d_platform.d_os);
}

TEST(OciManifestIndexEntryTest, MissingFieldsThrows)
{
    nlohmann::json j = {{"digest", "sha256:abc123"}};
    EXPECT_THROW(OciManifestIndexEntry::fromJson(j), std::invalid_argument);
}

TEST(OciManifestIndexTest, SerializeDeserialize)
{
    OciManifestIndex index;
    index.d_schemaVersion = 2;
    index.d_mediaType = "application/vnd.oci.image.index.v1+json";

    OciPlatform platform1{"amd64", "linux"};
    OciManifestIndexEntry entry1{"application/vnd.oci.image.manifest.v1+json",
                                 "sha256:abc123", kManifestIndexEntry1Size,
                                 platform1};

    OciPlatform platform2{"arm64", "linux"};
    OciManifestIndexEntry entry2{"application/vnd.oci.image.manifest.v1+json",
                                 "sha256:def456", kManifestIndexEntry2Size,
                                 platform2};

    index.d_manifests = {entry1, entry2};

    auto json = index.toJson();
    auto parsed = OciManifestIndex::fromJson(json);

    EXPECT_EQ(parsed.d_schemaVersion, index.d_schemaVersion);
    EXPECT_EQ(parsed.d_mediaType, index.d_mediaType);
    EXPECT_EQ(parsed.d_manifests.size(), 2);

    EXPECT_EQ(parsed.d_manifests[0].d_digest, "sha256:abc123");
    EXPECT_EQ(parsed.d_manifests[0].d_platform.d_architecture, "amd64");
    EXPECT_EQ(parsed.d_manifests[0].d_platform.d_os, "linux");

    EXPECT_EQ(parsed.d_manifests[1].d_digest, "sha256:def456");
    EXPECT_EQ(parsed.d_manifests[1].d_platform.d_architecture, "arm64");
    EXPECT_EQ(parsed.d_manifests[1].d_platform.d_os, "linux");
}

TEST(OciManifestIndexTest, MissingFieldsThrows)
{
    nlohmann::json j = {{"schemaVersion", 2}};
    EXPECT_THROW(OciManifestIndex::fromJson(j), std::invalid_argument);
}
