/*
 * 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/predicate/predicate_validator.h"

#include "arrow/type_fwd.h"
#include "gtest/gtest.h"
#include "paimon/data/decimal.h"
#include "paimon/data/timestamp.h"
#include "paimon/predicate/predicate_builder.h"
#include "paimon/result.h"
#include "paimon/testing/utils/testharness.h"

namespace arrow {
class Schema;
}  // namespace arrow

namespace paimon::test {
TEST(PredicateValidatorTest, TestValidateLiterals) {
    std::string str("apple");
    {
        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
                PredicateBuilder::Equal(/*field_index=*/4, /*field_name=*/"f4", FieldType::DOUBLE,
                                        Literal(static_cast<double>(6.5))),
                PredicateBuilder::Equal(/*field_index=*/5, /*field_name=*/"f5", FieldType::TINYINT,
                                        Literal(static_cast<int8_t>(20))),
                PredicateBuilder::Equal(/*field_index=*/6, /*field_name=*/"f6", FieldType::DATE,
                                        Literal(FieldType::DATE, 3)),
                PredicateBuilder::Equal(/*field_index=*/7, /*field_name=*/"f7",
                                        FieldType::TIMESTAMP,
                                        Literal(Timestamp(1230422400000l, 123460))),
                PredicateBuilder::Equal(/*field_index=*/8, /*field_name=*/"f8", FieldType::DECIMAL,
                                        Literal(Decimal(23, 5, 123456))),
                PredicateBuilder::Equal(/*field_index=*/9, /*field_name=*/"f9", FieldType::BINARY,
                                        Literal(FieldType::BINARY, str.data(), str.size())),
            }));
        ASSERT_OK(PredicateValidator::ValidatePredicateWithLiterals(predicate));
    }
    {
        // f1 field type is FLOAT, literal type is BIGINT
        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(5l)),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));
        ASSERT_NOK_WITH_MSG(PredicateValidator::ValidatePredicateWithLiterals(predicate),
                            "field f1 has field type BIGINT in literal, mismatch "
                            "field type FLOAT in predicate");
    }
    {
        // f2 field type is STRING, literal type is BINARY
        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::BINARY, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));
        ASSERT_NOK_WITH_MSG(PredicateValidator::ValidatePredicateWithLiterals(predicate),
                            "field f2 has field type BINARY in literal, mismatch "
                            "field type STRING in predicate");
    }
    {
        // f2 literal is null
        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING)),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));
        ASSERT_NOK_WITH_MSG(PredicateValidator::ValidatePredicateWithLiterals(predicate),
                            "literal cannot be null in predicate, field name f2");
    }
}

TEST(PredicateValidatorTest, TestValidateSchema) {
    std::string str("apple");
    {
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::int64()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f2", arrow::utf8()),
            arrow::field("f3", arrow::boolean()),
            arrow::field("f4", arrow::date32()),
            arrow::field("f5", arrow::timestamp(arrow::TimeUnit::NANO)),
            arrow::field("f6", arrow::decimal128(23, 5)),
            arrow::field("f7", arrow::binary()),
            arrow::field("f8", arrow::int8()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
                PredicateBuilder::Equal(/*field_index=*/4, /*field_name=*/"f4", FieldType::DATE,
                                        Literal(FieldType::DATE, 3)),
                PredicateBuilder::Equal(/*field_index=*/5, /*field_name=*/"f5",
                                        FieldType::TIMESTAMP,
                                        Literal(Timestamp(1230422400000l, 123460))),
                PredicateBuilder::Equal(/*field_index=*/6, /*field_name=*/"f6", FieldType::DECIMAL,
                                        Literal(Decimal(23, 5, 123456))),
                PredicateBuilder::Equal(/*field_index=*/7, /*field_name=*/"f7", FieldType::BINARY,
                                        Literal(FieldType::BINARY, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/8, /*field_name=*/"f8", FieldType::TINYINT,
                                        Literal(static_cast<int8_t>(20))),
            }));
        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                                  /*validate_field_idx=*/true));
    }
    {
        // f2 schema type is DECIMAL(23,5), predicate type can be different precision and scale,
        // such as DECIMAL(22,4)
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::int16()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f2", arrow::decimal128(23, 5)),
            arrow::field("f3", arrow::boolean()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::SMALLINT,
                                        Literal(static_cast<int16_t>(3))),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::DECIMAL,
                                        Literal(Decimal(22, 4, 123456))),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));
        ASSERT_OK(PredicateValidator::ValidatePredicateWithLiterals(predicate));
        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                                  /*validate_field_idx=*/true));
    }
    {
        // predicate field idx mismatch
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::int64()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f2", arrow::utf8()),
            arrow::field("f3", arrow::boolean()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));

        ASSERT_NOK_WITH_MSG(
            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                            /*validate_field_idx=*/true),
            "field f1 has field idx 1 in input schema, mismatch "
            "field idx 2 in predicate");
    }
    {
        // predicate field idx mismatch, but not validate
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::int64()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f2", arrow::utf8()),
            arrow::field("f3", arrow::boolean()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));

        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                                  /*validate_field_idx=*/false));
    }
    {
        // f0 is uint16
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::uint16()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f2", arrow::utf8()),
            arrow::field("f3", arrow::boolean()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::SMALLINT,
                                        Literal(static_cast<int16_t>(3))),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));
        ASSERT_NOK_WITH_MSG(
            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                            /*validate_field_idx=*/true),
            "Invalid type uint16 for predicate");
    }
    {
        // f2 schema type is DOUBLE, predicate type is STRING
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::int64()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f2", arrow::float64()),
            arrow::field("f3", arrow::boolean()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));
        ASSERT_NOK_WITH_MSG(
            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                            /*validate_field_idx=*/true),
            "schema type double mismatches predicate field type STRING");
    }
    {
        // f2 schema type is BINARY, predicate type is STRING
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::int64()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f2", arrow::binary()),
            arrow::field("f3", arrow::boolean()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::BIGINT,
                                        Literal(3l)),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));
        ASSERT_NOK_WITH_MSG(
            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                            /*validate_field_idx=*/true),
            "schema type binary mismatches predicate field type STRING");
    }
    {
        // f2 in predicate does not exist in schema
        std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
            arrow::field("f0", arrow::int16()),
            arrow::field("f1", arrow::float32()),
            arrow::field("f3", arrow::boolean()),
        }));

        ASSERT_OK_AND_ASSIGN(
            auto predicate,
            PredicateBuilder::And({
                PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0", FieldType::SMALLINT,
                                        Literal(static_cast<int16_t>(3))),
                PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
                                        Literal(static_cast<float>(5.5))),
                PredicateBuilder::Equal(/*field_index=*/2, /*field_name=*/"f2", FieldType::STRING,
                                        Literal(FieldType::STRING, str.data(), str.size())),
                PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f3", FieldType::BOOLEAN,
                                        Literal(true)),
            }));

        ASSERT_NOK_WITH_MSG(
            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
                                                            /*validate_field_idx=*/true),
            "field f2 does not exist in schema");
    }
}
}  // namespace paimon::test
