/*
 * 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/format/parquet/parquet_writer_builder.h"

#include <limits>

#include "arrow/type_fwd.h"
#include "arrow/util/type_fwd.h"
#include "gtest/gtest.h"
#include "paimon/defs.h"
#include "paimon/format/parquet/parquet_format_defs.h"
#include "paimon/status.h"
#include "paimon/testing/utils/testharness.h"
#include "parquet/properties.h"
#include "parquet/type_fwd.h"

namespace arrow {
class Schema;
}  // namespace arrow

namespace paimon::parquet::test {

TEST(ParquetWriterBuilderTest, DefaultPrepareWriterProperties) {
    arrow::FieldVector fields;
    std::shared_ptr<arrow::Schema> schema = arrow::schema(fields);
    std::map<std::string, std::string> options;
    options[Options::FILE_FORMAT] = "parquet";
    options[Options::MANIFEST_FORMAT] = "parquet";
    ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
    ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("zstd"));
    ASSERT_EQ(::parquet::ParquetVersion::PARQUET_2_6, properties->version());
    ASSERT_EQ(1024 * 1024, properties->dictionary_pagesize_limit());
    ASSERT_EQ(1024 * 1024, properties->data_pagesize());
    ASSERT_EQ(std::numeric_limits<int64_t>::max(), properties->max_row_group_length());
    ASSERT_EQ(128 * 1024 * 1024, properties->max_row_group_size());
    ASSERT_EQ(1024, properties->write_batch_size());
    ASSERT_EQ(1, properties->default_column_properties().compression_level());
    ASSERT_TRUE(properties->store_decimal_as_integer());
}

TEST(ParquetWriterBuilderTest, PrepareWriterProperties) {
    arrow::FieldVector fields;
    std::shared_ptr<arrow::Schema> schema = arrow::schema(fields);
    std::map<std::string, std::string> options;
    options[PARQUET_PAGE_SIZE] = "1024";
    options[PARQUET_DICTIONARY_PAGE_SIZE] = "4096";
    options[PARQUET_WRITER_VERSION] = "PARQUET_2_0";
    options[PARQUET_COMPRESSION_CODEC_ZSTD_LEVEL] = "3";
    options[PARQUET_BLOCK_SIZE] = "2048";
    options[Options::FILE_FORMAT] = "parquet";
    options[Options::MANIFEST_FORMAT] = "parquet";
    ParquetWriterBuilder builder(schema, /*batch_size=*/1024 * 1024, options);
    ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("zstd"));
    // Version numbering differs between C++ and Java Parquet implementations. Java's PARQUET_2_0
    // format version corresponds to C++'s PARQUET_2_6 enum value.
    ASSERT_EQ(::parquet::ParquetVersion::PARQUET_2_6, properties->version());
    ASSERT_EQ(4096, properties->dictionary_pagesize_limit());
    ASSERT_EQ(1024, properties->data_pagesize());
    ASSERT_EQ(2048, properties->max_row_group_size());
    ASSERT_EQ(1024 * 1024, properties->write_batch_size());
    ASSERT_EQ(3, properties->default_column_properties().compression_level());
}

TEST(ParquetWriterBuilderTest, PrepareWriterPropertiesWithZstdLevelPriority) {
    arrow::FieldVector fields;
    std::shared_ptr<arrow::Schema> schema = arrow::schema(fields);
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_COMPRESSION_ZSTD_LEVEL] = "4";
        options[PARQUET_COMPRESSION_CODEC_ZSTD_LEVEL] = "3";
        options[PARQUET_WRITER_VERSION] = "PARQUET_1_0";
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024 * 1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("zstd"));
        ASSERT_EQ(3, properties->default_column_properties().compression_level());
        ASSERT_EQ(::parquet::ParquetVersion::PARQUET_1_0, properties->version());
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_COMPRESSION_ZSTD_LEVEL] = "4";
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024 * 1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("zstd"));
        ASSERT_EQ(4, properties->default_column_properties().compression_level());
    }
}

TEST(ParquetWriterBuilderTest, TestPrepareWriterPropertiesFileCompression) {
    arrow::FieldVector fields;
    std::shared_ptr<arrow::Schema> schema = arrow::schema(fields);
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("lz4"));
        ASSERT_EQ(properties->default_column_properties().compression(),
                  arrow::Compression::LZ4_FRAME);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("lz4_raW"));
        ASSERT_EQ(properties->default_column_properties().compression(), arrow::Compression::LZ4);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("zstd"));
        ASSERT_EQ(properties->default_column_properties().compression(), arrow::Compression::ZSTD);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("LZ4"));
        ASSERT_EQ(properties->default_column_properties().compression(),
                  arrow::Compression::LZ4_FRAME);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("ZSTd"));
        ASSERT_EQ(properties->default_column_properties().compression(), arrow::Compression::ZSTD);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        options[PARQUET_COMPRESSION_CODEC_ZLIB_LEVEL] = "3";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("gzip"));
        ASSERT_EQ(3, properties->default_column_properties().compression_level());
        ASSERT_EQ(properties->default_column_properties().compression(), arrow::Compression::GZIP);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("snappy"));
        ASSERT_EQ(properties->default_column_properties().compression(),
                  arrow::Compression::SNAPPY);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("lzo"));
        ASSERT_EQ(properties->default_column_properties().compression(), arrow::Compression::LZO);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_NOK(builder.PrepareWriterProperties("unknown"));
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("uncompressed"));
        ASSERT_EQ(properties->default_column_properties().compression(),
                  arrow::Compression::UNCOMPRESSED);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("lz4_hadoop"));
        ASSERT_EQ(properties->default_column_properties().compression(),
                  arrow::Compression::LZ4_HADOOP);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        options[PARQUET_COMPRESSION_CODEC_BROTLI_LEVEL] = "2";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("brotli"));
        ASSERT_EQ(2, properties->default_column_properties().compression_level());
        ASSERT_EQ(properties->default_column_properties().compression(),
                  arrow::Compression::BROTLI);
    }
    {
        std::map<std::string, std::string> options;
        options[Options::FILE_FORMAT] = "parquet";
        options[Options::MANIFEST_FORMAT] = "parquet";
        ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
        ASSERT_OK_AND_ASSIGN(auto properties, builder.PrepareWriterProperties("None"));
        ASSERT_EQ(properties->default_column_properties().compression(),
                  arrow::Compression::UNCOMPRESSED);
    }
}

TEST(ParquetWriterBuilderTest, TestInvalidWriterVersion) {
    arrow::FieldVector fields;
    std::shared_ptr<arrow::Schema> schema = arrow::schema(fields);
    std::map<std::string, std::string> options;
    options[Options::FILE_FORMAT] = "parquet";
    options[Options::MANIFEST_FORMAT] = "parquet";
    options[PARQUET_WRITER_VERSION] = "PARQUET_3_0";
    ParquetWriterBuilder builder(schema, /*batch_size=*/1024, options);
    ASSERT_NOK_WITH_MSG(builder.PrepareWriterProperties("zstd"),
                        "Unknown writer version PARQUET_3_0");
}

}  // namespace paimon::parquet::test
