/*
 * 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/binary_array.h"

#include <cstdlib>
#include <optional>
#include <utility>

#include "arrow/api.h"
#include "arrow/array/array_nested.h"
#include "arrow/ipc/json_simple.h"
#include "arrow/util/checked_cast.h"
#include "gtest/gtest.h"
#include "paimon/common/data/binary_array_writer.h"
#include "paimon/common/data/binary_map.h"
#include "paimon/common/data/columnar/columnar_array.h"
#include "paimon/common/utils/date_time_utils.h"
#include "paimon/testing/utils/binary_row_generator.h"
#include "paimon/testing/utils/testharness.h"
namespace paimon::test {

TEST(BinaryArrayTest, TestBinaryArraySimple) {
    auto pool = GetDefaultPool();
    int64_t seed = DateTimeUtils::GetCurrentUTCTimeUs();
    std::srand(seed);
    std::vector<int64_t> values(100, 0);
    for (auto& value : values) {
        value = paimon::test::RandomNumber(0, 10000000);
    }
    auto binary_array = BinaryArray::FromLongArray(values, pool.get());
    ASSERT_EQ(values.size(), binary_array.Size()) << "seed:" << seed;
    for (size_t i = 0; i < values.size(); i++) {
        ASSERT_EQ(values[i], binary_array.GetLong(i)) << "seed:" << seed << ", idx: " << i;
    }

    ASSERT_FALSE(binary_array.IsNullAt(40)) << "seed:" << seed;
    ASSERT_FALSE(binary_array.IsNullAt(70)) << "seed:" << seed;
    auto res = binary_array.ToLongArray();
    ASSERT_EQ(res.value(), values);
}

TEST(BinaryArrayTest, TestSetAndGet) {
    auto pool = GetDefaultPool();
    {
        std::vector<bool> arr = {true, false};
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), sizeof(bool), pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteBoolean(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(true, array.GetBoolean(0));
        ASSERT_EQ(false, array.GetBoolean(1));
        ASSERT_OK_AND_ASSIGN(auto res, array.ToBooleanArray());
        std::vector<char> char_arr = {1, 0};
        ASSERT_EQ(res, char_arr);
    }
    {
        std::vector<int8_t> arr = {1, 2, 3, 4, 5};
        BinaryArray array;
        BinaryArrayWriter writer =
            BinaryArrayWriter(&array, arr.size(), sizeof(int8_t), pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteByte(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(1, array.GetByte(0));
        ASSERT_EQ(5, array.GetByte(4));
        ASSERT_OK_AND_ASSIGN(auto res, array.ToByteArray());
        std::vector<char> char_arr = {1, 2, 3, 4, 5};
        ASSERT_EQ(res, char_arr);
    }
    {
        std::vector<int16_t> arr = {1, 2, 3, 4, 5};
        BinaryArray array;
        BinaryArrayWriter writer =
            BinaryArrayWriter(&array, arr.size(), sizeof(int16_t), pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteShort(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(1, array.GetShort(0));
        ASSERT_EQ(5, array.GetShort(4));
        ASSERT_OK_AND_ASSIGN(auto res, array.ToShortArray());
        ASSERT_EQ(res, arr);
    }
    {
        std::vector<int32_t> arr = {1, 2, 3, 4, 5};
        BinaryArray array;
        BinaryArrayWriter writer =
            BinaryArrayWriter(&array, arr.size(), sizeof(int32_t), pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteInt(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(1, array.GetInt(0));
        ASSERT_EQ(5, array.GetInt(4));
        ASSERT_OK_AND_ASSIGN(auto res, array.ToIntArray());
        ASSERT_EQ(res, arr);
    }
    {
        // test date
        std::vector<int32_t> arr = {10000, 20006};
        BinaryArray array;
        BinaryArrayWriter writer =
            BinaryArrayWriter(&array, arr.size(), sizeof(int32_t), pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteInt(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(10000, array.GetDate(0));
        ASSERT_EQ(20006, array.GetDate(1));
        ASSERT_OK_AND_ASSIGN(auto res, array.ToIntArray());
        ASSERT_EQ(res, arr);
    }
    {
        std::vector<float> arr = {1.0, 2.0};
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), sizeof(float), pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteFloat(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(1.0, array.GetFloat(0));
        ASSERT_EQ(2.0, array.GetFloat(1));
        ASSERT_OK_AND_ASSIGN(auto res, array.ToFloatArray());
        ASSERT_EQ(res, arr);
    }
    {
        std::vector<double> arr = {1.0, 2.0};
        BinaryArray array;
        BinaryArrayWriter writer =
            BinaryArrayWriter(&array, arr.size(), sizeof(double), pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteDouble(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(2, writer.GetNumElements());
        ASSERT_EQ(1.0, array.GetDouble(0));
        ASSERT_EQ(2.0, array.GetDouble(1));
        ASSERT_OK_AND_ASSIGN(auto res, array.ToDoubleArray());
        ASSERT_EQ(res, arr);
    }
    // decimal
    {
        // not compact (precision <= 18)
        std::vector<Decimal> arr = {Decimal(6, 2, 123456), Decimal(6, 3, 123456)};
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), 8, pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteDecimal(i, arr[i], 6);
        }
        writer.Complete();
        ASSERT_EQ(arr[0], array.GetDecimal(0, 6, 2));
        ASSERT_EQ(arr[1], array.GetDecimal(1, 6, 3));
    }
    {
        // compact (precision > 18)
        std::vector<Decimal> arr = {Decimal(/*precision=*/20, /*scale=*/3, 123456),
                                    Decimal(/*precision=*/20, /*scale=*/3, 123456789)};
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), 8, pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteDecimal(i, arr[i], /*precision=*/20);
        }
        writer.Complete();
        ASSERT_EQ(arr[0], array.GetDecimal(0, /*precision=*/20, /*scale=*/3));
        ASSERT_EQ(arr[1], array.GetDecimal(1, /*precision=*/20, /*scale=*/3));
    }
    // timestamp
    {
        // not compact (precision > 3)
        std::vector<Timestamp> arr = {Timestamp(0, 0), Timestamp(12345, 1)};
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), 8, pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteTimestamp(i, arr[i], 9);
        }
        writer.Complete();
        ASSERT_EQ(arr[0], array.GetTimestamp(0, 9));
        ASSERT_EQ(arr[1], array.GetTimestamp(1, 9));
    }
    {
        // compact (precision <= 3)
        std::vector<Timestamp> arr = {Timestamp(0, 0), Timestamp(12345, 0)};
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), 8, pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteTimestamp(i, arr[i], 3);
        }
        writer.Complete();
        ASSERT_EQ(arr[0], array.GetTimestamp(0, 3));
        ASSERT_EQ(arr[1], array.GetTimestamp(1, 3));
    }
    // binary
    {
        std::vector<Bytes> arr;
        arr.emplace_back("hello", pool.get());
        arr.emplace_back("world", pool.get());
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), 8, pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteBinary(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(arr[0], *array.GetBinary(0));
        ASSERT_EQ(arr[1], *array.GetBinary(1));
    }
    // array
    {
        // element1
        std::vector<int32_t> arr1 = {1, 2};
        BinaryArray array1;
        BinaryArrayWriter writer1 =
            BinaryArrayWriter(&array1, arr1.size(), sizeof(int32_t), pool.get());
        for (size_t i = 0; i < arr1.size(); i++) {
            writer1.WriteInt(i, arr1[i]);
        }
        writer1.Complete();
        // element2
        std::vector<int32_t> arr2 = {100, 200};
        BinaryArray array2;
        BinaryArrayWriter writer2 =
            BinaryArrayWriter(&array2, arr2.size(), sizeof(int32_t), pool.get());
        for (size_t i = 0; i < arr2.size(); i++) {
            writer2.WriteInt(i, arr2[i]);
        }
        writer2.Complete();
        // array
        std::vector<BinaryArray> arr;
        arr.push_back(array1);
        arr.push_back(array2);
        BinaryArray array;
        BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), 8, pool.get());
        for (size_t i = 0; i < arr.size(); i++) {
            writer.WriteArray(i, arr[i]);
        }
        writer.Complete();
        ASSERT_EQ(arr[0].ToIntArray().value(), array.GetArray(0)->ToIntArray().value());
        ASSERT_EQ(arr[1].ToIntArray().value(), array.GetArray(1)->ToIntArray().value());
    }
    // row
    {
        BinaryRow row1 = BinaryRowGenerator::GenerateRow(
            {std::string("Alice"), 30, 12.1, NullType()}, pool.get());
        BinaryRow row2 =
            BinaryRowGenerator::GenerateRow({std::string("Bob"), 40, 14.1, NullType()}, pool.get());
        BinaryArray array;
        BinaryArrayWriter writer =
            BinaryArrayWriter(&array, /*num_elements=*/2, /*element_size=*/8, pool.get());
        writer.WriteRow(0, row1);
        writer.WriteRow(1, row2);
        writer.Complete();

        ASSERT_EQ(2, array.Size());
        auto de_row1 = array.GetRow(0, 4);
        ASSERT_EQ(row1, *(std::dynamic_pointer_cast<BinaryRow>(de_row1)));
        auto de_row2 = array.GetRow(1, 4);
        ASSERT_EQ(row2, *(std::dynamic_pointer_cast<BinaryRow>(de_row2)));
    }
    // map
    {
        auto key = BinaryArray::FromIntArray({1, 2, 3, 5}, pool.get());
        auto value = BinaryArray::FromLongArray({100ll, 200ll, 300ll, 500ll}, pool.get());
        auto map = BinaryMap::ValueOf(key, value, pool.get());
        BinaryArray array;
        BinaryArrayWriter writer =
            BinaryArrayWriter(&array, /*num_elements=*/1, /*element_size=*/8, pool.get());
        writer.WriteMap(0, *map);
        writer.Complete();

        ASSERT_EQ(1, array.Size());
        auto de_map = array.GetMap(0);
        ASSERT_EQ(key.HashCode(),
                  std::dynamic_pointer_cast<BinaryArray>(de_map->KeyArray())->HashCode());
        ASSERT_EQ(value.HashCode(),
                  std::dynamic_pointer_cast<BinaryArray>(de_map->ValueArray())->HashCode());
    }
}

TEST(BinaryArrayTest, TestCopy) {
    auto pool = GetDefaultPool();

    std::vector<bool> arr = {true, false};
    BinaryArray array;
    BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), sizeof(bool), pool.get());
    for (size_t i = 0; i < arr.size(); i++) {
        writer.WriteBoolean(i, arr[i]);
    }
    writer.Complete();

    auto copy_array = array.Copy(pool.get());
    ASSERT_EQ(array.ToBooleanArray().value(), copy_array.ToBooleanArray().value());
}

TEST(BinaryArrayTest, TestNullValue) {
    auto pool = GetDefaultPool();
    std::vector<int64_t> arr = {1, 2, 3, 4, 5};
    BinaryArray array;
    BinaryArrayWriter writer =
        BinaryArrayWriter(&array, arr.size() + 2, sizeof(int64_t), pool.get());
    for (size_t i = 0; i < arr.size(); i++) {
        writer.WriteLong(i, arr[i]);
    }
    // last two element is null
    writer.SetNullValue<int64_t>(5);
    writer.SetNullAt(6);
    writer.Complete();
    ASSERT_TRUE(array.AnyNull());

    auto ret = BinaryArray::FromLongArray(&array, pool.get());
    ASSERT_EQ(ret, array);
}

TEST(BinaryArrayTest, TestFromLongArray) {
    auto pool = GetDefaultPool();
    auto f1 = arrow::ipc::internal::json::ArrayFromJSON(arrow::list(arrow::int64()),
                                                        R"([[123, null], [789], [12345], [12]])")
                  .ValueOrDie();
    auto list_array = arrow::internal::checked_pointer_cast<arrow::ListArray>(f1);
    auto array = ColumnarArray(list_array->values().get(), pool, /*offset=*/0, 2);

    BinaryArray ret = BinaryArray::FromLongArray(&array, pool.get());

    BinaryArray expected_array;
    BinaryArrayWriter writer = BinaryArrayWriter(&expected_array, 2, sizeof(int64_t), pool.get());
    writer.Reset();
    writer.WriteLong(0, 123);
    writer.SetNullAt(1);
    writer.Complete();

    ASSERT_EQ(ret, expected_array);

    ASSERT_NOK_WITH_MSG(expected_array.ToLongArray(),
                        "Primitive array must not contain a null value.");
}

TEST(BinaryArrayTest, TestFromIntArray) {
    auto pool = GetDefaultPool();
    std::vector<int32_t> values = {10, 20, 30, 100};
    BinaryArray array = BinaryArray::FromIntArray(values, pool.get());
    ASSERT_OK_AND_ASSIGN(std::vector<int32_t> result, array.ToIntArray());
    ASSERT_EQ(result, values);
}

TEST(BinaryArrayTest, TestFromAllNullLongArray) {
    auto pool = GetDefaultPool();
    auto f1 = arrow::ipc::internal::json::ArrayFromJSON(arrow::list(arrow::int64()),
                                                        R"([[null, null], [789], [12345], [12]])")
                  .ValueOrDie();
    auto list_array = arrow::internal::checked_pointer_cast<arrow::ListArray>(f1);
    auto array = ColumnarArray(list_array->values().get(), pool, /*offset=*/0, 2);

    BinaryArray ret = BinaryArray::FromLongArray(&array, pool.get());

    BinaryArray expected_array;
    BinaryArrayWriter writer = BinaryArrayWriter(&expected_array, 2, sizeof(int64_t), pool.get());
    writer.Reset();
    writer.SetNullAt(0);
    writer.SetNullAt(1);
    writer.Complete();

    ASSERT_EQ(ret, expected_array);

    ASSERT_NOK_WITH_MSG(expected_array.ToLongArray(),
                        "Primitive array must not contain a null value.");
}
TEST(BinaryArrayTest, TestReset) {
    auto pool = GetDefaultPool();
    std::vector<int64_t> arr = {1, 2, 3, 4, 5};
    BinaryArray array;
    BinaryArrayWriter writer = BinaryArrayWriter(&array, arr.size(), sizeof(int64_t), pool.get());
    writer.Reset();
    for (size_t i = 0; i < arr.size(); i++) {
        writer.WriteLong(i, arr[i]);
    }
    writer.Complete();
    ASSERT_EQ(arr, array.ToLongArray().value());
}

TEST(BinaryArrayTest, TestGetElementSize) {
    ASSERT_EQ(sizeof(bool), BinaryArrayWriter::GetElementSize(arrow::Type::type::BOOL));
    ASSERT_EQ(sizeof(int8_t), BinaryArrayWriter::GetElementSize(arrow::Type::type::INT8));
    ASSERT_EQ(sizeof(int16_t), BinaryArrayWriter::GetElementSize(arrow::Type::type::INT16));
    ASSERT_EQ(sizeof(int32_t), BinaryArrayWriter::GetElementSize(arrow::Type::type::INT32));
    ASSERT_EQ(sizeof(int32_t), BinaryArrayWriter::GetElementSize(arrow::Type::type::DATE32));
    ASSERT_EQ(sizeof(int64_t), BinaryArrayWriter::GetElementSize(arrow::Type::type::INT64));
    ASSERT_EQ(sizeof(float), BinaryArrayWriter::GetElementSize(arrow::Type::type::FLOAT));
    ASSERT_EQ(sizeof(double), BinaryArrayWriter::GetElementSize(arrow::Type::type::DOUBLE));
    // default cases: variable-length types use 8 bytes (offset + length)
    ASSERT_EQ(8, BinaryArrayWriter::GetElementSize(arrow::Type::type::STRING));
    ASSERT_EQ(8, BinaryArrayWriter::GetElementSize(arrow::Type::type::BINARY));
    ASSERT_EQ(8, BinaryArrayWriter::GetElementSize(arrow::Type::type::TIMESTAMP));
    ASSERT_EQ(8, BinaryArrayWriter::GetElementSize(arrow::Type::type::DECIMAL128));
}

TEST(BinaryArrayTest, TestSetNullAtWithArrowType) {
    auto pool = GetDefaultPool();

    {
        // BOOL
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(bool), pool.get());
        writer.WriteBoolean(0, true);
        writer.SetNullAt(1, arrow::Type::type::BOOL);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_TRUE(array.GetBoolean(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // INT8
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(int8_t), pool.get());
        writer.WriteByte(0, 42);
        writer.SetNullAt(1, arrow::Type::type::INT8);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_EQ(42, array.GetByte(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // INT16
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(int16_t), pool.get());
        writer.WriteShort(0, 1000);
        writer.SetNullAt(1, arrow::Type::type::INT16);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_EQ(1000, array.GetShort(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // INT32
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(int32_t), pool.get());
        writer.WriteInt(0, 100000);
        writer.SetNullAt(1, arrow::Type::type::INT32);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_EQ(100000, array.GetInt(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // DATE32
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(int32_t), pool.get());
        writer.WriteInt(0, 19000);
        writer.SetNullAt(1, arrow::Type::type::DATE32);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_EQ(19000, array.GetDate(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // INT64
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(int64_t), pool.get());
        writer.WriteLong(0, 123456789L);
        writer.SetNullAt(1, arrow::Type::type::INT64);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_EQ(123456789L, array.GetLong(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // FLOAT
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(float), pool.get());
        writer.WriteFloat(0, 3.14f);
        writer.SetNullAt(1, arrow::Type::type::FLOAT);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_FLOAT_EQ(3.14f, array.GetFloat(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // DOUBLE
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, sizeof(double), pool.get());
        writer.WriteDouble(0, 2.718);
        writer.SetNullAt(1, arrow::Type::type::DOUBLE);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_DOUBLE_EQ(2.718, array.GetDouble(0));
        ASSERT_TRUE(array.IsNullAt(1));
    }
    {
        // STRING (default path, uses 8-byte null)
        BinaryArray array;
        BinaryArrayWriter writer(&array, 2, 8, pool.get());
        writer.WriteString(0, BinaryString::FromString("hello", pool.get()));
        writer.SetNullAt(1, arrow::Type::type::STRING);
        writer.Complete();
        ASSERT_FALSE(array.IsNullAt(0));
        ASSERT_EQ("hello", std::string(array.GetStringView(0)));
        ASSERT_TRUE(array.IsNullAt(1));
    }
}

}  // namespace paimon::test
