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

#include <memory>
#include <utility>

#include "gtest/gtest.h"
#include "paimon/common/utils/decimal_utils.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/io/byte_array_input_stream.h"
#include "paimon/io/data_input_stream.h"
#include "paimon/memory/bytes.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/result.h"
#include "paimon/status.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {
TEST(DecimalTest, TestSimple) {
    auto CheckResult = [](const Decimal& decimal, const std::vector<uint8_t>& bytes) {
        auto pool = GetDefaultPool();
        // prepare java bytes
        auto java_bytes = Bytes::AllocateBytes(bytes.size(), pool.get());
        memcpy(java_bytes->data(), bytes.data(), bytes.size());
        // prepare result cpp bytes
        auto cpp_serialized_bytes = decimal.ToUnscaledBytes();

        // check serialized bytes equal
        ASSERT_EQ(std::vector<char>(java_bytes->data(), java_bytes->data() + java_bytes->size()),
                  cpp_serialized_bytes);
        // check deserialize equal
        auto decimal2 = Decimal::FromUnscaledBytes(38, 38, java_bytes.get());
        ASSERT_EQ(decimal, decimal2);
    };
    {
        Decimal decimal(38, 38, DecimalUtils::StrToInt128("12345678998765432145678").value());
        std::vector<uint8_t> java_bytes = {0x2, 0x9d, 0x42, 0xb6, 0xa7, 0x2a, 0x9d, 0xc7, 0x7, 0xe};
        CheckResult(decimal, java_bytes);
    }
    {
        Decimal decimal(38, 38, DecimalUtils::StrToInt128("-12345678998765432145678").value());
        std::vector<uint8_t> java_bytes = {0xfd, 0x62, 0xbd, 0x49, 0x58,
                                           0xd5, 0x62, 0x38, 0xf8, 0xf2};
        CheckResult(decimal, java_bytes);
    }
    {
        Decimal decimal(38, 38, DecimalUtils::StrToInt128("0").value());
        std::vector<uint8_t> java_bytes = {0x0};
        CheckResult(decimal, java_bytes);
    }
    {
        Decimal decimal(38, 38, DecimalUtils::StrToInt128("1").value());
        std::vector<uint8_t> java_bytes = {0x1};
        CheckResult(decimal, java_bytes);
    }
    {
        Decimal decimal(38, 38, DecimalUtils::StrToInt128("-1").value());
        std::vector<uint8_t> java_bytes = {0xff};
        CheckResult(decimal, java_bytes);
    }
    {
        Decimal decimal(38, 38, DecimalUtils::StrToInt128("128").value());
        std::vector<uint8_t> java_bytes = {0x0, 0x80};
        CheckResult(decimal, java_bytes);
    }
    {
        Decimal decimal(38, 38, Decimal::INT128_MINIMUM_VALUE);
        std::vector<uint8_t> java_bytes = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        CheckResult(decimal, java_bytes);
    }
    {
        Decimal decimal(38, 38, Decimal::INT128_MAXIMUM_VALUE);
        std::vector<uint8_t> java_bytes = {0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                                           0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
        CheckResult(decimal, java_bytes);
    }
}

TEST(DecimalTest, TestCompatibleWithJava) {
    auto pool = GetDefaultPool();
    auto file_system = std::make_unique<LocalFileSystem>();
    auto file_name = paimon::test::GetDataDir() + "/decimal_bytes.data";
    int64_t file_length = file_system->GetFileStatus(file_name).value()->GetLen();
    ASSERT_GT(file_length, 0);
    ASSERT_OK_AND_ASSIGN(auto input_stream, file_system->Open(file_name));
    auto data_bytes = Bytes::AllocateBytes(file_length, pool.get());
    ASSERT_OK(input_stream->Read(data_bytes->data(), file_length));
    auto byte_array_input_stream =
        std::make_shared<ByteArrayInputStream>(data_bytes->data(), file_length);
    DataInputStream data_input_stream(byte_array_input_stream);
    for (int32_t i = 0; i < 2000; i++) {
        // read decimal str
        ASSERT_OK_AND_ASSIGN(int32_t decimal_str_len, data_input_stream.ReadValue<int32_t>());
        auto decimal_str = Bytes::AllocateBytes(decimal_str_len, pool.get());
        ASSERT_OK(data_input_stream.ReadBytes(decimal_str.get()));

        // read decimal serialized bytes from java
        ASSERT_OK_AND_ASSIGN(int32_t decimal_bytes_len, data_input_stream.ReadValue<int32_t>());
        auto decimal_bytes = Bytes::AllocateBytes(decimal_bytes_len, pool.get());
        ASSERT_OK(data_input_stream.ReadBytes(decimal_bytes.get()));

        // check result
        auto str = std::string(decimal_str->data(), decimal_str->size());
        Decimal decimal(/*precision=*/29, /*scale=*/29, DecimalUtils::StrToInt128(str).value());
        auto serialized_bytes = decimal.ToUnscaledBytes();
        ASSERT_EQ(
            std::vector<char>(decimal_bytes->data(), decimal_bytes->data() + decimal_bytes->size()),
            serialized_bytes);
        auto decimal2 =
            Decimal::FromUnscaledBytes(/*precision=*/29, /*scale=*/29, decimal_bytes.get());
        ASSERT_EQ(decimal, decimal2);
    }
}

TEST(DecimalTest, TestCompareTo) {
    auto min_int128 = DecimalUtils::StrToInt128("-170141183460469231731687303715884105728").value();
    auto can_negate = [min_int128](const Decimal& decimal) {
        return decimal.Value() != min_int128;
    };

    auto CheckResult = [can_negate](const Decimal& decimal1, const Decimal& decimal2) {
        ASSERT_FALSE(decimal1 < decimal1);
        ASSERT_FALSE(decimal1 > decimal1);
        ASSERT_EQ(decimal1, decimal1);

        ASSERT_EQ(decimal1.CompareTo(decimal2), -1);
        ASSERT_LT(decimal1, decimal2);
        ASSERT_EQ(decimal2.CompareTo(decimal1), 1);
        ASSERT_GT(decimal2, decimal1);
        auto decimal3 = decimal1;
        ASSERT_EQ(decimal3.CompareTo(decimal1), 0);
        ASSERT_EQ(decimal3, decimal1);

        if (!can_negate(decimal1) || !can_negate(decimal2)) {
            return;
        }
        Decimal negative_decimal1(decimal1.Precision(), decimal1.Scale(), -decimal1.Value());
        Decimal negative_decimal2(decimal2.Precision(), decimal2.Scale(), -decimal2.Value());
        ASSERT_EQ(negative_decimal1.CompareTo(negative_decimal2), 1);
        ASSERT_GT(negative_decimal1, negative_decimal2);
        ASSERT_EQ(negative_decimal2.CompareTo(negative_decimal1), -1);
        ASSERT_LT(negative_decimal2, negative_decimal1);
        auto negative_decimal3 = negative_decimal1;
        ASSERT_EQ(negative_decimal3.CompareTo(negative_decimal1), 0);
        ASSERT_EQ(negative_decimal3, negative_decimal1);
    };

    auto CheckEqual = [can_negate](const Decimal& decimal1, const Decimal& decimal2) {
        ASSERT_EQ(decimal1.CompareTo(decimal2), 0);
        ASSERT_EQ(decimal2.CompareTo(decimal1), 0);

        if (!can_negate(decimal1) || !can_negate(decimal2)) {
            return;
        }
        Decimal negative_decimal1(decimal1.Precision(), decimal1.Scale(), -decimal1.Value());
        Decimal negative_decimal2(decimal2.Precision(), decimal2.Scale(), -decimal2.Value());
        ASSERT_EQ(negative_decimal1.CompareTo(negative_decimal2), 0);
        ASSERT_EQ(negative_decimal2.CompareTo(negative_decimal1), 0);
    };

    // same scales
    {
        Decimal decimal1(23, 0, DecimalUtils::StrToInt128("99").value());
        Decimal decimal2(23, 0, DecimalUtils::StrToInt128("100").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 5, DecimalUtils::StrToInt128("34543").value());
        Decimal decimal2(23, 5, DecimalUtils::StrToInt128("4324324").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 15, DecimalUtils::StrToInt128("345345435432").value());
        Decimal decimal2(23, 15, DecimalUtils::StrToInt128("345344425435432").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 20, DecimalUtils::StrToInt128("5").value());
        Decimal decimal2(23, 20, DecimalUtils::StrToInt128("50").value());
        CheckResult(decimal1, decimal2);
    }

    // different scales
    {
        Decimal decimal1(23, 4, DecimalUtils::StrToInt128("10000").value());
        Decimal decimal2(23, 3, DecimalUtils::StrToInt128("10000").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 2, DecimalUtils::StrToInt128("111").value());
        Decimal decimal2(23, 3, DecimalUtils::StrToInt128("1111").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 5, DecimalUtils::StrToInt128("999999").value());
        Decimal decimal2(23, 5, DecimalUtils::StrToInt128("9999999").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 1, DecimalUtils::StrToInt128("100").value());
        Decimal decimal2(23, 0, DecimalUtils::StrToInt128("99").value());
        CheckResult(decimal1, decimal2);
    }

    // same integral parts
    {
        Decimal decimal1(23, 0, DecimalUtils::StrToInt128("99999").value());
        Decimal decimal2(23, 1, DecimalUtils::StrToInt128("999999").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 3, DecimalUtils::StrToInt128("12345123").value());
        Decimal decimal2(23, 5, DecimalUtils::StrToInt128("1234553432").value());
        CheckResult(decimal1, decimal2);
    }

    // equal numbers
    {
        Decimal decimal1(23, 3, DecimalUtils::StrToInt128("100000").value());
        Decimal decimal2(23, 0, DecimalUtils::StrToInt128("100").value());
        CheckEqual(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 3, DecimalUtils::StrToInt128("100000").value());
        Decimal decimal2(23, 3, DecimalUtils::StrToInt128("100000").value());
        CheckEqual(decimal1, decimal2);
    }
    {
        Decimal decimal1(23, 10, DecimalUtils::StrToInt128("1").value());
        Decimal decimal2(23, 11, DecimalUtils::StrToInt128("10").value());
        CheckEqual(decimal1, decimal2);
    }

    // large scales (>18)
    {
        Decimal decimal1(128, 35, DecimalUtils::StrToInt128("99").value());
        Decimal decimal2(128, 35, DecimalUtils::StrToInt128("100").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(
            128, 29, DecimalUtils::StrToInt128("12345678999999999999999999999999999998").value());
        Decimal decimal2(
            128, 30, DecimalUtils::StrToInt128("123456789999999999999999999999999999999").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(
            128, 30, DecimalUtils::StrToInt128("123456789999999999999999999999999999900").value());
        Decimal decimal2(
            128, 29, DecimalUtils::StrToInt128("12345678999999999999999999999999999990").value());
        CheckEqual(decimal1, decimal2);
    }

    // minimum and maximum
    {
        Decimal decimal1(128, 39, Decimal::INT128_MAXIMUM_VALUE);
        Decimal decimal2(
            128, 39, DecimalUtils::StrToInt128("170141183460469231731687303715884105727").value());
        CheckEqual(decimal1, decimal2);
    }
    {
        Decimal decimal1(128, 39, Decimal::INT128_MINIMUM_VALUE);
        Decimal decimal2(
            128, 39, DecimalUtils::StrToInt128("-170141183460469231731687303715884105728").value());
        CheckEqual(decimal1, decimal2);
    }

    // fractional overflow
    {
        Decimal decimal1(128, 39, Decimal::INT128_MAXIMUM_VALUE);
        Decimal decimal2(
            128, 38, DecimalUtils::StrToInt128("99999999999999999999999999999999999999").value());
        CheckResult(decimal1, decimal2);
    }
    {
        Decimal decimal1(
            128, 38, DecimalUtils::StrToInt128("-99999999999999999999999999999999999999").value());
        Decimal decimal2(128, 39, Decimal::INT128_MINIMUM_VALUE);
        CheckResult(decimal1, decimal2);
    }
}
}  // namespace paimon::test
