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

// Adapted from Apache Iceberg C++
// https://github.com/apache/iceberg-cpp/blob/v0.2.0/src/iceberg/avro/avro_direct_decoder.cc

#include "paimon/format/avro/avro_direct_decoder.h"

#include "arrow/api.h"
#include "arrow/util/checked_cast.h"
#include "avro/Decoder.hh"
#include "avro/Node.hh"
#include "avro/Types.hh"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/date_time_utils.h"
#include "paimon/format/avro/avro_utils.h"

namespace paimon::avro {

namespace {

/// Forward declaration for mutual recursion.
Status DecodeFieldToBuilder(const ::avro::NodePtr& avro_node,
                            const std::optional<std::set<size_t>>& projection,
                            ::avro::Decoder* decoder, arrow::ArrayBuilder* array_builder,
                            AvroDirectDecoder::DecodeContext* ctx);

/// \brief Skip an Avro value based on its schema without decoding
Status SkipAvroValue(const ::avro::NodePtr& avro_node, ::avro::Decoder* decoder) {
    switch (avro_node->type()) {
        case ::avro::AVRO_NULL:
            decoder->decodeNull();
            return Status::OK();

        case ::avro::AVRO_BOOL:
            decoder->decodeBool();
            return Status::OK();

        case ::avro::AVRO_INT:
            decoder->decodeInt();
            return Status::OK();

        case ::avro::AVRO_LONG:
            decoder->decodeLong();
            return Status::OK();

        case ::avro::AVRO_FLOAT:
            decoder->decodeFloat();
            return Status::OK();

        case ::avro::AVRO_DOUBLE:
            decoder->decodeDouble();
            return Status::OK();

        case ::avro::AVRO_STRING:
            decoder->skipString();
            return Status::OK();

        case ::avro::AVRO_BYTES:
            decoder->skipBytes();
            return Status::OK();

        case ::avro::AVRO_RECORD: {
            // Skip all fields in order
            for (size_t i = 0; i < avro_node->leaves(); ++i) {
                PAIMON_RETURN_NOT_OK(SkipAvroValue(avro_node->leafAt(i), decoder));
            }
            return Status::OK();
        }

        case ::avro::AVRO_ARRAY: {
            const auto& element_node = avro_node->leafAt(0);
            // skipArray() returns count like arrayStart(), must handle all blocks
            int64_t block_count = decoder->skipArray();
            while (block_count > 0) {
                for (int64_t i = 0; i < block_count; ++i) {
                    PAIMON_RETURN_NOT_OK(SkipAvroValue(element_node, decoder));
                }
                block_count = decoder->arrayNext();
            }
            return Status::OK();
        }

        case ::avro::AVRO_MAP: {
            const auto& value_node = avro_node->leafAt(1);
            // skipMap() returns count like mapStart(), must handle all blocks
            int64_t block_count = decoder->skipMap();
            while (block_count > 0) {
                for (int64_t i = 0; i < block_count; ++i) {
                    decoder->skipString();  // Skip key (always string in Avro maps)
                    PAIMON_RETURN_NOT_OK(SkipAvroValue(value_node, decoder));
                }
                block_count = decoder->mapNext();
            }
            return Status::OK();
        }

        case ::avro::AVRO_UNION: {
            const size_t branch_index = decoder->decodeUnionIndex();
            // Validate branch index
            const size_t num_branches = avro_node->leaves();
            if (branch_index >= num_branches) {
                return Status::Invalid(fmt::format("Union branch index {} out of range [0, {})",
                                                   branch_index, num_branches));
            }
            return SkipAvroValue(avro_node->leafAt(branch_index), decoder);
        }

        default:
            return Status::Invalid(fmt::format("Unsupported Avro type for skipping: {}",
                                               AvroUtils::ToString(avro_node)));
    }
}

/// Decode Avro record directly to Arrow struct builder.
Status DecodeStructToBuilder(const ::avro::NodePtr& avro_node,
                             const std::optional<std::set<size_t>>& projection,
                             ::avro::Decoder* decoder, arrow::ArrayBuilder* array_builder,
                             AvroDirectDecoder::DecodeContext* ctx) {
    if (avro_node->type() != ::avro::AVRO_RECORD) {
        return Status::Invalid(
            fmt::format("Expected Avro record, got type: {}", AvroUtils::ToString(avro_node)));
    }

    auto* struct_builder = arrow::internal::checked_cast<arrow::StructBuilder*>(array_builder);
    PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Append());

    size_t skipped_fields = 0;
    // Read all Avro fields in order (must maintain decoder position)
    for (size_t avro_idx = 0; avro_idx < avro_node->leaves(); ++avro_idx) {
        if (projection && projection->find(avro_idx) == projection->end()) {
            skipped_fields++;
            PAIMON_RETURN_NOT_OK(SkipAvroValue(avro_node->leafAt(avro_idx), decoder));
        } else {
            // Decode this field
            const auto& avro_field_node = avro_node->leafAt(avro_idx);
            auto* field_builder = struct_builder->field_builder(avro_idx - skipped_fields);
            PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(avro_field_node, /*projection=*/std::nullopt,
                                                      decoder, field_builder, ctx));
        }
    }

    return Status::OK();
}

/// Decode Avro array directly to Arrow list builder.
Status DecodeListToBuilder(const ::avro::NodePtr& avro_node, ::avro::Decoder* decoder,
                           arrow::ArrayBuilder* array_builder,
                           AvroDirectDecoder::DecodeContext* ctx) {
    if (avro_node->type() != ::avro::AVRO_ARRAY) {
        return Status::Invalid(
            fmt::format("Expected Avro array, got type: {}", AvroUtils::ToString(avro_node)));
    }

    auto* list_builder = arrow::internal::checked_cast<arrow::ListBuilder*>(array_builder);
    PAIMON_RETURN_NOT_OK_FROM_ARROW(list_builder->Append());

    auto* value_builder = list_builder->value_builder();
    const auto& element_node = avro_node->leafAt(0);

    // Read array block count
    int64_t block_count = decoder->arrayStart();
    while (block_count != 0) {
        for (int64_t i = 0; i < block_count; ++i) {
            PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(element_node, /*projection=*/std::nullopt,
                                                      decoder, value_builder, ctx));
        }
        block_count = decoder->arrayNext();
    }

    return Status::OK();
}

/// Decode Avro map directly to Arrow map builder.
Status DecodeMapToBuilder(const ::avro::NodePtr& avro_node, ::avro::Decoder* decoder,
                          arrow::ArrayBuilder* array_builder,
                          AvroDirectDecoder::DecodeContext* ctx) {
    auto* map_builder = arrow::internal::checked_cast<arrow::MapBuilder*>(array_builder);

    if (avro_node->type() == ::avro::AVRO_MAP) {
        // Handle regular Avro map: map<string, value>
        const auto& key_node = avro_node->leafAt(0);
        const auto& value_node = avro_node->leafAt(1);

        PAIMON_RETURN_NOT_OK_FROM_ARROW(map_builder->Append());
        auto* key_builder = map_builder->key_builder();
        auto* item_builder = map_builder->item_builder();

        // Read map block count
        int64_t block_count = decoder->mapStart();
        while (block_count != 0) {
            for (int64_t i = 0; i < block_count; ++i) {
                PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(key_node, /*projection=*/std::nullopt,
                                                          decoder, key_builder, ctx));
                PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(value_node, /*projection=*/std::nullopt,
                                                          decoder, item_builder, ctx));
            }
            block_count = decoder->mapNext();
        }
        return Status::OK();
    } else if (avro_node->type() == ::avro::AVRO_ARRAY && AvroUtils::HasMapLogicalType(avro_node)) {
        // Handle array-based map: list<struct<key, value>>
        PAIMON_RETURN_NOT_OK_FROM_ARROW(map_builder->Append());
        auto* key_builder = map_builder->key_builder();
        auto* item_builder = map_builder->item_builder();

        const auto& record_node = avro_node->leafAt(0);
        if (record_node->type() != ::avro::AVRO_RECORD || record_node->leaves() != 2) {
            return Status::Invalid(
                fmt::format("Array-based map must contain records with exactly 2 fields, got: {}",
                            AvroUtils::ToString(record_node)));
        }
        const auto& key_node = record_node->leafAt(0);
        const auto& value_node = record_node->leafAt(1);

        // Read array block count
        int64_t block_count = decoder->arrayStart();
        while (block_count != 0) {
            for (int64_t i = 0; i < block_count; ++i) {
                PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(key_node, /*projection=*/std::nullopt,
                                                          decoder, key_builder, ctx));
                PAIMON_RETURN_NOT_OK(DecodeFieldToBuilder(value_node, /*projection=*/std::nullopt,
                                                          decoder, item_builder, ctx));
            }
            block_count = decoder->arrayNext();
        }
        return Status::OK();
    } else {
        return Status::Invalid(
            fmt::format("Expected Avro map or array with map logical type, got: {}",
                        AvroUtils::ToString(avro_node)));
    }
}

/// Decode Avro data directly to Arrow array builder.
Status DecodeAvroValueToBuilder(const ::avro::NodePtr& avro_node,
                                const std::optional<std::set<size_t>>& projection,
                                ::avro::Decoder* decoder, arrow::ArrayBuilder* array_builder,
                                AvroDirectDecoder::DecodeContext* ctx) {
    auto type = avro_node->type();
    auto logical_type = avro_node->logicalType();

    switch (type) {
        case ::avro::AVRO_BOOL: {
            auto* builder = arrow::internal::checked_cast<arrow::BooleanBuilder*>(array_builder);
            bool value = decoder->decodeBool();
            PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
            return Status::OK();
        }

        case ::avro::AVRO_INT: {
            int32_t value = decoder->decodeInt();
            auto arrow_type = array_builder->type();
            switch (arrow_type->id()) {
                case arrow::Type::INT8: {
                    auto* builder =
                        arrow::internal::checked_cast<arrow::Int8Builder*>(array_builder);
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
                    return Status::OK();
                }
                case arrow::Type::INT16: {
                    auto* builder =
                        arrow::internal::checked_cast<arrow::Int16Builder*>(array_builder);
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
                    return Status::OK();
                }
                case arrow::Type::INT32: {
                    auto* builder =
                        arrow::internal::checked_cast<arrow::Int32Builder*>(array_builder);
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
                    return Status::OK();
                }
                case arrow::Type::DATE32: {
                    if (logical_type.type() != ::avro::LogicalType::Type::DATE) {
                        return Status::TypeError(
                            fmt::format("Unexpected avro type [{}] with arrow type [{}].",
                                        ::avro::toString(type), arrow_type->ToString()));
                    }
                    auto* builder =
                        arrow::internal::checked_cast<arrow::Date32Builder*>(array_builder);
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
                    return Status::OK();
                }
                default:
                    return Status::TypeError(
                        fmt::format("Unexpected avro type [{}] with arrow type [{}].",
                                    ::avro::toString(type), arrow_type->ToString()));
            }
        }

        case ::avro::AVRO_LONG: {
            int64_t value = decoder->decodeLong();
            switch (logical_type.type()) {
                case ::avro::LogicalType::Type::NONE: {
                    auto* builder =
                        arrow::internal::checked_cast<arrow::Int64Builder*>(array_builder);
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
                    return Status::OK();
                }
                case ::avro::LogicalType::Type::TIMESTAMP_MILLIS:
                case ::avro::LogicalType::Type::TIMESTAMP_MICROS:
                case ::avro::LogicalType::Type::TIMESTAMP_NANOS:
                case ::avro::LogicalType::Type::LOCAL_TIMESTAMP_MILLIS:
                case ::avro::LogicalType::Type::LOCAL_TIMESTAMP_MICROS:
                case ::avro::LogicalType::Type::LOCAL_TIMESTAMP_NANOS: {
                    auto* builder =
                        arrow::internal::checked_cast<arrow::TimestampBuilder*>(array_builder);
                    auto ts_type =
                        arrow::internal::checked_cast<arrow::TimestampType*>(builder->type().get());
                    // for arrow second, we need to convert it from avro millisecond
                    if (ts_type->unit() == arrow::TimeUnit::type::SECOND) {
                        value /= DateTimeUtils::CONVERSION_FACTORS[DateTimeUtils::MILLISECOND];
                    }
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
                    return Status::OK();
                }
                default:
                    return Status::TypeError(
                        fmt::format("Unexpected avro type [{}] with arrow type [{}].",
                                    ::avro::toString(type), array_builder->type()->ToString()));
            }
        }

        case ::avro::AVRO_FLOAT: {
            auto* builder = arrow::internal::checked_cast<arrow::FloatBuilder*>(array_builder);
            float value = decoder->decodeFloat();
            PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
            return Status::OK();
        }
        case ::avro::AVRO_DOUBLE: {
            auto* builder = arrow::internal::checked_cast<arrow::DoubleBuilder*>(array_builder);
            double value = decoder->decodeDouble();
            PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
            return Status::OK();
        }
        case ::avro::AVRO_STRING: {
            auto* builder = arrow::internal::checked_cast<arrow::StringBuilder*>(array_builder);
            decoder->decodeString(ctx->string_scratch);
            PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(ctx->string_scratch));
            return Status::OK();
        }

        case ::avro::AVRO_BYTES: {
            decoder->decodeBytes(ctx->bytes_scratch);
            switch (logical_type.type()) {
                case ::avro::LogicalType::Type::NONE: {
                    auto* builder =
                        arrow::internal::checked_cast<arrow::BinaryBuilder*>(array_builder);
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(
                        builder->Append(ctx->bytes_scratch.data(),
                                        static_cast<int32_t>(ctx->bytes_scratch.size())));
                    return Status::OK();
                }
                case ::avro::LogicalType::Type::DECIMAL: {
                    auto* builder =
                        arrow::internal::checked_cast<arrow::Decimal128Builder*>(array_builder);
                    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
                        arrow::Decimal128 decimal,
                        arrow::Decimal128::FromBigEndian(ctx->bytes_scratch.data(),
                                                         ctx->bytes_scratch.size()));
                    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(decimal));
                    return Status::OK();
                }
                default:
                    return Status::TypeError(
                        fmt::format("Unexpected avro type [{}] with arrow type [{}].",
                                    ::avro::toString(type), array_builder->type()->ToString()));
            }
        }

        case ::avro::AVRO_RECORD: {
            return DecodeStructToBuilder(avro_node, projection, decoder, array_builder, ctx);
        }
        case ::avro::AVRO_ARRAY: {
            if (AvroUtils::HasMapLogicalType(avro_node)) {
                return DecodeMapToBuilder(avro_node, decoder, array_builder, ctx);
            } else {
                return DecodeListToBuilder(avro_node, decoder, array_builder, ctx);
            }
        }
        case ::avro::AVRO_MAP: {
            return DecodeMapToBuilder(avro_node, decoder, array_builder, ctx);
        }
        default:
            return Status::Invalid(
                fmt::format("Unsupported avro type: {}", ::avro::toString(type)));
    }
}

Status DecodeFieldToBuilder(const ::avro::NodePtr& avro_node,
                            const std::optional<std::set<size_t>>& projection,
                            ::avro::Decoder* decoder, arrow::ArrayBuilder* array_builder,
                            AvroDirectDecoder::DecodeContext* ctx) {
    if (avro_node->type() == ::avro::AVRO_UNION) {
        const size_t branch_index = decoder->decodeUnionIndex();

        // Validate branch index
        const size_t num_branches = avro_node->leaves();
        if (branch_index >= num_branches) {
            return Status::Invalid(fmt::format("Union branch index {} out of range [0, {})",
                                               branch_index, num_branches));
        }

        const auto& branch_node = avro_node->leafAt(branch_index);
        if (branch_node->type() == ::avro::AVRO_NULL) {
            decoder->decodeNull();
            PAIMON_RETURN_NOT_OK_FROM_ARROW(array_builder->AppendNull());
            return Status::OK();
        } else {
            return DecodeFieldToBuilder(branch_node, projection, decoder, array_builder, ctx);
        }
    }

    return DecodeAvroValueToBuilder(avro_node, projection, decoder, array_builder, ctx);
}

}  // namespace

Status AvroDirectDecoder::DecodeAvroToBuilder(const ::avro::NodePtr& avro_node,
                                              const std::optional<std::set<size_t>>& projection,
                                              ::avro::Decoder* decoder,
                                              arrow::ArrayBuilder* array_builder,
                                              DecodeContext* ctx) {
    return DecodeFieldToBuilder(avro_node, projection, decoder, array_builder, ctx);
}

}  // namespace paimon::avro
