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

#include <memory>

#include "gtest/gtest.h"
#include "paimon/testing/utils/testharness.h"
#include "paimon/testing/utils/timezone_guard.h"

namespace paimon::test {

TEST(DateTimeUtilsTest, TestTimestampConverter) {
    {
        auto ret = DateTimeUtils::TimestampConverter(
            10L, DateTimeUtils::SECOND, DateTimeUtils::MILLISECOND, DateTimeUtils::NANOSECOND);
        ASSERT_EQ(ret, std::make_pair(10000L, 0L));
    }
    {
        auto ret = DateTimeUtils::TimestampConverter(
            10L, DateTimeUtils::SECOND, DateTimeUtils::NANOSECOND, DateTimeUtils::NANOSECOND);
        ASSERT_EQ(ret, std::make_pair(10000000000L, 0L));
    }
    {
        auto ret = DateTimeUtils::TimestampConverter(
            10L, DateTimeUtils::SECOND, DateTimeUtils::NANOSECOND, DateTimeUtils::SECOND);
        ASSERT_EQ(ret, std::make_pair(10000000000L, 0L));
    }

    {
        auto ret = DateTimeUtils::TimestampConverter(
            2567L, DateTimeUtils::MILLISECOND, DateTimeUtils::SECOND, DateTimeUtils::NANOSECOND);
        ASSERT_EQ(ret, std::make_pair(2L, 567000000L));
    }
    {
        auto ret = DateTimeUtils::TimestampConverter(2567L, DateTimeUtils::MILLISECOND,
                                                     DateTimeUtils::SECOND, DateTimeUtils::SECOND);
        ASSERT_EQ(ret, std::make_pair(2L, 0L));
    }
    {
        auto ret = DateTimeUtils::TimestampConverter(
            2567L, DateTimeUtils::MILLISECOND, DateTimeUtils::MICROSECOND, DateTimeUtils::SECOND);
        ASSERT_EQ(ret, std::make_pair(2567000L, 0L));
    }
    {
        auto ret = DateTimeUtils::TimestampConverter(2567L, DateTimeUtils::MILLISECOND,
                                                     DateTimeUtils::MICROSECOND,
                                                     DateTimeUtils::NANOSECOND);
        ASSERT_EQ(ret, std::make_pair(2567000L, 0L));
    }

    {
        auto ret = DateTimeUtils::TimestampConverter(12345678L, DateTimeUtils::NANOSECOND,
                                                     DateTimeUtils::MILLISECOND,
                                                     DateTimeUtils::NANOSECOND);
        ASSERT_EQ(ret, std::make_pair(12L, 345678L));
    }
    {
        auto ret = DateTimeUtils::TimestampConverter(12345678L, DateTimeUtils::NANOSECOND,
                                                     DateTimeUtils::MILLISECOND,
                                                     DateTimeUtils::MICROSECOND);
        ASSERT_EQ(ret, std::make_pair(12L, 345L));
    }
    {
        auto ret =
            DateTimeUtils::TimestampConverter(12345678L, DateTimeUtils::NANOSECOND,
                                              DateTimeUtils::MILLISECOND, DateTimeUtils::SECOND);
        ASSERT_EQ(ret, std::make_pair(12L, 0L));
    }
    {
        auto ret = DateTimeUtils::TimestampConverter(
            -2240521239998998999L, DateTimeUtils::NANOSECOND, DateTimeUtils::MILLISECOND,
            DateTimeUtils::NANOSECOND);
        ASSERT_EQ(ret, std::make_pair(-2240521239999L, 1001L));
    }
    {
        // 9999-12-31
        auto ret = DateTimeUtils::TimestampConverter(253402252995L, DateTimeUtils::SECOND,
                                                     DateTimeUtils::MILLISECOND,
                                                     DateTimeUtils::NANOSECOND);
        ASSERT_EQ(ret, std::make_pair(253402252995000L, 0L));
    }
}

TEST(DateTimeUtilsTest, TestTimestampToInteger) {
    {
        ASSERT_EQ(DateTimeUtils::TimestampToInteger(Timestamp(1758173447000l, 0),
                                                    /*dst_type=*/DateTimeUtils::TimeType::SECOND),
                  1758173447l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(1758173447001l, 0),
                                              /*dst_type=*/DateTimeUtils::TimeType::MILLISECOND),
            1758173447001l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(1758173447001l, 1000),
                                              /*dst_type=*/DateTimeUtils::TimeType::MICROSECOND),
            1758173447001001l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(1758173447001l, 1001),
                                              /*dst_type=*/DateTimeUtils::TimeType::NANOSECOND),
            1758173447001001001l);
    }
    {
        ASSERT_EQ(DateTimeUtils::TimestampToInteger(Timestamp(-2493033748000l, 0),
                                                    /*dst_type=*/DateTimeUtils::TimeType::SECOND),
                  -2493033748l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(-2493033748001l, 0),
                                              /*dst_type=*/DateTimeUtils::TimeType::MILLISECOND),
            -2493033748001l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(-2493033748001l, 1000),
                                              /*dst_type=*/DateTimeUtils::TimeType::MICROSECOND),
            -2493033748000999l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(-2493033748001l, 1001),
                                              /*dst_type=*/DateTimeUtils::TimeType::NANOSECOND),
            -2493033748000998999l);
    }

    {
        // 9999-12-31, cannot convert to nano second, which is overflow for int64
        ASSERT_EQ(DateTimeUtils::TimestampToInteger(Timestamp(253402252995000l, 0),
                                                    /*dst_type=*/DateTimeUtils::TimeType::SECOND),
                  253402252995l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(253402252995001l, 0),
                                              /*dst_type=*/DateTimeUtils::TimeType::MILLISECOND),
            253402252995001l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(253402252995001l, 1000),
                                              /*dst_type=*/DateTimeUtils::TimeType::MICROSECOND),
            253402252995001001l);
    }
    {
        // 0000-01-01, cannot convert to nano second, which is overflow for int64
        ASSERT_EQ(DateTimeUtils::TimestampToInteger(Timestamp(-62167219200000l, 0),
                                                    /*dst_type=*/DateTimeUtils::TimeType::SECOND),
                  -62167219200l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(-62167219200000l, 0),
                                              /*dst_type=*/DateTimeUtils::TimeType::MILLISECOND),
            -62167219200000l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(-62167219200000l, 1000),
                                              /*dst_type=*/DateTimeUtils::TimeType::MICROSECOND),
            -62167219199999999l);
    }

    {
        // test precision loss
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(1758173447001l, 1001),
                                              /*dst_type=*/DateTimeUtils::TimeType::MICROSECOND),
            1758173447001001l);
        ASSERT_EQ(
            DateTimeUtils::TimestampToInteger(Timestamp(1758173447001l, 1001),
                                              /*dst_type=*/DateTimeUtils::TimeType::MILLISECOND),
            1758173447001l);
        ASSERT_EQ(DateTimeUtils::TimestampToInteger(Timestamp(1758173447001l, 1001),
                                                    /*dst_type=*/DateTimeUtils::TimeType::SECOND),
                  1758173447l);
    }
}

TEST(DateTimeUtilsTest, TestGetPrecisionFromType) {
    auto ts_sec_type = arrow::timestamp(arrow::TimeUnit::type::SECOND);
    auto ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_sec_type);
    ASSERT_EQ(DateTimeUtils::GetPrecisionFromType(ts_type), 0);

    auto ts_milli_type = arrow::timestamp(arrow::TimeUnit::type::MILLI);
    ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_milli_type);
    ASSERT_EQ(DateTimeUtils::GetPrecisionFromType(ts_type), 3);

    auto ts_micro_type = arrow::timestamp(arrow::TimeUnit::type::MICRO);
    ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_micro_type);
    ASSERT_EQ(DateTimeUtils::GetPrecisionFromType(ts_type), 6);

    auto ts_nano_type = arrow::timestamp(arrow::TimeUnit::type::NANO);
    ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_nano_type);
    ASSERT_EQ(DateTimeUtils::GetPrecisionFromType(ts_type), 9);
}

TEST(DateTimeUtilsTest, TestGetTimeTypeFromArrowType) {
    auto ts_sec_type = arrow::timestamp(arrow::TimeUnit::type::SECOND);
    auto ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_sec_type);
    ASSERT_EQ(DateTimeUtils::GetTimeTypeFromArrowType(ts_type), DateTimeUtils::TimeType::SECOND);

    auto ts_milli_type = arrow::timestamp(arrow::TimeUnit::type::MILLI);
    ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_milli_type);
    ASSERT_EQ(DateTimeUtils::GetTimeTypeFromArrowType(ts_type),
              DateTimeUtils::TimeType::MILLISECOND);

    auto ts_micro_type = arrow::timestamp(arrow::TimeUnit::type::MICRO);
    ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_micro_type);
    ASSERT_EQ(DateTimeUtils::GetTimeTypeFromArrowType(ts_type),
              DateTimeUtils::TimeType::MICROSECOND);

    auto ts_nano_type = arrow::timestamp(arrow::TimeUnit::type::NANO);
    ts_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(ts_nano_type);
    ASSERT_EQ(DateTimeUtils::GetTimeTypeFromArrowType(ts_type),
              DateTimeUtils::TimeType::NANOSECOND);
}

TEST(DateTimeUtilsTest, TestGetTypeFromPrecision) {
    auto timezone = DateTimeUtils::GetLocalTimezoneName();
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(0, /*with_timezone=*/false));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::SECOND)));
    }
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(0, /*with_timezone=*/true));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::SECOND, timezone)));
    }
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(3, /*with_timezone=*/false));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::MILLI)));
    }
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(3, /*with_timezone=*/true));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::MILLI, timezone)));
    }
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(6, /*with_timezone=*/false));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::MICRO)));
    }
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(6, /*with_timezone=*/true));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::MICRO, timezone)));
    }
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(9, /*with_timezone=*/false));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::NANO)));
    }
    {
        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> ts_type,
                             DateTimeUtils::GetTypeFromPrecision(9, /*with_timezone=*/true));
        ASSERT_TRUE(ts_type->Equals(arrow::timestamp(arrow::TimeUnit::type::NANO, timezone)));
    }
    {
        ASSERT_NOK_WITH_MSG(DateTimeUtils::GetTypeFromPrecision(4, /*with_timezone=*/true),
                            "only support precision 0/3/6/9 in timestamp type");
    }
}

TEST(DateTimeUtilsTest, TestGetLocalTimezoneName) {
    std::string timezone = DateTimeUtils::GetLocalTimezoneName();
    {
        TimezoneGuard guard("US/Hawaii");
        ASSERT_EQ(DateTimeUtils::GetLocalTimezoneName(), "US/Hawaii");
    }
    ASSERT_EQ(DateTimeUtils::GetLocalTimezoneName(), timezone);
}

TEST(DateTimeUtilsTest, TestGetCurrentLocalTimeUs) {
    TimezoneGuard guard("Asia/Shanghai");
    uint64_t utc_ts = DateTimeUtils::GetCurrentUTCTimeUs();
    uint64_t local_ts = DateTimeUtils::GetCurrentLocalTimeUs().value();
    ASSERT_GT(local_ts, utc_ts);
    ASSERT_GE(local_ts - utc_ts, 28800000000l);
}

TEST(DateTimeUtilsTest, TestToLocalTimestamp) {
    {
        TimezoneGuard guard("Asia/Shanghai");
        ASSERT_OK_AND_ASSIGN(Timestamp timestamp, DateTimeUtils::ToLocalTimestamp(
                                                      Timestamp::FromEpochMillis(1700000000123L)));
        ASSERT_EQ(timestamp, Timestamp::FromEpochMillis(1700028800123L));
    }
    {
        TimezoneGuard guard("UTC");
        ASSERT_OK_AND_ASSIGN(Timestamp timestamp, DateTimeUtils::ToLocalTimestamp(
                                                      Timestamp::FromEpochMillis(1700000000123L)));
        ASSERT_EQ(timestamp, Timestamp::FromEpochMillis(1700000000123L));
    }
}

TEST(DateTimeUtilsTest, TestGetCurrentLocalHour) {
    int32_t shanghai_hour = 0;
    int32_t utc_hour = 0;
    {
        TimezoneGuard guard("Asia/Shanghai");
        ASSERT_OK_AND_ASSIGN(shanghai_hour, DateTimeUtils::GetCurrentLocalHour());
    }
    {
        TimezoneGuard guard("UTC");
        ASSERT_OK_AND_ASSIGN(utc_hour, DateTimeUtils::GetCurrentLocalHour());
    }
    ASSERT_EQ((shanghai_hour - utc_hour + 24) % 24, 8);
}

TEST(DateTimeUtilsTest, TestToUTCTimestamp) {
    TimezoneGuard guard("Asia/Shanghai");
    {
        Timestamp ts(0, 0);
        ASSERT_OK_AND_ASSIGN(Timestamp utc_ts, DateTimeUtils::ToUTCTimestamp(ts));
        ASSERT_EQ(utc_ts, Timestamp(-28800000l, 0));
    }
    {
        // test precision loss for nano
        Timestamp ts(0, 500);
        ASSERT_OK_AND_ASSIGN(Timestamp utc_ts, DateTimeUtils::ToUTCTimestamp(ts));
        ASSERT_EQ(utc_ts, Timestamp(-28800000l, 0));
    }
}
TEST(DateTimeUtilsTest, TestGetArrowTimeUnitStr) {
    ASSERT_EQ(DateTimeUtils::GetArrowTimeUnitStr(arrow::TimeUnit::SECOND), "SECOND");
    ASSERT_EQ(DateTimeUtils::GetArrowTimeUnitStr(arrow::TimeUnit::MILLI), "MILLISECOND");
    ASSERT_EQ(DateTimeUtils::GetArrowTimeUnitStr(arrow::TimeUnit::MICRO), "MICROSECOND");
    ASSERT_EQ(DateTimeUtils::GetArrowTimeUnitStr(arrow::TimeUnit::NANO), "NANOSECOND");
}

}  // namespace paimon::test
