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

#include <utility>

#include "gtest/gtest.h"
#include "paimon/common/utils/decimal_utils.h"
#include "paimon/result.h"

namespace paimon::test {

TEST(ColumnStatsTest, TestBooleanColumnStats) {
    auto stats = ColumnStats::CreateBooleanColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<BooleanColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(true);
    ASSERT_EQ(true, typed_stats->Min().value());
    ASSERT_EQ(true, typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(true, typed_stats->Min().value());
    ASSERT_EQ(true, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min true, max true, null count 2", stats->ToString());
}

TEST(ColumnStatsTest, TestTinyIntColumnStats) {
    auto stats = ColumnStats::CreateTinyIntColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<TinyIntColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(5);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(5, typed_stats->Max().value());
    typed_stats->Collect(10);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    typed_stats->Collect(1);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1, max 10, null count 2", stats->ToString());
}
TEST(ColumnStatsTest, TestSmallIntColumnStats) {
    auto stats = ColumnStats::CreateSmallIntColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<SmallIntColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(5);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(5, typed_stats->Max().value());
    typed_stats->Collect(10);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    typed_stats->Collect(1);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1, max 10, null count 2", stats->ToString());
}

TEST(ColumnStatsTest, TestIntColumnStats) {
    auto stats = ColumnStats::CreateIntColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<IntColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(5);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(5, typed_stats->Max().value());
    typed_stats->Collect(10);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    typed_stats->Collect(1);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1, max 10, null count 2", stats->ToString());
}
TEST(ColumnStatsTest, TestDateColumnStats) {
    auto stats = ColumnStats::CreateDateColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<DateColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(5);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(5, typed_stats->Max().value());
    typed_stats->Collect(10);
    ASSERT_EQ(5, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    typed_stats->Collect(1);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(1, typed_stats->Min().value());
    ASSERT_EQ(10, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1, max 10, null count 2", stats->ToString());
}
TEST(ColumnStatsTest, TestBigIntColumnStats) {
    auto stats = ColumnStats::CreateBigIntColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<BigIntColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1l, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(5l);
    ASSERT_EQ(5l, typed_stats->Min().value());
    ASSERT_EQ(5l, typed_stats->Max().value());
    typed_stats->Collect(10l);
    ASSERT_EQ(5l, typed_stats->Min().value());
    ASSERT_EQ(10l, typed_stats->Max().value());
    typed_stats->Collect(1l);
    ASSERT_EQ(1l, typed_stats->Min().value());
    ASSERT_EQ(10l, typed_stats->Max().value());
    ASSERT_EQ(1l, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(1l, typed_stats->Min().value());
    ASSERT_EQ(10l, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1, max 10, null count 2", stats->ToString());
}
TEST(ColumnStatsTest, TestFloatColumnStats) {
    auto stats = ColumnStats::CreateFloatColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<FloatColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(5.1f);
    ASSERT_EQ(5.1f, typed_stats->Min().value());
    ASSERT_EQ(5.1f, typed_stats->Max().value());
    typed_stats->Collect(10.1f);
    ASSERT_EQ(5.1f, typed_stats->Min().value());
    ASSERT_EQ(10.1f, typed_stats->Max().value());
    typed_stats->Collect(1.1f);
    ASSERT_EQ(1.1f, typed_stats->Min().value());
    ASSERT_EQ(10.1f, typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(1.1f, typed_stats->Min().value());
    ASSERT_EQ(10.1f, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1.1, max 10.1, null count 2", stats->ToString());
}

TEST(ColumnStatsTest, TestDoubleColumnStats) {
    auto stats = ColumnStats::CreateDoubleColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<DoubleColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(5.12);
    ASSERT_EQ(5.12, typed_stats->Min().value());
    ASSERT_EQ(5.12, typed_stats->Max().value());
    typed_stats->Collect(10.12);
    ASSERT_EQ(5.12, typed_stats->Min().value());
    ASSERT_EQ(10.12, typed_stats->Max().value());
    typed_stats->Collect(1.12);
    ASSERT_EQ(1.12, typed_stats->Min().value());
    ASSERT_EQ(10.12, typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(1.12, typed_stats->Min().value());
    ASSERT_EQ(10.12, typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1.12, max 10.12, null count 2", stats->ToString());
}

TEST(ColumnStatsTest, TestStringColumnStats) {
    auto stats = ColumnStats::CreateStringColumnStats(std::nullopt, std::nullopt, std::nullopt);
    auto typed_stats = dynamic_cast<StringColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect("abc");
    ASSERT_EQ("abc", typed_stats->Min().value());
    ASSERT_EQ("abc", typed_stats->Max().value());
    typed_stats->Collect("cba");
    ASSERT_EQ("abc", typed_stats->Min().value());
    ASSERT_EQ("cba", typed_stats->Max().value());
    typed_stats->Collect("你好");
    ASSERT_EQ("abc", typed_stats->Min().value());
    ASSERT_EQ("你好", typed_stats->Max().value());
    ASSERT_EQ(1, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ("abc", typed_stats->Min().value());
    ASSERT_EQ("你好", typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min abc, max 你好, null count 2", stats->ToString());
}

TEST(ColumnStatsTest, TestTimestampColumnStats) {
    auto stats = ColumnStats::CreateTimestampColumnStats(std::nullopt, std::nullopt, std::nullopt,
                                                         /*precision=*/9);
    auto typed_stats = dynamic_cast<TimestampColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1l, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/1));
    ASSERT_EQ(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/1), typed_stats->Min().value());
    ASSERT_EQ(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/1), typed_stats->Max().value());
    typed_stats->Collect(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/2));
    ASSERT_EQ(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/1), typed_stats->Min().value());
    ASSERT_EQ(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/2), typed_stats->Max().value());
    typed_stats->Collect(Timestamp(/*millisecond=*/9, /*nano_of_millisecond=*/1));
    ASSERT_EQ(Timestamp(/*millisecond=*/9, /*nano_of_millisecond=*/1), typed_stats->Min().value());
    ASSERT_EQ(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/2), typed_stats->Max().value());
    ASSERT_EQ(1l, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(Timestamp(/*millisecond=*/9, /*nano_of_millisecond=*/1), typed_stats->Min().value());
    ASSERT_EQ(Timestamp(/*millisecond=*/10, /*nano_of_millisecond=*/2), typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 1970-01-01 00:00:00.009000001, max 1970-01-01 00:00:00.010000002, null count 2",
              stats->ToString());
}

TEST(ColumnStatsTest, TestDecimalColumnStats) {
    int32_t precision = 21;
    int32_t scale = 3;
    auto stats = ColumnStats::CreateDecimalColumnStats(std::nullopt, std::nullopt, std::nullopt,
                                                       precision, scale);
    auto typed_stats = dynamic_cast<DecimalColumnStats*>(stats.get());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(std::nullopt, typed_stats->Min());
    ASSERT_EQ(std::nullopt, typed_stats->Max());
    ASSERT_EQ(1l, typed_stats->NullCount().value());
    ASSERT_EQ("min null, max null, null count 1", stats->ToString());
    typed_stats->Collect(
        Decimal(precision, scale, DecimalUtils::StrToInt128("123456789987654321234").value()));
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("123456789987654321234").value()),
              typed_stats->Min().value());
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("123456789987654321234").value()),
              typed_stats->Max().value());
    typed_stats->Collect(
        Decimal(precision, scale, DecimalUtils::StrToInt128("923456789987654321234").value()));
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("123456789987654321234").value()),
              typed_stats->Min().value());
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("923456789987654321234").value()),
              typed_stats->Max().value());
    typed_stats->Collect(
        Decimal(precision, scale, DecimalUtils::StrToInt128("123456789987654321233").value()));
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("123456789987654321233").value()),
              typed_stats->Min().value());
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("923456789987654321234").value()),
              typed_stats->Max().value());
    ASSERT_EQ(1l, typed_stats->NullCount().value());
    typed_stats->Collect(std::nullopt);
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("123456789987654321233").value()),
              typed_stats->Min().value());
    ASSERT_EQ(Decimal(precision, scale, DecimalUtils::StrToInt128("923456789987654321234").value()),
              typed_stats->Max().value());
    ASSERT_EQ(2, typed_stats->NullCount().value());
    ASSERT_EQ("min 123456789987654321.233, max 923456789987654321.234, null count 2",
              stats->ToString());
}

}  // namespace paimon::test
