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

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

#include "gtest/gtest.h"
#include "paimon/common/memory/memory_slice_input.h"
#include "paimon/common/memory/memory_slice_output.h"
#include "paimon/memory/bytes.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {

class MemorySliceTest : public ::testing::Test {
 protected:
    std::shared_ptr<MemoryPool> pool_ = GetDefaultPool();
};

// ==================== MemorySliceOutput Tests ====================

TEST_F(MemorySliceTest, TestOutputWriteValueAndToSlice) {
    MemorySliceOutput output(64, pool_.get());
    ASSERT_EQ(0, output.Size());

    output.WriteValue<int32_t>(42);
    output.WriteValue<int64_t>(123456789LL);
    output.WriteValue<int16_t>(static_cast<int16_t>(7));
    output.WriteValue<int8_t>(static_cast<int8_t>(-1));
    output.WriteValue<bool>(true);

    // 4 + 8 + 2 + 1 + 1 = 16
    ASSERT_EQ(16, output.Size());

    MemorySlice slice = output.ToSlice();
    ASSERT_EQ(16, slice.Length());
    ASSERT_EQ(0, slice.Offset());

    ASSERT_EQ(42, slice.ReadInt(0));
    ASSERT_EQ(123456789LL, slice.ReadLong(4));
    ASSERT_EQ(7, slice.ReadShort(12));
    ASSERT_EQ(-1, slice.ReadByte(14));
}

TEST_F(MemorySliceTest, TestOutputWriteBytes) {
    MemorySliceOutput output(16, pool_.get());

    std::string data = "hello world";
    auto bytes = std::make_shared<Bytes>(data, pool_.get());
    output.WriteBytes(bytes);
    ASSERT_EQ(static_cast<int32_t>(data.size()), output.Size());

    MemorySlice slice = output.ToSlice();
    ASSERT_EQ("hello world", slice.ReadStringView());
}

TEST_F(MemorySliceTest, TestOutputWriteBytesSubRange) {
    MemorySliceOutput output(16, pool_.get());

    std::string data = "hello world";
    auto bytes = std::make_shared<Bytes>(data, pool_.get());
    output.WriteBytes(bytes, 6, 5);
    ASSERT_EQ(5, output.Size());

    MemorySlice slice = output.ToSlice();
    ASSERT_EQ("world", slice.ReadStringView());
}

TEST_F(MemorySliceTest, TestOutputVarLenInt) {
    MemorySliceOutput output(32, pool_.get());

    ASSERT_OK(output.WriteVarLenInt(0));
    ASSERT_OK(output.WriteVarLenInt(127));
    ASSERT_OK(output.WriteVarLenInt(128));
    ASSERT_OK(output.WriteVarLenInt(16384));

    MemorySlice slice = output.ToSlice();
    MemorySliceInput input(slice);

    ASSERT_OK_AND_ASSIGN(int32_t v0, input.ReadVarLenInt());
    ASSERT_EQ(0, v0);
    ASSERT_OK_AND_ASSIGN(int32_t v127, input.ReadVarLenInt());
    ASSERT_EQ(127, v127);
    ASSERT_OK_AND_ASSIGN(int32_t v128, input.ReadVarLenInt());
    ASSERT_EQ(128, v128);
    ASSERT_OK_AND_ASSIGN(int32_t v16384, input.ReadVarLenInt());
    ASSERT_EQ(16384, v16384);
}

TEST_F(MemorySliceTest, TestOutputVarLenLong) {
    MemorySliceOutput output(32, pool_.get());

    ASSERT_OK(output.WriteVarLenLong(0));
    ASSERT_OK(output.WriteVarLenLong(127));
    ASSERT_OK(output.WriteVarLenLong(128));
    ASSERT_OK(output.WriteVarLenLong(1234567890123LL));

    MemorySlice slice = output.ToSlice();
    MemorySliceInput input(slice);

    ASSERT_OK_AND_ASSIGN(int64_t v0, input.ReadVarLenLong());
    ASSERT_EQ(0, v0);
    ASSERT_OK_AND_ASSIGN(int64_t v127, input.ReadVarLenLong());
    ASSERT_EQ(127, v127);
    ASSERT_OK_AND_ASSIGN(int64_t v128, input.ReadVarLenLong());
    ASSERT_EQ(128, v128);
    ASSERT_OK_AND_ASSIGN(int64_t v_large, input.ReadVarLenLong());
    ASSERT_EQ(1234567890123LL, v_large);
}

TEST_F(MemorySliceTest, TestOutputVarLenNegativeValue) {
    MemorySliceOutput output(16, pool_.get());

    ASSERT_NOK_WITH_MSG(output.WriteVarLenInt(-1), "negative value: v=-1");
    ASSERT_NOK_WITH_MSG(output.WriteVarLenLong(-2), "negative value: v=-2");
    ASSERT_EQ(0, output.Size());
}

TEST_F(MemorySliceTest, TestOutputReset) {
    MemorySliceOutput output(16, pool_.get());
    output.WriteValue<int32_t>(42);
    ASSERT_EQ(4, output.Size());

    output.Reset();
    ASSERT_EQ(0, output.Size());

    output.WriteValue<int64_t>(99);
    ASSERT_EQ(8, output.Size());
    MemorySlice slice = output.ToSlice();
    ASSERT_EQ(99, slice.ReadLong(0));
}

TEST_F(MemorySliceTest, TestOutputAutoGrow) {
    // Start with tiny buffer, force multiple grows
    MemorySliceOutput output(4, pool_.get());
    for (int32_t i = 0; i < 100; i++) {
        output.WriteValue<int32_t>(i);
    }
    ASSERT_EQ(400, output.Size());

    MemorySlice slice = output.ToSlice();
    for (int32_t i = 0; i < 100; i++) {
        ASSERT_EQ(i, slice.ReadInt(i * 4));
    }
}

TEST_F(MemorySliceTest, TestOutputByteOrder) {
    {
        MemorySliceOutput output(16, pool_.get());
        output.SetOrder(ByteOrder::PAIMON_BIG_ENDIAN);
        output.WriteValue<int32_t>(0x01020304);
        MemorySlice slice = output.ToSlice();

        MemorySliceInput input(slice);
        input.SetOrder(ByteOrder::PAIMON_BIG_ENDIAN);
        ASSERT_EQ(0x01020304, input.ReadInt());
    }
    {
        MemorySliceOutput output(16, pool_.get());
        output.SetOrder(ByteOrder::PAIMON_LITTLE_ENDIAN);
        output.WriteValue<int32_t>(0x01020304);
        MemorySlice slice = output.ToSlice();

        MemorySliceInput input(slice);
        input.SetOrder(ByteOrder::PAIMON_LITTLE_ENDIAN);
        ASSERT_EQ(0x01020304, input.ReadInt());
    }
    {
        MemorySliceOutput output(16, pool_.get());
        output.SetOrder(ByteOrder::PAIMON_BIG_ENDIAN);
        output.WriteValue<int64_t>(123456789123456ll);
        MemorySlice slice = output.ToSlice();

        MemorySliceInput input(slice);
        input.SetOrder(ByteOrder::PAIMON_BIG_ENDIAN);
        ASSERT_EQ(123456789123456ll, input.ReadLong());
    }
    {
        MemorySliceOutput output(16, pool_.get());
        output.SetOrder(ByteOrder::PAIMON_LITTLE_ENDIAN);
        output.WriteValue<int64_t>(123456789123456ll);
        MemorySlice slice = output.ToSlice();

        MemorySliceInput input(slice);
        input.SetOrder(ByteOrder::PAIMON_LITTLE_ENDIAN);
        ASSERT_EQ(123456789123456ll, input.ReadLong());
    }
}

// ==================== MemorySlice Tests ====================

TEST_F(MemorySliceTest, TestSliceWrapBytes) {
    std::string data = "abcdefghij";
    auto bytes = std::make_shared<Bytes>(data, pool_.get());
    MemorySlice slice = MemorySlice::Wrap(bytes);

    ASSERT_EQ(10, slice.Length());
    ASSERT_EQ(0, slice.Offset());
    ASSERT_EQ("abcdefghij", slice.ReadStringView());
}

TEST_F(MemorySliceTest, TestSliceWrapSegment) {
    auto bytes = std::make_shared<Bytes>("hello", pool_.get());
    MemorySegment segment = MemorySegment::Wrap(bytes);
    MemorySlice slice = MemorySlice::Wrap(segment);

    ASSERT_EQ(5, slice.Length());
    ASSERT_EQ("hello", slice.ReadStringView());
}

TEST_F(MemorySliceTest, TestSliceSubSlice) {
    std::string data = "abcdefghij";
    auto bytes = std::make_shared<Bytes>(data, pool_.get());
    MemorySlice slice = MemorySlice::Wrap(bytes);

    MemorySlice sub = slice.Slice(3, 4);
    ASSERT_EQ(4, sub.Length());
    ASSERT_EQ(3, sub.Offset());
    ASSERT_EQ("defg", sub.ReadStringView());

    // Slice with same range returns equivalent slice
    MemorySlice same = slice.Slice(0, 10);
    ASSERT_EQ(10, same.Length());
    ASSERT_EQ("abcdefghij", same.ReadStringView());
}

TEST_F(MemorySliceTest, TestSliceReadPrimitives) {
    MemorySliceOutput output(32, pool_.get());
    output.WriteValue<int8_t>(static_cast<int8_t>(0x7F));
    output.WriteValue<int16_t>(static_cast<int16_t>(1234));
    output.WriteValue<int32_t>(56789);
    output.WriteValue<int64_t>(9876543210LL);

    MemorySlice slice = output.ToSlice();
    ASSERT_EQ(0x7F, slice.ReadByte(0));
    ASSERT_EQ(static_cast<int16_t>(1234), slice.ReadShort(1));
    ASSERT_EQ(56789, slice.ReadInt(3));
    ASSERT_EQ(9876543210LL, slice.ReadLong(7));
}

TEST_F(MemorySliceTest, TestSliceCopyBytes) {
    std::string data = "copy me";
    auto bytes = std::make_shared<Bytes>(data, pool_.get());

    MemorySlice slice = MemorySlice::Wrap(bytes);
    auto copied = slice.CopyBytes(pool_.get());
    ASSERT_EQ(data.size(), copied->size());
    ASSERT_EQ(data, std::string(copied->data(), copied->size()));
    // Verify it's a true copy (different pointer)
    ASSERT_NE(bytes->data(), copied->data());

    MemorySlice slice2(MemorySegment::Wrap(bytes), /*offset=*/5, /*length=*/2);
    copied = slice2.CopyBytes(pool_.get());
    ASSERT_EQ(2, copied->size());
    ASSERT_EQ("me", std::string(copied->data(), copied->size()));
    ASSERT_NE(bytes->data(), copied->data());
}

TEST_F(MemorySliceTest, TestSliceGetOrCreateHeapMemory) {
    auto bytes = std::make_shared<Bytes>("test", pool_.get());
    MemorySlice slice = MemorySlice::Wrap(bytes);
    ASSERT_EQ(bytes, slice.GetOrCreateHeapMemory(pool_.get()));
}

TEST_F(MemorySliceTest, TestSliceToInput) {
    MemorySliceOutput output(16, pool_.get());
    output.WriteValue<int32_t>(42);
    output.WriteValue<int64_t>(99);

    MemorySlice slice = output.ToSlice();
    MemorySliceInput input = slice.ToInput();

    ASSERT_EQ(42, input.ReadInt());
    ASSERT_EQ(99, input.ReadLong());
}

// ==================== MemorySliceInput Tests ====================

TEST_F(MemorySliceTest, TestInputPositionAndAvailable) {
    MemorySliceOutput output(16, pool_.get());
    output.WriteValue<int32_t>(1);
    output.WriteValue<int32_t>(2);

    MemorySliceInput input(output.ToSlice());
    ASSERT_EQ(0, input.Position());
    ASSERT_EQ(8, input.Available());
    ASSERT_TRUE(input.IsReadable());

    input.ReadInt();
    ASSERT_EQ(4, input.Position());
    ASSERT_EQ(4, input.Available());
    ASSERT_TRUE(input.IsReadable());

    input.ReadInt();
    ASSERT_EQ(8, input.Position());
    ASSERT_EQ(0, input.Available());
    ASSERT_FALSE(input.IsReadable());
}

TEST_F(MemorySliceTest, TestInputSetPosition) {
    MemorySliceOutput output(16, pool_.get());
    output.WriteValue<int32_t>(111);
    output.WriteValue<int32_t>(222);

    MemorySliceInput input(output.ToSlice());
    input.ReadInt();
    ASSERT_EQ(4, input.Position());

    ASSERT_OK(input.SetPosition(0));
    ASSERT_EQ(0, input.Position());
    ASSERT_EQ(111, input.ReadInt());

    // Invalid positions
    ASSERT_NOK_WITH_MSG(input.SetPosition(-1), "position -1 index out of bounds");
    ASSERT_NOK_WITH_MSG(input.SetPosition(100), "position 100 index out of bounds");
}

TEST_F(MemorySliceTest, TestInputReadByte) {
    MemorySliceOutput output(8, pool_.get());
    output.WriteValue<int8_t>(static_cast<int8_t>(-128));
    output.WriteValue<int8_t>(static_cast<int8_t>(127));
    output.WriteValue<int8_t>(static_cast<int8_t>(0));

    MemorySliceInput input(output.ToSlice());
    ASSERT_EQ(-128, input.ReadByte());
    ASSERT_EQ(127, input.ReadByte());
    ASSERT_EQ(0, input.ReadByte());
}

TEST_F(MemorySliceTest, TestInputReadUnsignedByte) {
    MemorySliceOutput output(8, pool_.get());
    output.WriteValue<int8_t>(static_cast<int8_t>(0xFF));

    MemorySliceInput input(output.ToSlice());
    // ReadUnsignedByte masks with 0xFF
    int8_t value = input.ReadUnsignedByte();
    ASSERT_EQ(static_cast<int8_t>(0xFF & 0xFF), value);
}

TEST_F(MemorySliceTest, TestInputReadIntAndLong) {
    MemorySliceOutput output(32, pool_.get());
    output.WriteValue<int32_t>(0);
    output.WriteValue<int32_t>(INT32_MAX);
    output.WriteValue<int32_t>(INT32_MIN);
    output.WriteValue<int64_t>(0);
    output.WriteValue<int64_t>(INT64_MAX);
    output.WriteValue<int64_t>(INT64_MIN);

    MemorySliceInput input(output.ToSlice());
    ASSERT_EQ(0, input.ReadInt());
    ASSERT_EQ(INT32_MAX, input.ReadInt());
    ASSERT_EQ(INT32_MIN, input.ReadInt());
    ASSERT_EQ(0, input.ReadLong());
    ASSERT_EQ(INT64_MAX, input.ReadLong());
    ASSERT_EQ(INT64_MIN, input.ReadLong());
}

TEST_F(MemorySliceTest, TestInputReadSliceView) {
    std::string data = "abcdefghij";
    auto bytes = std::make_shared<Bytes>(data, pool_.get());
    MemorySlice slice = MemorySlice::Wrap(bytes);
    MemorySliceInput input(slice);

    MemorySlice sub = input.ReadSliceView(5);
    ASSERT_EQ("abcde", sub.ReadStringView());
    ASSERT_EQ(5, input.Position());

    MemorySlice sub2 = input.ReadSliceView(5);
    ASSERT_EQ("fghij", sub2.ReadStringView());
    ASSERT_EQ(10, input.Position());
}

// ==================== Round-trip Output → Input Tests ====================

TEST_F(MemorySliceTest, TestRoundTripMixedTypes) {
    MemorySliceOutput output(64, pool_.get());
    output.WriteValue<int8_t>(static_cast<int8_t>(42));
    output.WriteValue<int16_t>(static_cast<int16_t>(1000));
    output.WriteValue<int32_t>(123456);
    output.WriteValue<int64_t>(9876543210LL);
    ASSERT_OK(output.WriteVarLenInt(300));
    ASSERT_OK(output.WriteVarLenLong(1000000LL));

    auto bytes = std::make_shared<Bytes>("test", pool_.get());
    output.WriteValue<int32_t>(static_cast<int32_t>(bytes->size()));
    output.WriteBytes(bytes);

    MemorySlice slice = output.ToSlice();
    MemorySliceInput input(slice);
    ASSERT_EQ(42, input.ReadByte());
    // int16_t written at position 1, read via slice
    ASSERT_EQ(1000, slice.ReadShort(1));
    ASSERT_OK(input.SetPosition(3));
    ASSERT_EQ(123456, input.ReadInt());
    ASSERT_OK(input.SetPosition(3));
    ASSERT_EQ(123456, input.ReadInt());
    ASSERT_EQ(9876543210LL, input.ReadLong());

    ASSERT_OK_AND_ASSIGN(int32_t var_int, input.ReadVarLenInt());
    ASSERT_EQ(300, var_int);
    ASSERT_OK_AND_ASSIGN(int64_t var_long, input.ReadVarLenLong());
    ASSERT_EQ(1000000LL, var_long);

    int32_t str_len = input.ReadInt();
    ASSERT_EQ(4, str_len);
    MemorySlice str_slice = input.ReadSliceView(str_len);
    ASSERT_EQ("test", str_slice.ReadStringView());
}

}  // namespace paimon::test
