/*
    SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
    SPDX-FileCopyrightText: 2026 Dominique Michel <dominique.michel@enioka.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "utils.h"
#include "davprotocolattribute.h"

#include <KDAV/DavCollection>
#include <KDAV/DavItem>
#include <KDAV/DavUrl>
#include <KDAV/ProtocolInfo>

#include <Akonadi/Collection>
#include <Akonadi/CollectionColorAttribute>
#include <Akonadi/EntityDisplayAttribute>
#include <KCalendarCore/ICalFormat>
#include <KCalendarCore/Incidence>
#include <KCalendarCore/MemoryCalendar>
#include <KContacts/Addressee>
#include <KContacts/VCardConverter>

#include <KLocalizedString>

#include <QByteArray>
#include <QRandomGenerator>
#include <QString>
#include <QTimeZone>

#include "davresource_debug.h"

using IncidencePtr = QSharedPointer<KCalendarCore::Incidence>;

static QString createUniqueId()
{
    const qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000;
    const int r = QRandomGenerator::global()->bounded(1000);
    const QString id = u'R' + QString::number(r);
    const QString uid = QString::number(time) + u'.' + id;
    return uid;
}

QString Utils::translatedProtocolName(KDAV::Protocol protocol)
{
    QString protocolName;

    switch (protocol) {
    case KDAV::CalDav:
        protocolName = i18n("CalDav");
        break;
    case KDAV::CardDav:
        protocolName = i18n("CardDav");
        break;
    case KDAV::GroupDav:
        protocolName = i18n("GroupDav");
        break;
    }

    return protocolName;
}

QStringList Utils::tagsToCategories(const Akonadi::Tag::List &tags)
{
    QStringList categories;
    categories.reserve(tags.size());
    for (const Akonadi::Tag &tag : tags) {
        categories.emplace_back(tag.name());
    }
    return categories;
}

KDAV::DavCollection Utils::createDavCollection(const Akonadi::Collection &collection, const KDAV::DavUrl &davUrl)
{
    auto davCollection = KDAV::DavCollection();

    davCollection.setUrl(davUrl);
    if (collection.hasAttribute<Akonadi::CollectionColorAttribute>()) {
        davCollection.setColor(collection.attribute<Akonadi::CollectionColorAttribute>()->color());
    }

    if (collection.hasAttribute<Akonadi::EntityDisplayAttribute>()) {
        const auto displayName = collection.attribute<Akonadi::EntityDisplayAttribute>()->displayName();
        if (!displayName.isEmpty()) {
            davCollection.setDisplayName(displayName);
        }
    }

    auto contentTypes = KDAV::DavCollection::ContentTypes();
    for (const auto &mimeType : collection.contentMimeTypes()) {
        if (mimeType == QString::fromStdString("text/calendar")) {
            contentTypes |= KDAV::DavCollection::Calendar;
        } else if (mimeType == KCalendarCore::Event::eventMimeType()) {
            contentTypes |= KDAV::DavCollection::Events;
        } else if (mimeType == KCalendarCore::Todo::todoMimeType()) {
            contentTypes |= KDAV::DavCollection::Todos;
        } else if (mimeType == KContacts::Addressee::mimeType()) {
            contentTypes |= KDAV::DavCollection::Contacts;
        } else if (mimeType == KCalendarCore::FreeBusy::freeBusyMimeType()) {
            contentTypes |= KDAV::DavCollection::FreeBusy;
        } else if (mimeType == KCalendarCore::Journal::journalMimeType()) {
            contentTypes |= KDAV::DavCollection::Journal;
        } else {
            qCWarning(DAVRESOURCE_LOG) << "Unknown mime type" << mimeType;
        }
    }
    davCollection.setContentTypes(contentTypes);

    const auto rights = collection.rights();
    auto privileges = davCollection.privileges();
    if (rights & Akonadi::Collection::AllRights) {
        privileges |= KDAV::All;
    }
    if (rights & Akonadi::Collection::CanChangeItem) {
        privileges |= KDAV::WriteContent;
    }
    if (rights & Akonadi::Collection::CanCreateItem) {
        privileges |= KDAV::Bind;
    }
    if (rights & Akonadi::Collection::CanDeleteItem) {
        privileges |= KDAV::Unbind;
    }
    if (rights & Akonadi::Collection::ReadOnly) {
        privileges |= KDAV::Read;
    }
    davCollection.setPrivileges(privileges);

    return davCollection;
}

KDAV::DavItem Utils::createDavItem(const Akonadi::Item &item, const Akonadi::Collection &collection, const Akonadi::Item::List &dependentItems)
{
    QByteArray rawData;
    QString mimeType;
    QUrl url;
    KDAV::DavItem davItem;
    const QString basePath = collection.remoteId();

    if (item.hasPayload<KContacts::Addressee>()) {
        auto contact = item.payload<KContacts::Addressee>();
        contact.setCategories(tagsToCategories(item.tags()));
        const QString fileName = createUniqueId();

        url = QUrl::fromUserInput(basePath + fileName + QLatin1StringView(".vcf"));

        const auto protoAttr = collection.attribute<DavProtocolAttribute>();
        if (protoAttr) {
            mimeType = KDAV::ProtocolInfo::contactsMimeType(KDAV::Protocol(protoAttr->davProtocol()));
        } else {
            mimeType = KContacts::Addressee::mimeType();
        }

        KContacts::VCardConverter converter;
        // rawData is already UTF-8
        rawData = converter.exportVCard(contact, KContacts::VCardConverter::v3_0);
    } else if (item.hasPayload<IncidencePtr>()) {
        const KCalendarCore::MemoryCalendar::Ptr calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
        auto incidence = item.payload<IncidencePtr>();
        incidence->setCategories(tagsToCategories(item.tags()));
        calendar->addIncidence(incidence);
        for (const Akonadi::Item &dependentItem : std::as_const(dependentItems)) {
            auto payload = dependentItem.payload<IncidencePtr>();
            payload->setCategories(tagsToCategories(dependentItem.tags()));
            calendar->addIncidence(payload);
        }

        const QString fileName = createUniqueId();

        url = QUrl::fromUserInput(basePath + fileName + QLatin1StringView(".ics"));
        mimeType = QStringLiteral("text/calendar");

        KCalendarCore::ICalFormat formatter;
        rawData = formatter.toString(calendar).toUtf8();
    }

    davItem.setContentType(mimeType);
    davItem.setData(rawData);
    davItem.setUrl(KDAV::DavUrl(url, KDAV::CalDav));
    davItem.setEtag(item.remoteRevision());

    return davItem;
}

static Akonadi::Tag::List categoriesToTags(const QStringList &categories)
{
    Akonadi::Tag::List tags;
    tags.reserve(categories.size());
    for (const QString &category : categories) {
        tags.emplace_back(category);
    }
    return tags;
}

bool Utils::parseDavData(const KDAV::DavItem &source, Akonadi::Item &target, Akonadi::Item::List &extraItems)
{
    const QString data = QString::fromUtf8(source.data());

    if (target.mimeType() == KContacts::Addressee::mimeType()) {
        KContacts::VCardConverter converter;
        const KContacts::Addressee contact = converter.parseVCard(source.data());

        if (contact.isEmpty()) {
            return false;
        }

        target.setTags(categoriesToTags(contact.categories()));
        target.setPayloadFromData(source.data());
    } else {
        KCalendarCore::ICalFormat formatter;
        const KCalendarCore::MemoryCalendar::Ptr calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
        formatter.fromString(calendar, data);
        KCalendarCore::Incidence::List incidences = calendar->incidences();

        if (incidences.isEmpty()) {
            return false;
        }

        // All items must have the same uid in a single object.
        // Find the main VEVENT (if that's indeed what we have,
        // could be a VTODO or a VJOURNAL but that doesn't matter)
        // and then apply the recurrence exceptions
        IncidencePtr mainIncidence;
        KCalendarCore::Incidence::List exceptions;

        for (const IncidencePtr &incidence : std::as_const(incidences)) {
            if (incidence->hasRecurrenceId()) {
                qCDebug(DAVRESOURCE_LOG) << "Exception found with ID" << incidence->instanceIdentifier();
                exceptions << incidence;
            } else {
                mainIncidence = incidence;
            }
        }

        if (!mainIncidence) {
            // Some broken events have only one incidence, with a recurrence ID - like a detached exception.
            // Rather than skipping those, make them appear: pick first incidence as the main one
            mainIncidence = incidences.at(0);
            exceptions.removeFirst();
        }

        for (const IncidencePtr &exception : std::as_const(exceptions)) {
            if (exception->status() == KCalendarCore::Incidence::StatusCanceled) {
                const QDateTime exDateTime(exception->recurrenceId());
                mainIncidence->recurrence()->addExDateTime(exDateTime);
            } else {
                // The exception remote id will contain a fragment pointing to
                // its instance identifier to distinguish it from the main
                // event.
                const QString rid = target.remoteId() + QLatin1StringView("#") + exception->instanceIdentifier();
                qCDebug(DAVRESOURCE_LOG) << "Extra incidence at" << rid;
                Akonadi::Item extraItem = target;
                extraItem.setRemoteId(rid);
                extraItem.setRemoteRevision(source.etag());
                extraItem.setMimeType(exception->mimeType());
                extraItem.setPayload<IncidencePtr>(exception);
                extraItem.setTags(categoriesToTags(exception->categories()));
                extraItems << extraItem;
            }
        }

        target.setPayload<IncidencePtr>(mainIncidence);
        target.setTags(categoriesToTags(mainIncidence->categories()));
        // fix mime type for CalDAV collections
        target.setMimeType(mainIncidence->mimeType());

        /*
        for ( const IncidencePtr &incidence : incidences ) {
          QString rid = item.remoteId() + QLatin1StringView( "#" ) + incidence->instanceIdentifier();
          Akonadi::Item extraItem = item;
          extraItem.setRemoteId( rid );
          extraItem.setRemoteRevision( davItem.etag() );
          extraItem.setMimeType( incidence->mimeType() );
          extraItem.setPayload<IncidencePtr>( incidence );
          items << extraItem;
        }
        */
    }

    return true;
}

std::optional<KDAV::Protocol> Utils::protocolFromCollection(const Akonadi::Collection &collection)
{
    for (const auto &mimeType : collection.contentMimeTypes()) {
        if (mimeType == QString::fromStdString("text/calendar") || mimeType == KCalendarCore::Event::eventMimeType()
            || mimeType == KCalendarCore::Todo::todoMimeType() || mimeType == KCalendarCore::FreeBusy::freeBusyMimeType()
            || mimeType == KCalendarCore::Journal::journalMimeType()) {
            return KDAV::Protocol::CalDav;
        }
        if (mimeType == KContacts::Addressee::mimeType()) {
            return KDAV::Protocol::CardDav;
        }
    }
    return std::nullopt;
}
