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

#include <memory>
#include <numeric>
#include <vector>

#include "arrow/api.h"
#include "arrow/c/bridge.h"
#include "paimon/common/factories/io_hook.h"
#include "paimon/common/file_index/rangebitmap/range_bitmap_file_index.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/scope_guard.h"
#include "paimon/file_index/bitmap_index_result.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/predicate/literal.h"
#include "paimon/testing/utils/io_exception_helper.h"
#include "paimon/testing/utils/testharness.h"
#include "paimon/utils/roaring_bitmap32.h"

namespace paimon::test {

class RangeBitmapIoTest : public ::testing::Test {
 public:
    void SetUp() override {
        dir_ = paimon::test::UniqueTestDirectory::Create();
        fs_ = dir_->GetFileSystem();
        pool_ = GetDefaultPool();
    }

    void TearDown() override {
        ASSERT_OK(fs_->Delete(dir_->Str()));
    }

    // Helper function to create writer, write data and return serialized bytes
    template <typename ArrowBuilder, typename ValueType>
    Result<PAIMON_UNIQUE_PTR<Bytes>> CreateIndexData(
        const std::shared_ptr<arrow::DataType>& arrow_type,
        const std::vector<ValueType>& test_data) {
        // Create Arrow array from test data
        auto builder = std::make_shared<ArrowBuilder>();
        for (const auto& value : test_data) {
            PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
        }
        std::shared_ptr<arrow::Array> arrow_array;
        PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Finish(&arrow_array));

        // Wrap in StructArray
        arrow::FieldVector fields = {arrow::field("test_field", arrow_type)};
        PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::StructArray> struct_array,
                                          arrow::StructArray::Make({arrow_array}, fields));
        auto c_array = std::make_unique<::ArrowArray>();
        PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*struct_array, c_array.get()));

        // Create schema
        const auto schema = arrow::schema({arrow::field("test_field", arrow_type)});

        // Create writer and write data
        PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<RangeBitmapFileIndexWriter> writer,
                               RangeBitmapFileIndexWriter::Create(schema, "test_field", {}, pool_));

        PAIMON_RETURN_NOT_OK(writer->AddBatch(c_array.get()));
        return writer->SerializedBytes();
    }

 protected:
    std::unique_ptr<paimon::test::UniqueTestDirectory> dir_;
    std::shared_ptr<paimon::FileSystem> fs_;
    std::shared_ptr<MemoryPool> pool_;
};

// Test normal read/write operations
TEST_F(RangeBitmapIoTest, TestSimple) {
    // Prepare test data
    std::vector<int32_t> test_data = {10, 20, 30, 40, 50};
    const auto& arrow_type = arrow::int32();

    // Create index data
    ASSERT_OK_AND_ASSIGN(PAIMON_UNIQUE_PTR<Bytes> serialized_bytes,
                         (CreateIndexData<arrow::Int32Builder, int32_t>(arrow_type, test_data)));
    ASSERT_TRUE(serialized_bytes);
    ASSERT_GT(serialized_bytes->size(), 0);

    // Write to file
    std::string file_path = dir_->Str() + "/range_bitmap_test.data";
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
                         fs_->Create(file_path, /*overwrite=*/false));
    ASSERT_OK_AND_ASSIGN(
        int64_t write_len,
        out->Write(reinterpret_cast<char*>(serialized_bytes->data()), serialized_bytes->size()));
    ASSERT_EQ(write_len, serialized_bytes->size());
    ASSERT_OK(out->Flush());
    ASSERT_OK(out->Close());

    // Read from file and create reader
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in, fs_->Open(file_path));
    ASSERT_OK_AND_ASSIGN(
        std::shared_ptr<RangeBitmapFileIndexReader> reader,
        RangeBitmapFileIndexReader::Create(arrow_type, 0, serialized_bytes->size(), in, pool_));

    // Test equality query
    ASSERT_OK_AND_ASSIGN(auto eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(30))));
    auto bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(const RoaringBitmap32* bitmap_result, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap_result);
    // Value 30 is at position 2
    ASSERT_TRUE(bitmap_result->Contains(2));
    ASSERT_FALSE(bitmap_result->Contains(0));
    ASSERT_FALSE(bitmap_result->Contains(1));

    // Test range query
    ASSERT_OK_AND_ASSIGN(auto gt_result,
                         reader->VisitGreaterThan(Literal(static_cast<int32_t>(25))));
    bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(gt_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(bitmap_result, bitmap_typed_result->GetBitmap());
    // Values > 25: 30, 40, 50 at positions 2, 3, 4
    ASSERT_TRUE(bitmap_result->Contains(2));
    ASSERT_TRUE(bitmap_result->Contains(3));
    ASSERT_TRUE(bitmap_result->Contains(4));
    ASSERT_FALSE(bitmap_result->Contains(0));
    ASSERT_FALSE(bitmap_result->Contains(1));

    ASSERT_OK(in->Close());
}

// Test I/O exceptions using IOHook
TEST_F(RangeBitmapIoTest, TestIOException) {
    bool run_complete = false;
    auto io_hook = paimon::IOHook::GetInstance();

    for (size_t i = 0; i < 200; i++) {
        auto test_dir = paimon::test::UniqueTestDirectory::Create();
        ASSERT_TRUE(test_dir);
        paimon::ScopeGuard guard([&io_hook]() { io_hook->Clear(); });
        io_hook->Reset(i, paimon::IOHook::Mode::RETURN_ERROR);

        // Prepare test data
        std::vector<int32_t> test_data = {10, 20, 30, 40, 50};
        const auto& arrow_type = arrow::int32();

        // Create index data
        auto serialized_bytes_result =
            CreateIndexData<arrow::Int32Builder, int32_t>(arrow_type, test_data);
        CHECK_HOOK_STATUS(serialized_bytes_result.status(), i);
        PAIMON_UNIQUE_PTR<Bytes> serialized_bytes = std::move(serialized_bytes_result).value();

        // Write to file
        std::string file_path = test_dir->Str() + "/range_bitmap_io_exception_test.data";

        auto out_result = fs_->Create(file_path, /*overwrite=*/false);
        CHECK_HOOK_STATUS(out_result.status(), i);
        std::shared_ptr<OutputStream> out = std::move(out_result).value();

        auto write_result =
            out->Write(reinterpret_cast<char*>(serialized_bytes->data()), serialized_bytes->size());
        CHECK_HOOK_STATUS(write_result.status(), i);

        CHECK_HOOK_STATUS(out->Flush(), i);
        CHECK_HOOK_STATUS(out->Close(), i);

        // Read from file and create reader
        auto in_result = fs_->Open(file_path);
        CHECK_HOOK_STATUS(in_result.status(), i);
        std::shared_ptr<InputStream> in = std::move(in_result).value();

        auto reader_result =
            RangeBitmapFileIndexReader::Create(arrow_type, 0, serialized_bytes->size(), in, pool_);
        CHECK_HOOK_STATUS(reader_result.status(), i);
        std::shared_ptr<RangeBitmapFileIndexReader> reader = std::move(reader_result).value();

        // Test query
        auto eq_result = reader->VisitEqual(Literal(static_cast<int32_t>(30)));
        CHECK_HOOK_STATUS(eq_result.status(), i);
        auto bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result.value());
        ASSERT_TRUE(bitmap_typed_result);
        auto bitmap_result = bitmap_typed_result->GetBitmap();
        CHECK_HOOK_STATUS(bitmap_result.status(), i);
        ASSERT_TRUE(bitmap_result.value()->Contains(2));  // Value 30 is at position 2

        run_complete = true;
        break;
    }
    ASSERT_TRUE(run_complete);
}

// Test Java compatibility - read java-generated index file
// data: [1, 3, 5, 7, 9, null, null, 10]
TEST_F(RangeBitmapIoTest, TestJavaCompatibility) {
    // Load pre-generated range bitmap index file
    std::string index_file = GetDataDir() + "/file_index/rangebitmap/rangebitmap.index";
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in, fs_->Open(index_file));
    ASSERT_OK_AND_ASSIGN(int64_t file_size, in->Length());
    ASSERT_GT(file_size, 0);

    // Create reader from the index file (int32 type)
    const auto& arrow_type = arrow::int32();
    ASSERT_OK_AND_ASSIGN(std::shared_ptr<RangeBitmapFileIndexReader> reader,
                         RangeBitmapFileIndexReader::Create(
                             arrow_type, 0, static_cast<int32_t>(file_size), in, pool_));

    // Test equality query - value 1 at position 0
    ASSERT_OK_AND_ASSIGN(auto eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(1))));
    auto bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(const RoaringBitmap32* bitmap, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap);
    ASSERT_TRUE(bitmap->Contains(0));  // value 1 at position 0
    ASSERT_FALSE(bitmap->Contains(1));

    // Test equality query - value 5 at position 2
    ASSERT_OK_AND_ASSIGN(eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(5))));
    bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap);
    ASSERT_TRUE(bitmap->Contains(2));  // value 5 at position 2

    // Test equality query - value 10 at position 7
    ASSERT_OK_AND_ASSIGN(eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(10))));
    bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap);
    ASSERT_TRUE(bitmap->Contains(7));  // value 10 at position 7

    // Test greater than query - values > 5: 7, 9, 10 at positions 3, 4, 7
    ASSERT_OK_AND_ASSIGN(auto gt_result,
                         reader->VisitGreaterThan(Literal(static_cast<int32_t>(5))));
    bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(gt_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap);
    ASSERT_TRUE(bitmap->Contains(3));   // 7
    ASSERT_TRUE(bitmap->Contains(4));   // 9
    ASSERT_TRUE(bitmap->Contains(7));   // 10
    ASSERT_FALSE(bitmap->Contains(0));  // 1
    ASSERT_FALSE(bitmap->Contains(2));  // 5

    // Test less than query - values < 5: 1, 3 at positions 0, 1
    ASSERT_OK_AND_ASSIGN(auto lt_result, reader->VisitLessThan(Literal(static_cast<int32_t>(5))));
    bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(lt_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap);
    ASSERT_TRUE(bitmap->Contains(0));   // 1
    ASSERT_TRUE(bitmap->Contains(1));   // 3
    ASSERT_FALSE(bitmap->Contains(2));  // 5
    ASSERT_FALSE(bitmap->Contains(7));  // 10

    // Test is_not_null query - non-null positions: 0, 1, 2, 3, 4, 7
    ASSERT_OK_AND_ASSIGN(auto not_null_result, reader->VisitIsNotNull());
    bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(not_null_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap);
    ASSERT_TRUE(bitmap->Contains(0));
    ASSERT_TRUE(bitmap->Contains(1));
    ASSERT_TRUE(bitmap->Contains(2));
    ASSERT_TRUE(bitmap->Contains(3));
    ASSERT_TRUE(bitmap->Contains(4));
    ASSERT_TRUE(bitmap->Contains(7));
    ASSERT_FALSE(bitmap->Contains(5));  // null
    ASSERT_FALSE(bitmap->Contains(6));  // null

    // Test is_null query - null positions: 5, 6
    ASSERT_OK_AND_ASSIGN(auto null_result, reader->VisitIsNull());
    bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(null_result);
    ASSERT_TRUE(bitmap_typed_result);
    ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
    ASSERT_TRUE(bitmap);
    ASSERT_TRUE(bitmap->Contains(5));  // null at position 5
    ASSERT_TRUE(bitmap->Contains(6));  // null at position 6
    ASSERT_FALSE(bitmap->Contains(0));
    ASSERT_FALSE(bitmap->Contains(7));

    ASSERT_OK(in->Close());
}

}  // namespace paimon::test
