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

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "gtest/gtest.h"
#include "paimon/common/data/binary_array_writer.h"
#include "paimon/common/data/binary_map.h"
#include "paimon/common/data/binary_string.h"
#include "paimon/data/decimal.h"
#include "paimon/data/timestamp.h"
#include "paimon/memory/bytes.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/testing/utils/binary_row_generator.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {
TEST(DataEvolutionArrayTest, TestSimple) {
    auto pool = GetDefaultPool();

    std::vector<int32_t> array_offsets = {0, 2, 0, 1, 2, 1};
    std::vector<int32_t> field_offsets = {0, 0, 1, 1, 1, 0};

    std::vector<int64_t> src_array1 = {1, -100};
    auto array1 = BinaryRowGenerator::FromLongArrayWithNull(src_array1, pool.get());

    std::vector<int64_t> src_array2 = {2, -2};
    auto array2 = BinaryRowGenerator::FromLongArrayWithNull(src_array2, pool.get());

    std::vector<int64_t> src_array3 = {3, -3};
    auto array3 = BinaryRowGenerator::FromLongArrayWithNull(src_array3, pool.get());

    DataEvolutionArray data_evolution_array(std::vector<BinaryArray>({array1, array2, array3}),
                                            array_offsets, field_offsets);

    ASSERT_FALSE(data_evolution_array.IsNullAt(0));

    ASSERT_EQ(data_evolution_array.GetLong(0), 1);
    ASSERT_EQ(data_evolution_array.GetLong(1), 3);
    ASSERT_EQ(data_evolution_array.GetLong(2), -100);
    ASSERT_EQ(data_evolution_array.GetLong(3), -2);
    ASSERT_EQ(data_evolution_array.GetLong(4), -3);
    ASSERT_EQ(data_evolution_array.GetLong(5), 2);

    ASSERT_OK_AND_ASSIGN(auto ret, data_evolution_array.ToLongArray());
    ASSERT_EQ(ret, std::vector<int64_t>({1, 3, -100, -2, -3, 2}));

    ASSERT_NOK_WITH_MSG(data_evolution_array.ToBooleanArray(),
                        "DataEvolutionArray not support ToBooleanArray");
}

TEST(DataEvolutionArrayTest, TestNullValue) {
    auto pool = GetDefaultPool();

    std::vector<int32_t> array_offsets = {-2, -1, 0, 0};
    std::vector<int32_t> field_offsets = {-1, -1, 0, 1};

    std::vector<int64_t> src_array1 = {1, -100};
    auto array1 = BinaryRowGenerator::FromLongArrayWithNull(src_array1, pool.get());

    DataEvolutionArray data_evolution_array(std::vector<BinaryArray>({array1}), array_offsets,
                                            field_offsets);

    ASSERT_TRUE(data_evolution_array.IsNullAt(0));
    ASSERT_TRUE(data_evolution_array.IsNullAt(1));
    ASSERT_EQ(data_evolution_array.GetLong(2), 1);
    ASSERT_EQ(data_evolution_array.GetLong(3), -100);
}

TEST(DataEvolutionArrayTest, TestAllFieldTypes) {
    auto pool = GetDefaultPool();

    // We create separate BinaryArrays for each field type and use DataEvolutionArray
    // to proxy access across them. Each array has 2 elements; the evolution array
    // picks one element from each underlying array via array_offsets/field_offsets.

    // --- Boolean array (array index 0) ---
    BinaryArray bool_array;
    {
        BinaryArrayWriter writer(&bool_array, 2, sizeof(bool), pool.get());
        writer.WriteBoolean(0, true);
        writer.WriteBoolean(1, false);
        writer.Complete();
    }

    // --- Byte array (array index 1) ---
    BinaryArray byte_array;
    {
        BinaryArrayWriter writer(&byte_array, 2, sizeof(int8_t), pool.get());
        writer.WriteByte(0, 42);
        writer.WriteByte(1, -7);
        writer.Complete();
    }

    // --- Short array (array index 2) ---
    BinaryArray short_array;
    {
        BinaryArrayWriter writer(&short_array, 2, sizeof(int16_t), pool.get());
        writer.WriteShort(0, 1024);
        writer.WriteShort(1, -512);
        writer.Complete();
    }

    // --- Int array (array index 3) ---
    BinaryArray int_array;
    {
        BinaryArrayWriter writer(&int_array, 2, sizeof(int32_t), pool.get());
        writer.WriteInt(0, 100000);
        writer.WriteInt(1, -99999);
        writer.Complete();
    }

    // --- Long array (array index 4) ---
    BinaryArray long_array;
    {
        BinaryArrayWriter writer(&long_array, 2, sizeof(int64_t), pool.get());
        writer.WriteLong(0, 1234567890LL);
        writer.WriteLong(1, -987654321LL);
        writer.Complete();
    }

    // --- Float array (array index 5) ---
    BinaryArray float_array;
    {
        BinaryArrayWriter writer(&float_array, 2, sizeof(float), pool.get());
        writer.WriteFloat(0, 3.14f);
        writer.WriteFloat(1, -2.71f);
        writer.Complete();
    }

    // --- Double array (array index 6) ---
    BinaryArray double_array;
    {
        BinaryArrayWriter writer(&double_array, 2, sizeof(double), pool.get());
        writer.WriteDouble(0, 1.23456789);
        writer.WriteDouble(1, -9.87654321);
        writer.Complete();
    }

    // --- String array (array index 7) ---
    BinaryArray string_array;
    {
        BinaryArrayWriter writer(&string_array, 2, 8, pool.get());
        writer.WriteString(0, BinaryString::FromString("hello", pool.get()));
        writer.WriteString(1, BinaryString::FromString("world", pool.get()));
        writer.Complete();
    }

    // --- Decimal array (array index 8), precision=10, scale=2 ---
    BinaryArray decimal_array;
    {
        BinaryArrayWriter writer(&decimal_array, 2, 8, pool.get());
        writer.WriteDecimal(0, Decimal(10, 2, 12345), 10);
        writer.WriteDecimal(1, Decimal(10, 2, 67890), 10);
        writer.Complete();
    }

    // --- Timestamp array (array index 9), precision=9 (non-compact) ---
    BinaryArray timestamp_array;
    {
        BinaryArrayWriter writer(&timestamp_array, 2, 8, pool.get());
        writer.WriteTimestamp(0, Timestamp(1000, 500), 9);
        writer.WriteTimestamp(1, Timestamp(2000, 999), 9);
        writer.Complete();
    }

    // --- Binary/Bytes array (array index 10) ---
    BinaryArray binary_array;
    {
        Bytes bytes_val1("abcde", pool.get());
        Bytes bytes_val2("fghij", pool.get());
        BinaryArrayWriter writer(&binary_array, 2, 8, pool.get());
        writer.WriteBinary(0, bytes_val1);
        writer.WriteBinary(1, bytes_val2);
        writer.Complete();
    }

    // --- Nested Array array (array index 11) ---
    BinaryArray nested_array;
    {
        auto inner = BinaryArray::FromIntArray({10, 20}, pool.get());
        auto inner2 = BinaryArray::FromIntArray({30, 40}, pool.get());
        BinaryArrayWriter writer(&nested_array, 2, 8, pool.get());
        writer.WriteArray(0, inner);
        writer.WriteArray(1, inner2);
        writer.Complete();
    }

    // --- Map array (array index 12) ---
    BinaryArray map_array;
    {
        auto map_key = BinaryArray::FromIntArray({1, 2}, pool.get());
        auto map_val = BinaryArray::FromLongArray({100LL, 200LL}, pool.get());
        auto map_obj = BinaryMap::ValueOf(map_key, map_val, pool.get());
        BinaryArrayWriter writer(&map_array, 1, 8, pool.get());
        writer.WriteMap(0, *map_obj);
        writer.Complete();
    }

    // --- Row array (array index 13) ---
    BinaryArray row_array;
    {
        BinaryRow row1 = BinaryRowGenerator::GenerateRow(
            {std::string("Alice"), static_cast<int32_t>(30)}, pool.get());
        BinaryArrayWriter writer(&row_array, 1, 8, pool.get());
        writer.WriteRow(0, row1);
        writer.Complete();
    }

    // --- Date array (array index 14), stored as int32 ---
    BinaryArray date_array;
    {
        BinaryArrayWriter writer(&date_array, 2, sizeof(int32_t), pool.get());
        writer.WriteInt(0, 19000);  // days since epoch
        writer.WriteInt(1, 18500);
        writer.Complete();
    }

    std::vector<BinaryArray> arrays = {
        bool_array,       // 0
        byte_array,       // 1
        short_array,      // 2
        int_array,        // 3
        long_array,       // 4
        float_array,      // 5
        double_array,     // 6
        string_array,     // 7
        decimal_array,    // 8
        timestamp_array,  // 9
        binary_array,     // 10
        nested_array,     // 11
        map_array,        // 12
        row_array,        // 13
        date_array,       // 14
    };

    // Each evolution entry: (array_index, field_index_within_array)
    // pos 0:  boolean   -> arrays[0][0]  = true
    // pos 1:  byte      -> arrays[1][0]  = 42
    // pos 2:  short     -> arrays[2][1]  = -512
    // pos 3:  int       -> arrays[3][0]  = 100000
    // pos 4:  long      -> arrays[4][1]  = -987654321
    // pos 5:  float     -> arrays[5][0]  = 3.14f
    // pos 6:  double    -> arrays[6][1]  = -9.87654321
    // pos 7:  string    -> arrays[7][0]  = "hello"
    // pos 8:  decimal   -> arrays[8][1]  = Decimal(10,2,67890)
    // pos 9:  timestamp -> arrays[9][0]  = Timestamp(1000,500)
    // pos 10: binary    -> arrays[10][1] = "fghij"
    // pos 11: array     -> arrays[11][0] = [10,20]
    // pos 12: map       -> arrays[12][0]
    // pos 13: row       -> arrays[13][0] = ("Alice", 30)
    // pos 14: date      -> arrays[14][0] = 19000
    // pos 15: string    -> arrays[7][1]  = "world"
    std::vector<int32_t> array_offsets = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 7};
    std::vector<int32_t> field_offsets = {0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1};

    DataEvolutionArray evolution(arrays, array_offsets, field_offsets);

    ASSERT_EQ(evolution.Size(), 16);

    // Boolean
    ASSERT_FALSE(evolution.IsNullAt(0));
    ASSERT_EQ(evolution.GetBoolean(0), true);

    // Byte
    ASSERT_EQ(evolution.GetByte(1), 42);

    // Short
    ASSERT_EQ(evolution.GetShort(2), -512);

    // Int
    ASSERT_EQ(evolution.GetInt(3), 100000);

    // Long
    ASSERT_EQ(evolution.GetLong(4), -987654321LL);

    // Float
    ASSERT_FLOAT_EQ(evolution.GetFloat(5), 3.14f);

    // Double
    ASSERT_DOUBLE_EQ(evolution.GetDouble(6), -9.87654321);

    // String
    ASSERT_EQ(evolution.GetString(7).ToString(), "hello");

    // StringView
    ASSERT_EQ(evolution.GetStringView(15), "world");

    // Decimal
    ASSERT_EQ(evolution.GetDecimal(8, 10, 2), Decimal(10, 2, 67890));

    // Timestamp
    ASSERT_EQ(evolution.GetTimestamp(9, 9), Timestamp(1000, 500));

    // Binary
    auto retrieved_binary = evolution.GetBinary(10);
    Bytes expected_binary("fghij", pool.get());
    ASSERT_EQ(*retrieved_binary, expected_binary);

    // Nested Array
    auto retrieved_array = evolution.GetArray(11);
    ASSERT_OK_AND_ASSIGN(auto inner_values, retrieved_array->ToIntArray());
    ASSERT_EQ(inner_values, std::vector<int32_t>({10, 20}));

    // Map
    auto retrieved_map = evolution.GetMap(12);
    ASSERT_EQ(retrieved_map->Size(), 2);

    // Row
    auto retrieved_row = evolution.GetRow(13, 2);
    ASSERT_EQ(retrieved_row->GetString(0).ToString(), "Alice");
    ASSERT_EQ(retrieved_row->GetInt(1), 30);

    // Date
    ASSERT_EQ(evolution.GetDate(14), 19000);

    // Verify unsupported ToXxxArray methods
    ASSERT_NOK_WITH_MSG(evolution.ToBooleanArray(),
                        "DataEvolutionArray not support ToBooleanArray");
    ASSERT_NOK_WITH_MSG(evolution.ToByteArray(), "DataEvolutionArray not support ToByteArray");
    ASSERT_NOK_WITH_MSG(evolution.ToShortArray(), "DataEvolutionArray not support ToShortArray");
    ASSERT_NOK_WITH_MSG(evolution.ToIntArray(), "DataEvolutionArray not support ToIntArray");
    ASSERT_NOK_WITH_MSG(evolution.ToFloatArray(), "DataEvolutionArray not support ToFloatArray");
    ASSERT_NOK_WITH_MSG(evolution.ToDoubleArray(), "DataEvolutionArray not support ToDoubleArray");
}

}  // namespace paimon::test
