// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later

#include "pollblock.h"

#include <KLocalization>

#include "neochatroom.h"

#include <Quotient/csapi/relations.h>
#include <Quotient/events/roompowerlevelsevent.h>

using namespace Quotient;
using namespace Blocks;

PollBlock::PollBlock(Type type, const QString &pollStartId, NeoChatRoom *room, QObject *parent)
    : Block(type, parent)
    , m_pollStartId(pollStartId)
    , m_room(room)
{
    if (room != nullptr) {
        connect(room, &NeoChatRoom::aboutToAddNewMessages, this, &PollBlock::updatePoll);
        connect(room, &NeoChatRoom::pendingEventAboutToAdd, this, &PollBlock::handleEvent);
        checkLoadRelations();
    }
}

void PollBlock::updatePoll(Quotient::RoomEventsRange events)
{
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (!pollStartEvent) {
        return;
    }
    std::ranges::for_each(events, [this](const std::unique_ptr<Quotient::RoomEvent> &event) {
        handleEvent(event.get());
    });
}

void PollBlock::checkLoadRelations(const QString &nextBatch)
{
    const auto pollStartEvent = m_room->getEvent(m_pollStartId).first;
    if (pollStartEvent == nullptr) {
        return;
    }

    m_room->connection()->callApi<GetRelatingEventsJob>(m_room->id(), pollStartEvent->id(), nextBatch).onResult([this](const auto &job) {
        for (const auto &event : job->chunk()) {
            handleEvent(event.get());
        }

        // This is paginated API. If it indicates that there's more data, run again starting from the supplied pagination token.
        if (!job->nextBatch().isEmpty()) {
            checkLoadRelations(job->nextBatch());
        }
    });
}

void PollBlock::handleEvent(Quotient::RoomEvent *event)
{
    if (auto encEvent = eventCast<const EncryptedEvent>(event)) {
        const auto decrypted = room()->decryptMessage(*encEvent);
        handleEvent(decrypted.get());
        return;
    }

    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return;
    }

    if (event->is<PollEndEvent>()) {
        const auto endEvent = eventCast<const PollEndEvent>(event);
        if (endEvent->relatesTo()->eventId != m_pollStartId) {
            return;
        }

        auto plEvent = m_room->currentState().get<RoomPowerLevelsEvent>();
        if (!plEvent) {
            return;
        }
        auto userPl = plEvent->powerLevelForUser(event->senderId());
        if (event->senderId() == pollStartEvent->senderId() || userPl >= plEvent->redact()) {
            m_hasEnded = true;
            m_endedTimestamp = event->originTimestamp();
            Q_EMIT hasEndedChanged();
        }
    }
    if (event->is<PollResponseEvent>()) {
        handleResponse(eventCast<const PollResponseEvent>(event));
    }
    if (event->contentPart<QJsonObject>("m.relates_to"_L1).contains("rel_type"_L1)
        && event->contentPart<QJsonObject>("m.relates_to"_L1)["rel_type"_L1].toString() == "m.replace"_L1
        && event->contentPart<QJsonObject>("m.relates_to"_L1)["event_id"_L1].toString() == pollStartEvent->id()) {
        Q_EMIT questionChanged();
        Q_EMIT answersChanged();
    }
}

void PollBlock::handleResponse(const Quotient::PollResponseEvent *event)
{
    if (event == nullptr) {
        return;
    }

    if (event->relatesTo()->eventId != m_pollStartId) {
        return;
    }

    // If there is no origin timestamp it's pending and therefore must be newer.
    if ((event->originTimestamp() > m_selectionTimestamps[event->senderId()] || event->id().isEmpty())
        && (!m_hasEnded || event->originTimestamp() < m_endedTimestamp)) {
        m_selectionTimestamps[event->senderId()] = event->originTimestamp();

        const auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
        if (pollStartEvent == nullptr) {
            return;
        }

        m_selections[event->senderId()] = event->selections().size() > 0
            ? event->selections().first(std::min(pollStartEvent->maxSelections(), (int)event->selections().size()))
            : event->selections();
        if (m_selections.contains(event->senderId()) && m_selections[event->senderId()].isEmpty()) {
            m_selections.remove(event->senderId());
        }
    }

    Q_EMIT totalCountChanged();
    Q_EMIT selectionsChanged();
}

NeoChatRoom *PollBlock::room() const
{
    return m_room.get();
}

QString PollBlock::question() const
{
    if (!m_room) {
        return {};
    }
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return {};
    }
    return pollStartEvent->question();
}

int PollBlock::numAnswers() const
{
    if (m_room == nullptr) {
        return {};
    }
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return {};
    }
    return pollStartEvent->answers().length();
}

Quotient::EventContent::Answer PollBlock::answerAtRow(int row) const
{
    if (m_room == nullptr) {
        return {};
    }
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return {};
    }
    return pollStartEvent->answers()[row];
}

int PollBlock::answerCountAtId(const QString &id) const
{
    int count = 0;
    for (const auto &selection : m_selections) {
        if (selection.contains(id)) {
            count++;
        }
    }
    return count;
}

bool PollBlock::checkMemberSelectedId(const QString &memberId, const QString &id) const
{
    return m_selections[memberId].contains(id);
}

PollKind::Kind PollBlock::kind() const
{
    if (m_room == nullptr) {
        return {};
    }
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return {};
    }
    return pollStartEvent->kind();
}

PollAnswerModel *PollBlock::answerModel()
{
    if (m_answerModel == nullptr) {
        m_answerModel = new PollAnswerModel(this);
    }
    return m_answerModel;
}

int PollBlock::totalCount() const
{
    int votes = 0;
    for (const auto &selection : m_selections) {
        votes += selection.size();
    }
    return votes;
}

QStringList PollBlock::winningAnswerIds() const
{
    if (m_room == nullptr) {
        return {};
    }
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return {};
    }

    QStringList currentWinners;
    for (const auto &answer : pollStartEvent->answers()) {
        if (currentWinners.isEmpty()) {
            currentWinners += answer.id;
            continue;
        }
        if (answerCountAtId(currentWinners.first()) < answerCountAtId(answer.id)) {
            currentWinners.clear();
            currentWinners += answer.id;
            continue;
        }
        if (answerCountAtId(currentWinners.first()) == answerCountAtId(answer.id)) {
            currentWinners += answer.id;
        }
    }
    return currentWinners;
}

void PollBlock::sendPollAnswer(const QString &eventId, const QString &answerId)
{
    Q_ASSERT(eventId.length() > 0);
    Q_ASSERT(answerId.length() > 0);
    if (m_room == nullptr) {
        qWarning() << "PollHandler is empty, cannot send an answer.";
        return;
    }
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return;
    }

    QStringList ownAnswers = m_selections[m_room->localMember().id()];
    if (ownAnswers.contains(answerId)) {
        ownAnswers.erase(std::remove_if(ownAnswers.begin(), ownAnswers.end(), [answerId](const auto &it) {
            return answerId == it;
        }));
    } else {
        while (ownAnswers.size() >= pollStartEvent->maxSelections() && ownAnswers.size() > 0) {
            ownAnswers.pop_front();
        }
        ownAnswers.insert(0, answerId);
    }

    const auto &response = m_room->post<PollResponseEvent>(eventId, ownAnswers);
    handleResponse(eventCast<const PollResponseEvent>(response.event()));
}

bool PollBlock::hasEnded() const
{
    return m_hasEnded;
}

void PollBlock::endPoll() const
{
    room()->post<PollEndEvent>(m_pollStartId, endText());
}

QString PollBlock::endText() const
{
    if (!m_room) {
        return {};
    }
    auto pollStartEvent = eventCast<const PollStartEvent>(m_room->getEvent(m_pollStartId).first);
    if (pollStartEvent == nullptr) {
        return {};
    }
    int maxCount = 0;
    QString answerText = {};
    for (const auto &answer : pollStartEvent->answers()) {
        const auto currentCount = answerCountAtId(answer.id);
        if (currentCount > maxCount) {
            maxCount = currentCount;
            answerText = answer.text;
        }
    }

    return i18nc("%1 is the poll answer that had the most votes", "The poll has ended. Top answer: %1", answerText);
}

#include "moc_pollblock.cpp"
