/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "paimon/common/data/variant/generic_variant.h"

#include <functional>
#include <limits>
#include <string>
#include <vector>

#include "gtest/gtest.h"
#include "paimon/common/data/variant/variant_builder.h"
#include "paimon/common/data/variant/variant_defs.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {

class GenericVariantTest : public ::testing::Test {
 public:
    static std::string ToHex(std::string_view data) {
        constexpr char kHexDigits[] = "0123456789abcdef";
        std::string result;
        result.reserve(data.size() * 2);
        for (char c : data) {
            auto byte = static_cast<uint8_t>(c);
            result.push_back(kHexDigits[byte >> 4]);
            result.push_back(kHexDigits[byte & 0xF]);
        }
        return result;
    }

    std::shared_ptr<GenericVariant> FromJson(const std::string& json) {
        auto result = GenericVariant::FromJson(json, pool_);
        EXPECT_TRUE(result.ok()) << result.status().ToString();
        return result.value();
    }

    // Asserts that parsing `json` produces exactly the value/metadata binaries produced by the
    // Java implementation (`GenericVariantBuilder`), and that rendering back to JSON matches the
    // Java `toJson` output.
    void CheckGolden(const std::string& json, const std::string& expected_value_hex,
                     const std::string& expected_metadata_hex,
                     const std::string& expected_to_json) {
        auto variant = FromJson(json);
        ASSERT_OK_AND_ASSIGN(std::string_view value, variant->Value());
        ASSERT_EQ(ToHex(value), expected_value_hex) << "value bytes mismatch for: " << json;
        ASSERT_EQ(ToHex(variant->Metadata()), expected_metadata_hex)
            << "metadata bytes mismatch for: " << json;
        ASSERT_OK_AND_ASSIGN(std::string to_json, variant->ToJson());
        ASSERT_EQ(to_json, expected_to_json);
    }

 protected:
    std::shared_ptr<MemoryPool> pool_ = GetDefaultPool();
};

// Golden binaries generated by the Java implementation (org.apache.paimon.data.variant); these
// pin the cross-implementation byte compatibility of the variant encoding.
TEST_F(GenericVariantTest, GoldenPrimitives) {
    CheckGolden("null", "00", "010000", "null");
    CheckGolden("true", "04", "010000", "true");
    CheckGolden("false", "08", "010000", "false");
    CheckGolden("1", "0c01", "010000", "1");
    CheckGolden("-1", "0cff", "010000", "-1");
    CheckGolden("300", "102c01", "010000", "300");
    CheckGolden("100000", "14a0860100", "010000", "100000");
    CheckGolden("12345678901234", "18f22fce733a0b0000", "010000", "12345678901234");
    CheckGolden("1e40", "1ca55cc3f129633d48", "010000", "1.0E40");
    CheckGolden("1.0123456789012345678901234567890123456789", "1c240bf2619132f03f", "010000",
                "1.0123456789012346");
    CheckGolden("2.5e-3", "1c7b14ae47e17a643f", "010000", "0.0025");
    CheckGolden("100.99", "200273270000", "010000", "100.99");
    CheckGolden("-0.5", "2001fbffffff", "010000", "-0.5");
    CheckGolden("0.0", "200100000000", "010000", "0");
    CheckGolden("12345678.90123", "2405cb04fb711f010000", "010000", "12345678.90123");
    CheckGolden("1234567890123456789.0123456789", "280a1581396eb1c9be46321be42700000000", "010000",
                "1234567890123456789.0123456789");
    // An integer that overflows int64 is parsed as an exact decimal.
    CheckGolden("123456789012345678901234567890", "2800d20a3f4eeee073c3f60fe98e01000000", "010000",
                "123456789012345678901234567890");
}

TEST_F(GenericVariantTest, GoldenStrings) {
    CheckGolden("\"Hello, World!\"", "3548656c6c6f2c20576f726c6421", "010000", "\"Hello, World!\"");
    CheckGolden("\"\"", "01", "010000", "\"\"");
    CheckGolden(
        "\"This is a long string that definitely exceeds the sixty-three byte short string limit "
        "...!\"",
        "405a000000546869732069732061206c6f6e6720737472696e67207468617420646566696e6974656c792065"
        "786365656473207468652073697874792d746872656520627974652073686f727420737472696e67206c696d"
        "6974202e2e2e21",
        "010000",
        "\"This is a long string that definitely exceeds the sixty-three byte short string limit "
        "...!\"");
}

TEST_F(GenericVariantTest, GoldenContainers) {
    CheckGolden("{}", "020000", "010000", "{}");
    CheckGolden("[]", "030000", "010000", "[]");
    CheckGolden(R"({"a": 1, "b": "hello"})", "020200010002080c011568656c6c6f", "01020001026162",
                R"({"a":1,"b":"hello"})");
    CheckGolden(R"([1, "two", 3.5, null, true, {"k":[]}])",
                "03060002060c0d0e160c010d74776f20012300000000040201000003030000", "010100016b",
                R"([1,"two",3.5,null,true,{"k":[]}])");
}

TEST_F(GenericVariantTest, GoldenNested) {
    CheckGolden(
        "{\"object\":{\"name\":\"Apache Paimon\",\"age\":2,\"address\":{\"street\":\"Main "
        "St\",\"city\":\"Hangzhou\"}},\"array\":[1,2,3,4,5],\"string\":\"Hello, "
        "World!\",\"long\":12345678901234,\"double\":1."
        "0123456789012345678901234567890123456789,\"decimal\":100.99,\"boolean1\":true,"
        "\"boolean2\":false,\"nullField\":null}",
        "0209060b0c0a09080d000731696a635a516b00436c0203030201100e0028354170616368652050"
        "61696d6f6e0c02020205040800111d4d61696e2053742148616e677a686f75030500020406080a"
        "0c010c020c030c040c053548656c6c6f2c20576f726c642118f22fce733a0b00001c240bf26191"
        "32f03f200273270000040800",
        "010e00060a0d141a1e23292d333a424a536f626a6563746e616d6561676561646472657373737472656574"
        "636974796172726179737472696e676c6f6e67646f75626c65646563696d616c626f6f6c65616e31626f6f"
        "6c65616e326e756c6c4669656c64",
        "{\"array\":[1,2,3,4,5],\"boolean1\":true,\"boolean2\":false,\"decimal\":100.99,"
        "\"double\":1.0123456789012346,\"long\":12345678901234,\"nullField\":null,\"object\":{"
        "\"address\":{\"city\":\"Hangzhou\",\"street\":\"Main St\"},\"age\":2,\"name\":\"Apache "
        "Paimon\"},\"string\":\"Hello, World!\"}");
}

TEST_F(GenericVariantTest, GoldenUnicodeEscape) {
    CheckGolden(R"({"\u4e2d\u6587": "\u4f60\u597d\n\t\"quoted\""})",
                "020100001141e4bda0e5a5bd0a092271756f74656422", "01010006e4b8ade69687",
                "{\"中文\":\"你好\\n\\t\\\"quoted\\\"\"}");
}

TEST_F(GenericVariantTest, TypedAccessors) {
    auto variant = FromJson(R"({"a": 1, "b": "hello"})");
    ASSERT_OK_AND_ASSIGN(VariantValueType type, variant->GetType());
    ASSERT_EQ(type, VariantValueType::kObject);
    ASSERT_OK_AND_ASSIGN(int32_t object_size, variant->ObjectSize());
    ASSERT_EQ(object_size, 2);

    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> a, variant->GetFieldByKey("a"));
    ASSERT_NE(a, nullptr);
    ASSERT_OK_AND_ASSIGN(int64_t a_value, a->GetLong());
    ASSERT_EQ(a_value, 1);

    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> b, variant->GetFieldByKey("b"));
    ASSERT_NE(b, nullptr);
    ASSERT_OK_AND_ASSIGN(std::string_view b_value, b->GetString());
    ASSERT_EQ(b_value, "hello");

    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> missing, variant->GetFieldByKey("c"));
    ASSERT_EQ(missing, nullptr);

    ASSERT_OK_AND_ASSIGN(auto field0, variant->GetFieldAtIndex(0));
    ASSERT_TRUE(field0.has_value());
    ASSERT_EQ(field0->key, "a");
    ASSERT_OK_AND_ASSIGN(auto field_oob, variant->GetFieldAtIndex(2));
    ASSERT_FALSE(field_oob.has_value());

    auto array = FromJson("[10, 20, 30]");
    ASSERT_OK_AND_ASSIGN(int32_t array_size, array->ArraySize());
    ASSERT_EQ(array_size, 3);
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> elem, array->GetElementAtIndex(1));
    ASSERT_NE(elem, nullptr);
    ASSERT_OK_AND_ASSIGN(int64_t elem_value, elem->GetLong());
    ASSERT_EQ(elem_value, 20);
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> elem_oob, array->GetElementAtIndex(3));
    ASSERT_EQ(elem_oob, nullptr);
}

TEST_F(GenericVariantTest, ObjectBinarySearch) {
    // More fields than kBinarySearchThreshold exercises the binary-search lookup.
    std::string json = "{";
    for (int32_t i = 0; i < 40; ++i) {
        if (i != 0) {
            json += ",";
        }
        json += "\"key" + std::to_string(i) + "\":" + std::to_string(i);
    }
    json += "}";
    auto variant = FromJson(json);
    for (int32_t i = 0; i < 40; ++i) {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> field,
                             variant->GetFieldByKey("key" + std::to_string(i)));
        ASSERT_NE(field, nullptr);
        ASSERT_OK_AND_ASSIGN(int64_t value, field->GetLong());
        ASSERT_EQ(value, i);
    }
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> missing, variant->GetFieldByKey("key40"));
    ASSERT_EQ(missing, nullptr);
}

TEST_F(GenericVariantTest, DuplicateKeys) {
    ASSERT_NOK(GenericVariant::FromJson("{\"a\": 1, \"a\": 2}", pool_));
    ASSERT_OK_AND_ASSIGN(
        std::shared_ptr<GenericVariant> variant,
        VariantBuilder::ParseJson("{\"a\": 1, \"a\": 2}", /*allow_duplicate_keys=*/true, pool_));
    ASSERT_OK_AND_ASSIGN(std::string to_json, variant->ToJson());
    ASSERT_EQ(to_json, "{\"a\":2}");
}

TEST_F(GenericVariantTest, CorruptedLayoutRejected) {
    // Headers claiming a near-INT32_MAX element count must be rejected by the 64-bit layout
    // bound instead of overflowing the 32-bit offset arithmetic.
    std::string metadata;
    metadata.push_back(static_cast<char>(0x01));
    metadata.push_back(static_cast<char>(0x00));
    {
        // Object, large size, 4-byte ids and offsets, num_elements = INT32_MAX.
        std::string value;
        value.push_back(static_cast<char>((0x1F << 2) | 0x02));
        value.append({static_cast<char>(0xFF), static_cast<char>(0xFF), static_cast<char>(0xFF),
                      static_cast<char>(0x7F)});
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> variant,
                             GenericVariant::Create(value, metadata, pool_));
        ASSERT_NOK(variant->ToJson());
    }
    {
        // Array, large size, 4-byte offsets, num_elements = INT32_MAX.
        std::string value;
        value.push_back(static_cast<char>((0x07 << 2) | 0x03));
        value.append({static_cast<char>(0xFF), static_cast<char>(0xFF), static_cast<char>(0xFF),
                      static_cast<char>(0x7F)});
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> variant,
                             GenericVariant::Create(value, metadata, pool_));
        ASSERT_NOK(variant->ToJson());
    }
}

TEST_F(GenericVariantTest, OverLimitInputRejectedBeforeAllocation) {
    // The size check must run before the buffers are allocated/copied; the fake-length view is
    // never dereferenced.
    char byte = 0;
    std::string_view huge(&byte, static_cast<size_t>(VariantDefs::kSizeLimit) + 1);
    ASSERT_NOK(GenericVariant::Create(huge, std::string_view(&byte, 1), pool_));
    ASSERT_NOK(GenericVariant::Create(std::string_view(&byte, 1), huge, pool_));
}

TEST_F(GenericVariantTest, MalformedInput) {
    ASSERT_NOK(GenericVariant::FromJson("", pool_));
    ASSERT_NOK(GenericVariant::FromJson("{", pool_));
    ASSERT_NOK(GenericVariant::FromJson("{\"a\":}", pool_));
    ASSERT_NOK(GenericVariant::FromJson("NaN", pool_));

    // Unsupported metadata version.
    std::string bad_metadata = std::string("\x02\x00\x00", 3);
    std::string value = std::string("\x00", 1);
    ASSERT_NOK(GenericVariant::Create(value, bad_metadata, pool_));
    // Empty metadata.
    ASSERT_NOK(GenericVariant::Create(value, std::string(), pool_));
}

TEST_F(GenericVariantTest, SizeInBytesAndViews) {
    auto variant = FromJson(R"({"a": 1, "b": "hello"})");
    ASSERT_EQ(variant->SizeInBytes(),
              static_cast<int64_t>(variant->RawValue().size() + variant->Metadata().size()));
    // A sub-variant shares buffers with its parent and reports a positive position.
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> b, variant->GetFieldByKey("b"));
    ASSERT_GT(b->Pos(), 0);
    ASSERT_OK_AND_ASSIGN(std::string_view b_slice, b->Value());
    ASSERT_EQ(ToHex(b_slice), "1568656c6c6f");
}

TEST_F(GenericVariantTest, AppendVariantRebuild) {
    // Rebuilding a sub-variant through a fresh builder produces a self-contained variant.
    auto variant = FromJson(R"({"outer": {"x": [1, 2], "y": "z"}})");
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> outer, variant->GetFieldByKey("outer"));
    VariantBuilder builder(/*allow_duplicate_keys=*/false);
    ASSERT_OK(builder.AppendVariant(*outer));
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> rebuilt, builder.Build(pool_));
    ASSERT_EQ(rebuilt->Pos(), 0);
    ASSERT_OK_AND_ASSIGN(std::string to_json, rebuilt->ToJson());
    ASSERT_EQ(to_json, "{\"x\":[1,2],\"y\":\"z\"}");
}

TEST_F(GenericVariantTest, TimestampAndSpecialTypesToJson) {
    // JSON can't produce date/timestamp/binary/uuid variants; build them directly.
    {
        VariantBuilder builder(false);
        ASSERT_OK(builder.AppendDate(19737));  // 2024-01-15
        ASSERT_OK_AND_ASSIGN(auto v, builder.Build(pool_));
        ASSERT_OK_AND_ASSIGN(std::string json, v->ToJson());
        ASSERT_EQ(json, "\"2024-01-15\"");
    }
    {
        VariantBuilder builder(false);
        ASSERT_OK(builder.AppendTimestamp(1705312496123456LL));
        ASSERT_OK_AND_ASSIGN(auto v, builder.Build(pool_));
        ASSERT_OK_AND_ASSIGN(std::string json, v->ToJson());
        ASSERT_EQ(json, "\"2024-01-15 09:54:56.123456+00:00\"");
        ASSERT_OK_AND_ASSIGN(std::string json_shanghai, v->ToJson("Asia/Shanghai"));
        ASSERT_EQ(json_shanghai, "\"2024-01-15 17:54:56.123456+08:00\"");
        ASSERT_OK_AND_ASSIGN(std::string json_offset, v->ToJson("+08:00"));
        ASSERT_EQ(json_offset, "\"2024-01-15 17:54:56.123456+08:00\"");
        ASSERT_NOK(v->ToJson("Not/AZone"));
    }
    {
        VariantBuilder builder(false);
        ASSERT_OK(builder.AppendTimestampNtz(1705312496000000LL));
        ASSERT_OK_AND_ASSIGN(auto v, builder.Build(pool_));
        ASSERT_OK_AND_ASSIGN(std::string json, v->ToJson());
        ASSERT_EQ(json, "\"2024-01-15 09:54:56\"");
    }
    {
        VariantBuilder builder(false);
        ASSERT_OK(builder.AppendBinary(std::string_view("\x01\x02\x03", 3)));
        ASSERT_OK_AND_ASSIGN(auto v, builder.Build(pool_));
        ASSERT_OK_AND_ASSIGN(std::string json, v->ToJson());
        ASSERT_EQ(json, "\"AQID\"");
    }
    {
        VariantBuilder builder(false);
        std::string uuid_bytes =
            std::string("\x12\x3e\x45\x67\xe8\x9b\x12\xd3\xa4\x56\x42\x66\x14\x17\x40\x00", 16);
        ASSERT_OK(builder.AppendUuid(uuid_bytes));
        ASSERT_OK_AND_ASSIGN(auto v, builder.Build(pool_));
        ASSERT_OK_AND_ASSIGN(std::string json, v->ToJson());
        ASSERT_EQ(json, "\"123e4567-e89b-12d3-a456-426614174000\"");
    }
    {
        VariantBuilder builder(false);
        ASSERT_OK(builder.AppendFloat(1.5f));
        ASSERT_OK_AND_ASSIGN(auto v, builder.Build(pool_));
        ASSERT_OK_AND_ASSIGN(std::string json, v->ToJson());
        ASSERT_EQ(json, "1.5");
    }
}

TEST_F(GenericVariantTest, NonFiniteDoubleToJson) {
    VariantBuilder builder(false);
    ASSERT_OK(builder.AppendDouble(std::numeric_limits<double>::infinity()));
    ASSERT_OK_AND_ASSIGN(auto v, builder.Build(pool_));
    ASSERT_OK_AND_ASSIGN(std::string json, v->ToJson());
    ASSERT_EQ(json, "\"Infinity\"");
}

TEST_F(GenericVariantTest, GetTypeInfoReturnsHeaderBits) {
    // GetTypeInfo exposes the primitive header's type-info bits; 42 is encoded as an int1.
    auto v = FromJson("42");
    ASSERT_OK_AND_ASSIGN(int32_t type_info, v->GetTypeInfo());
    EXPECT_EQ(type_info, VariantDefs::kInt1);
}

TEST_F(GenericVariantTest, TypedGettersRejectMismatchedTypes) {
    // Each accessor validates the value header and fails when the stored type differs.
    auto number = FromJson("42");    // primitive int1
    auto text = FromJson("\"hi\"");  // short string
    auto real = FromJson("1.5e0");   // double

    // A primitive long is neither boolean/double/decimal/float/binary/string/uuid, nor a
    // container.
    ASSERT_NOK(number->GetBoolean());
    ASSERT_NOK(number->GetDouble());
    ASSERT_NOK(number->GetDecimal());
    ASSERT_NOK(number->GetFloat());
    ASSERT_NOK(number->GetBinary());
    ASSERT_NOK(number->GetString());
    ASSERT_NOK(number->GetUuid());
    ASSERT_NOK(number->ObjectSize());
    ASSERT_NOK(number->ArraySize());
    // A short string is not a primitive, so long/decimal decoding rejects it early.
    ASSERT_NOK(text->GetLong());
    ASSERT_NOK(text->GetDecimal());
    // A double is a primitive but not an integer-like type.
    ASSERT_NOK(real->GetLong());
}

TEST_F(GenericVariantTest, ValueSizeCoversAllPrimitiveWidths) {
    // Builds an array whose elements span the primitive width branches of `ValueSize` (int4,
    // decimal8, binary, uuid). Copying elements into the array and re-reading each element's
    // value both exercise `ValueSize`.
    auto build = [this](const std::function<Status(VariantBuilder&)>& append) {
        VariantBuilder builder(/*allow_duplicate_keys=*/false);
        EXPECT_OK(append(builder));
        auto result = builder.Build(pool_);
        EXPECT_TRUE(result.ok()) << result.status().ToString();
        return result.value();
    };
    std::shared_ptr<GenericVariant> int4 =
        build([](VariantBuilder& b) { return b.AppendLong(100000); });  // needs 4 bytes
    std::shared_ptr<GenericVariant> decimal8 =
        build([](VariantBuilder& b) { return b.AppendDecimal(VariantDecimal{1234567890, 2}); });
    std::shared_ptr<GenericVariant> binary =
        build([](VariantBuilder& b) { return b.AppendBinary(std::string_view("abc", 3)); });
    std::string uuid_bytes(16, '\x07');
    std::shared_ptr<GenericVariant> uuid =
        build([&](VariantBuilder& b) { return b.AppendUuid(uuid_bytes); });

    VariantBuilder array_builder(/*allow_duplicate_keys=*/false);
    int32_t start = array_builder.GetWritePos();
    std::vector<int32_t> offsets;
    for (const std::shared_ptr<GenericVariant>* element : {&int4, &decimal8, &binary, &uuid}) {
        offsets.push_back(array_builder.GetWritePos() - start);
        ASSERT_OK(array_builder.AppendVariant(**element));
    }
    ASSERT_OK(array_builder.FinishWritingArray(start, offsets));
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> array_variant, array_builder.Build(pool_));

    ASSERT_OK_AND_ASSIGN(int32_t size, array_variant->ArraySize());
    ASSERT_EQ(size, 4);
    for (int32_t i = 0; i < size; ++i) {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<GenericVariant> element,
                             array_variant->GetElementAtIndex(i));
        ASSERT_OK(element->Value());
    }
}

}  // namespace paimon::test
