// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "wrappers.qpb.h"
#include <QtProtobufWellKnownTypes/duration.qpb.h>
#include <QtProtobufWellKnownTypes/private/qprotobufwellknowntypesjsonserializers_p.h>
#include <QtProtobufWellKnownTypes/timestamp.qpb.h>

#include <QtProtobuf/private/protobufscalarjsonserializers_p.h>
#include <QtProtobuf/private/qprotobufjsonserializer_p.h>
#include <QtProtobuf/private/qtprotobuflogging_p.h>

#include <QtCore/qjsonvalue.h>
#include <QtCore/qtimezone.h>

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

namespace {
QJsonValue serializeProtobufWellKnownTimestamp(const QProtobufMessage *message)
{
    qint64 secs = 0;
    qint32 nanos = 0;

    if (const auto secondsValue = message->property("seconds"); secondsValue.canConvert<qint64>()) {
        secs = secondsValue.value<qint64>();
    } else {
        qProtoWarning("Failed to convert seconds");
    }

    if (const auto nanosValue = message->property("nanos"); nanosValue.canConvert<qint32>()) {
        nanos = nanosValue.value<qint32>();
    } else {
        qProtoWarning("Failed to convert nanos");
    }

    const auto datetime = QDateTime::fromMSecsSinceEpoch(secs * 1000 + nanos / 1000'000,
                                                         QTimeZone::UTC);
    return ProtobufScalarJsonSerializers::serializeCommon<
        QString>(datetime.toString(Qt::ISODateWithMs));
}

bool deserializeProtobufWellKnownTimestamp(QProtobufMessage *message, const QJsonValue &value)
{
    if (!value.isString())
        return false;

    const auto tsString = value.toString();
    // Protobuf requires upper-case letters in timestamp string be case sensitive.
    if (tsString.toUpper() != tsString)
        return false;

    if (tsString.contains(u' '))
        return false;

    //Ensure the field either ends with Z or a valid offset
    static const QRegularExpression TimeStampEnding(".+([\\+\\-]\\d{2}:\\d{2}|Z)$"_L1);
    if (!TimeStampEnding.match(tsString).hasMatch())
        return false;

    const auto datetime = QDateTime::fromString(tsString, Qt::ISODateWithMs);

    if (!datetime.isValid()) {
        qProtoWarning("Datetime is invalid");
        return false;
    }
    const auto msecs = datetime.toMSecsSinceEpoch();
    const qint64 seconds = msecs / 1000;
    const qint32 nanos = (msecs % 1000) * 1000'000;

    message->setProperty("seconds", QVariant::fromValue(seconds));
    message->setProperty("nanos", QVariant::fromValue(nanos));

    return true;
}

bool checkDurationRanges(qint64 seconds, qint32 nanos)
{
    if (nanos > 999'999'999 || nanos < -999'999'999) {
        qProtoWarning("The input nanoseconds are out of range: %d", nanos);
        return false;
    }

    if (seconds > 315'576'000'000 || seconds < -315'576'000'000) {
        qProtoWarning("The input nanoseconds are out of range: %lld", seconds);
        return false;
    }
    return true;
}

QJsonValue serializeProtobufWellKnownDuration(const QProtobufMessage *message)
{
    qint64 seconds = 0;
    qint32 nanos = 0;

    if (const auto secondsValue = message->property("seconds"); secondsValue.canConvert<qint64>()) {
        seconds = secondsValue.value<qint64>();
    } else {
        qProtoWarning("Failed to convert seconds");
        return {QJsonValue::Undefined};
    }

    if (const auto nanosValue = message->property("nanos"); nanosValue.canConvert<qint32>()) {
        nanos = nanosValue.value<qint32>();
    } else {
        qProtoWarning("Failed to convert nanos");
        return {QJsonValue::Undefined};
    }

    if (seconds != 0 && nanos != 0 && (seconds ^ qint64(nanos)) < 0 ) {
        qProtoWarning("Duration seconds and nanos must have the same sign");
        return {QJsonValue::Undefined};
    }

    if (!checkDurationRanges(seconds, nanos))
        return {QJsonValue::Undefined};

    // Reserve the maximum possible number of bytes that can be used by duration string:
    //  - 9 symbols for nanoseconds
    //  - 12 symbols for seconds
    //  - 1 symbol for the sign
    //  - 1 symbol for the floating point preriod
    //  - 1 symbol for the trailing 's'
    static constexpr qsizetype ResultMaxSize = 24;
    QString result;
    result.reserve(ResultMaxSize);

    if (nanos < 0) {
        if (seconds == 0)
            result.append(u'-');
        nanos = qAbs(nanos);
    }
    result.append(QString::number(seconds));

    if (nanos != 0) {
        result.append(u'.');
        // Calculate the number of '0' we need to prepend before nanos and cut the traling zeros
        qint32 multiplier = 100'000'000;
        for (;multiplier != 0 && nanos % multiplier != 0; multiplier /= 10) {
            if (nanos / multiplier == 0)
                result.append(u'0');
        }
        nanos /= multiplier;
        result.append(QString::number(nanos));
    }
    result.append(u's');

    Q_ASSERT_X(result.size() <= ResultMaxSize, "serializeProtobufWellKnownDuration",
               "Serialized duration is malformed. It's an internal Qt issue please file a bugreport"
               " https://bugreports.qt.io");

    return ProtobufScalarJsonSerializers::serializeCommon<QString>(result);
}

bool deserializeProtobufWellKnownDuration(QProtobufMessage *message, const QJsonValue &value)
{
    if (!value.isString())
        return false;

    const static QRegularExpression DurationRegex("^(-?)(\\d{1,12})(\\.(\\d{1,9}))?s$"_L1);

    const auto valueString = value.toString();
    const auto match = DurationRegex.match(valueString);

    if (!match.hasMatch()) {
        qProtoWarning("The input string is malformed: %s", qPrintable(valueString));
        return false;
    }

    const auto sign = match.captured(1);
    auto nanosString = match.captured(4);
    auto seconds = match.captured(2).toLongLong();
    qint32 nanos = nanosString.toInt();

    if (!checkDurationRanges(seconds, nanos))
        return false;

    if (nanos != 0)
        nanos *= qint32(qPow(10, 9 - nanosString.size()));

    if (!sign.isEmpty()) {
        seconds *= -1;
        nanos *= -1;
    }

    message->setProperty("seconds", QVariant::fromValue(seconds));
    message->setProperty("nanos", QVariant::fromValue(nanos));

    return true;
}

template <typename T>
bool deserializeProtobufWellKnownValueType(QProtobufMessage *message, const QJsonValue &value)
{
    using ReturnType = std::invoke_result_t<decltype(&T::value), T>;

    bool ok = false;
    message->setProperty("value",
                         ProtobufScalarJsonSerializers::deserializeCommon<ReturnType>(value, ok));
    return ok;
}

template <typename T>
void registerProtobufWellKnownValueTypeJsonHandler()
{
    QtProtobufPrivate::registerCustomJsonHandler(QMetaType::fromType<T>(), nullptr,
                                                 deserializeProtobufWellKnownValueType<T>);
}

} // namespace

void QtProtobufWellKnownTypesPrivate::registerTimestampCustomJsonHandler()
{
    QtProtobufPrivate::registerCustomJsonHandler(QMetaType::fromType<google::protobuf::Timestamp>(),
                                                 serializeProtobufWellKnownTimestamp,
                                                 deserializeProtobufWellKnownTimestamp);
}

void QtProtobufWellKnownTypesPrivate::registerDurationCustomJsonHandler()
{
    QtProtobufPrivate::registerCustomJsonHandler(QMetaType::fromType<google::protobuf::Duration>(),
                                                 serializeProtobufWellKnownDuration,
                                                 deserializeProtobufWellKnownDuration);
}

void QtProtobufWellKnownTypesPrivate::registerBoolValueCustomJsonHandler()
{
    registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::BoolValue>();
}

void QtProtobufWellKnownTypesPrivate::registerInt32ValueCustomJsonHandler()
{
    registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::Int32Value>();
}

void QtProtobufWellKnownTypesPrivate::registerInt64ValueCustomJsonHandler()
{
    registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::Int64Value>();
}

void QtProtobufWellKnownTypesPrivate::registerUInt32ValueCustomJsonHandler()
{
    registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::UInt32Value>();
}

void QtProtobufWellKnownTypesPrivate::registerUInt64ValueCustomJsonHandler()
{
    registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::UInt64Value>();
}

void QtProtobufWellKnownTypesPrivate::registerFloatValueCustomJsonHandler()
{
    registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::FloatValue>();
}

void QtProtobufWellKnownTypesPrivate::registerDoubleValueCustomJsonHandler()
{
    registerProtobufWellKnownValueTypeJsonHandler<google::protobuf::DoubleValue>();
}

void QtProtobufWellKnownTypesPrivate::registerStringValueCustomJsonHandler()
{
    QtProtobufPrivate::
        registerCustomJsonHandler(QMetaType::fromType<google::protobuf::StringValue>(), nullptr,
                                  [](QProtobufMessage *message, const QJsonValue &value) -> bool {
                                      if (!value.isString())
                                          return false;
                                      message->setProperty("value",
                                                           QVariant::fromValue(value.toString()));
                                      return true;
                                  });
}

void QtProtobufWellKnownTypesPrivate::registerBytesValueCustomJsonHandler()
{
    QtProtobufPrivate::registerCustomJsonHandler(
        QMetaType::fromType<google::protobuf::BytesValue>(), nullptr,
        [](QProtobufMessage *message, const QJsonValue &value) -> bool {
            if (!value.isString())
                return false;
            message->setProperty("value",
                                 QVariant::fromValue(QByteArray::fromBase64(value.toString()
                                                                                .toLatin1())));
            return true;
        });
}

QT_END_NAMESPACE
