/*
 * 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.
 */

#pragma once

#include <memory>
#include <string>

#include "arrow/compute/api.h"
#include "fmt/format.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/field_type_utils.h"
#include "paimon/data/decimal.h"
#include "paimon/predicate/literal.h"

namespace paimon {
class CastingUtils {
 public:
    CastingUtils() = delete;
    ~CastingUtils() = delete;

    // make sure src and casted literals are both integer (i.e., TINYINT, SMALLINT, INT, BIGINT)
    static bool IsIntegerLiteralCastedOverflow(const Literal& src_literal,
                                               const Literal& casted_literal) {
        return GetLongValueFromLiteral(src_literal) != GetLongValueFromLiteral(casted_literal);
    }

    static Result<std::shared_ptr<arrow::Array>> Cast(
        const std::shared_ptr<arrow::Array>& src_array,
        const std::shared_ptr<arrow::DataType>& target_type,
        const arrow::compute::CastOptions& options, arrow::MemoryPool* pool);

    template <typename SrcScalar, typename SrcDataType, typename TargetScalar,
              typename TargetDataType>
    static Result<Literal> Cast(const Literal& literal,
                                const std::shared_ptr<arrow::DataType>& src_type,
                                const std::shared_ptr<arrow::DataType>& target_type,
                                const arrow::compute::CastOptions& options) {
        PAIMON_ASSIGN_OR_RAISE(FieldType target_field_type,
                               FieldTypeUtils::ConvertToFieldType(target_type->id()));
        if (literal.IsNull()) {
            return Literal(target_field_type);
        }
        auto src_value = literal.GetValue<SrcDataType>();
        if (!arrow::compute::CanCast(*src_type, *target_type)) {
            return Status::Invalid(fmt::format("cast literal failed: cannot cast from {} to {}",
                                               src_type->ToString(), target_type->ToString()));
        }
        std::shared_ptr<arrow::Scalar> src_scalar;
        if constexpr (std::is_same_v<SrcDataType, Decimal>) {
            src_scalar = std::make_shared<SrcScalar>(
                arrow::Decimal128(src_value.HighBits(), src_value.LowBits()), src_type);
        } else {
            src_scalar = std::make_shared<SrcScalar>(src_value);
        }
        arrow::TypeHolder type_holder(target_type.get());
        PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
            arrow::Datum casted_result,
            arrow::compute::Cast(arrow::Datum(src_scalar), type_holder, options));
        auto* casted_scalar =
            arrow::internal::checked_cast<TargetScalar*>(casted_result.scalar().get());
        if (!casted_scalar) {
            return Status::Invalid(fmt::format("cast literal failed: cannot cast to {} scalar",
                                               target_type->ToString()));
        }
        if constexpr (std::is_same_v<TargetDataType, std::string>) {
            std::string_view casted_data = casted_scalar->view();
            return Literal(FieldType::STRING, casted_data.data(), casted_data.size());
        } else {
            const auto* casted_data = static_cast<const TargetDataType*>(casted_scalar->data());
            assert(casted_data);
            return Literal(*casted_data);
        }
    }

    // src_array is local time (no tz), target type is utc (with tz)
    static Result<std::shared_ptr<arrow::Array>> TimestampToTimestampWithTimezone(
        const std::shared_ptr<arrow::Array>& src_array,
        const std::shared_ptr<arrow::TimestampType>& target_type, arrow::MemoryPool* pool);

    // src_array is utc (with tz) , target type is local time (no tz),
    static Result<std::shared_ptr<arrow::Array>> TimestampWithTimezoneToTimestamp(
        const std::shared_ptr<arrow::Array>& src_array,
        const std::shared_ptr<arrow::TimestampType>& target_type, arrow::MemoryPool* pool);

 private:
    static int64_t GetLongValueFromLiteral(const Literal& literal);
};
}  // namespace paimon
