/*
 * 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 <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "gtest/gtest.h"
#include "paimon/common/sst/block_iterator.h"
#include "paimon/common/sst/block_reader.h"
#include "paimon/common/sst/block_writer.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {

class BlockWriteReadTest : public ::testing::Test {
 protected:
    void SetUp() override {
        pool_ = GetDefaultPool();
        comparator_ = [](const MemorySlice& a, const MemorySlice& b) -> Result<int32_t> {
            std::string_view va = a.ReadStringView();
            std::string_view vb = b.ReadStringView();
            if (va < vb) {
                return -1;
            }
            if (va > vb) {
                return 1;
            }
            return 0;
        };
    }

    std::shared_ptr<Bytes> MakeBytes(const std::string& str) const {
        return std::make_shared<Bytes>(str, pool_.get());
    }

    /// Build a block with the given key-value pairs via BlockWriter.
    MemorySlice BuildBlock(const std::vector<std::pair<std::string, std::string>>& pairs,
                           BlockWriter* writer) const {
        for (const auto& [k, v] : pairs) {
            auto key = MakeBytes(k);
            auto value = MakeBytes(v);
            EXPECT_OK(writer->Write(key, value));
        }
        EXPECT_OK_AND_ASSIGN(auto result, writer->Finish());
        return result;
    }

    std::shared_ptr<MemoryPool> pool_;
    MemorySlice::SliceComparator comparator_;
};

TEST_F(BlockWriteReadTest, AlignedWriteAndRead) {
    BlockWriter writer(1024, pool_);
    ASSERT_EQ(writer.Size(), 0);

    auto key1 = MakeBytes("aaaa");
    auto val1 = MakeBytes("1111");
    ASSERT_OK(writer.Write(key1, val1));
    ASSERT_EQ(writer.Size(), 1);
    ASSERT_GT(writer.Memory(), 0);

    auto key2 = MakeBytes("bbbb");
    auto val2 = MakeBytes("2222");
    auto key3 = MakeBytes("cccc");
    auto val3 = MakeBytes("3333");
    ASSERT_OK(writer.Write(key2, val2));
    ASSERT_OK(writer.Write(key3, val3));
    ASSERT_EQ(writer.Size(), 3);

    auto block = BuildBlock({}, &writer);
    ASSERT_GT(block.Length(), 0);
    // Last byte should be ALIGNED (0) because all kv pairs are same-size
    ASSERT_EQ(block.ReadByte(block.Length() - 1), static_cast<int8_t>(BlockAlignedType::ALIGNED));

    // Read back
    ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_));
    ASSERT_EQ(reader->RecordCount(), 3);
    ASSERT_NE(reader->Comparator(), nullptr);

    // SeekTo returns byte positions, should be increasing
    ASSERT_EQ(reader->SeekTo(0), 0);
    ASSERT_GT(reader->SeekTo(1), 0);
    ASSERT_GT(reader->SeekTo(2), reader->SeekTo(1));

    // BlockInput should be readable
    ASSERT_TRUE(reader->BlockInput().IsReadable());

    // Iterate and verify all entries
    auto iter = reader->Iterator();
    ASSERT_TRUE(iter->HasNext());
    ASSERT_OK_AND_ASSIGN(auto entry1, iter->Next());
    ASSERT_EQ(entry1.key.ReadStringView(), "aaaa");
    ASSERT_EQ(entry1.value.ReadStringView(), "1111");

    ASSERT_OK_AND_ASSIGN(auto entry2, iter->Next());
    ASSERT_EQ(entry2.key.ReadStringView(), "bbbb");
    ASSERT_EQ(entry2.value.ReadStringView(), "2222");

    ASSERT_OK_AND_ASSIGN(auto entry3, iter->Next());
    ASSERT_EQ(entry3.key.ReadStringView(), "cccc");
    ASSERT_EQ(entry3.value.ReadStringView(), "3333");

    ASSERT_FALSE(iter->HasNext());
}

TEST_F(BlockWriteReadTest, UnalignedWriteAndRead) {
    BlockWriter writer(1024, pool_, /*aligned=*/false);
    auto block = BuildBlock({{"a", "short"}, {"bb", "a_longer_value"}}, &writer);
    ASSERT_GT(block.Length(), 0);
    // Last byte should be UNALIGNED (1)
    ASSERT_EQ(block.ReadByte(block.Length() - 1), static_cast<int8_t>(BlockAlignedType::UNALIGNED));

    ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_));
    ASSERT_EQ(reader->RecordCount(), 2);

    auto iter = reader->Iterator();
    ASSERT_OK_AND_ASSIGN(auto entry1, iter->Next());
    ASSERT_EQ(entry1.key.ReadStringView(), "a");
    ASSERT_EQ(entry1.value.ReadStringView(), "short");

    ASSERT_OK_AND_ASSIGN(auto entry2, iter->Next());
    ASSERT_EQ(entry2.key.ReadStringView(), "bb");
    ASSERT_EQ(entry2.value.ReadStringView(), "a_longer_value");

    ASSERT_FALSE(iter->HasNext());
}

TEST_F(BlockWriteReadTest, EmptyBlockWriteAndRead) {
    BlockWriter writer(1024, pool_);

    ASSERT_OK_AND_ASSIGN(auto block, writer.Finish());
    ASSERT_GT(block.Length(), 0);
    // Empty block must be UNALIGNED (1)
    ASSERT_EQ(block.ReadByte(block.Length() - 1), static_cast<int8_t>(BlockAlignedType::UNALIGNED));

    // Reader should see 0 records
    ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_));
    ASSERT_EQ(reader->RecordCount(), 0);

    auto iter = reader->Iterator();
    ASSERT_FALSE(iter->HasNext());
}

TEST_F(BlockWriteReadTest, ResetAndRewrite) {
    BlockWriter writer(1024, pool_);

    auto key = MakeBytes("old_key");
    auto val = MakeBytes("old_val");
    ASSERT_OK(writer.Write(key, val));
    ASSERT_EQ(writer.Size(), 1);

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

    // Write new data and read back
    auto block = BuildBlock({{"new_key", "new_val"}}, &writer);
    ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_));
    ASSERT_EQ(reader->RecordCount(), 1);

    auto iter = reader->Iterator();
    ASSERT_OK_AND_ASSIGN(auto entry, iter->Next());
    ASSERT_EQ(entry.key.ReadStringView(), "new_key");
    ASSERT_EQ(entry.value.ReadStringView(), "new_val");
}

TEST_F(BlockWriteReadTest, MemoryAlignedVsUnaligned) {
    BlockWriter aligned_writer(1024, pool_);
    auto k1 = MakeBytes("aaaa");
    auto v1 = MakeBytes("1111");
    auto k2 = MakeBytes("bbbb");
    auto v2 = MakeBytes("2222");
    ASSERT_OK(aligned_writer.Write(k1, v1));
    ASSERT_OK(aligned_writer.Write(k2, v2));

    BlockWriter unaligned_writer(1024, pool_, /*aligned=*/false);
    auto k3 = MakeBytes("aaaa");
    auto v3 = MakeBytes("1111");
    auto k4 = MakeBytes("bbbb");
    auto v4 = MakeBytes("2222");
    ASSERT_OK(unaligned_writer.Write(k3, v3));
    ASSERT_OK(unaligned_writer.Write(k4, v4));

    // Unaligned memory should be larger due to position index overhead
    ASSERT_GT(unaligned_writer.Memory(), aligned_writer.Memory());
}

TEST_F(BlockWriteReadTest, SkipKeyAndReadValue) {
    BlockWriter writer(1024, pool_);
    auto block = BuildBlock({{"key1", "val1"}, {"key2", "val2"}}, &writer);

    ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_));
    auto iter = reader->Iterator();

    ASSERT_TRUE(iter->HasNext());
    ASSERT_OK_AND_ASSIGN(auto value1, iter->SkipKeyAndReadValue());
    ASSERT_EQ(value1.ReadStringView(), "val1");

    ASSERT_TRUE(iter->HasNext());
    ASSERT_OK_AND_ASSIGN(auto value2, iter->SkipKeyAndReadValue());
    ASSERT_EQ(value2.ReadStringView(), "val2");

    ASSERT_FALSE(iter->HasNext());
}

TEST_F(BlockWriteReadTest, IteratorSeekTo) {
    BlockWriter writer(1024, pool_);
    auto block =
        BuildBlock({{"apple", "1"}, {"banana", "2"}, {"cherry", "3"}, {"date", "4"}}, &writer);

    ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_));

    // Exact match
    {
        auto iter = reader->Iterator();
        auto target = MemorySlice::Wrap(MakeBytes("cherry"));
        ASSERT_OK_AND_ASSIGN(bool found, iter->SeekTo(target));
        ASSERT_TRUE(found);
        ASSERT_TRUE(iter->HasNext());
        ASSERT_OK_AND_ASSIGN(auto entry, iter->Next());
        ASSERT_EQ(entry.key.ReadStringView(), "cherry");
        ASSERT_EQ(entry.value.ReadStringView(), "3");
    }

    // No exact match: "blueberry" should land on "cherry" (first key >= target)
    {
        auto iter = reader->Iterator();
        auto target = MemorySlice::Wrap(MakeBytes("blueberry"));
        ASSERT_OK_AND_ASSIGN(bool found, iter->SeekTo(target));
        ASSERT_FALSE(found);
        ASSERT_TRUE(iter->HasNext());
        ASSERT_OK_AND_ASSIGN(auto entry, iter->Next());
        ASSERT_EQ(entry.key.ReadStringView(), "cherry");
    }
}

}  // namespace paimon::test
