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

#include "pollhandler.h"

#include "neochatroom.h"

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

#include <algorithm>

using namespace Quotient;

PollHandler::PollHandler(NeoChatRoom *room, const Quotient::PollStartEvent *pollStartEvent)
    : QObject(room)
    , m_pollStartEvent(pollStartEvent)
{
    if (room != nullptr && m_pollStartEvent != nullptr) {
        connect(room, &NeoChatRoom::aboutToAddNewMessages, this, &PollHandler::updatePoll);
        checkLoadRelations();
    }
}

void PollHandler::updatePoll(Quotient::RoomEventsRange events)
{
    // This function will never be called if the PollHandler was not initialized with
    // a NeoChatRoom as parent and a PollStartEvent so no need to null check.
    auto room = dynamic_cast<NeoChatRoom *>(parent());
    for (const auto &event : events) {
        if (event->is<PollEndEvent>()) {
            auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
            if (!plEvent) {
                continue;
            }
            auto userPl = plEvent->powerLevelForUser(event->senderId());
            if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
                m_hasEnded = true;
                m_endedTimestamp = event->originTimestamp();
                Q_EMIT hasEndedChanged();
            }
        }
        if (event->is<PollResponseEvent>()) {
            handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
        }
        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() == m_pollStartEvent->id()) {
            Q_EMIT questionChanged();
            Q_EMIT optionsChanged();
        }
    }
}

void PollHandler::checkLoadRelations()
{
    // This function will never be called if the PollHandler was not initialized with
    // a NeoChatRoom as parent and a PollStartEvent so no need to null check.
    auto room = dynamic_cast<NeoChatRoom *>(parent());
    m_maxVotes = m_pollStartEvent->maxSelections();
    auto job = room->connection()->callApi<GetRelatingEventsJob>(room->id(), m_pollStartEvent->id());
    connect(job, &BaseJob::success, this, [this, job, room]() {
        for (const auto &event : job->chunk()) {
            if (event->is<PollEndEvent>()) {
                auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
                if (!plEvent) {
                    continue;
                }
                auto userPl = plEvent->powerLevelForUser(event->senderId());
                if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
                    m_hasEnded = true;
                    m_endedTimestamp = event->originTimestamp();
                    Q_EMIT hasEndedChanged();
                }
            }
            if (event->is<PollResponseEvent>()) {
                handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
            }
        }
    });
}

void PollHandler::handleAnswer(const QJsonObject &content, const QString &sender, QDateTime timestamp)
{
    if (timestamp > m_answerTimestamps[sender] && (!m_hasEnded || timestamp < m_endedTimestamp)) {
        m_answerTimestamps[sender] = timestamp;
        m_answers[sender] = {};
        int i = 0;
        for (const auto &answer : content["org.matrix.msc3381.poll.response"_L1]["answers"_L1].toArray()) {
            auto array = m_answers[sender].toArray();
            array.insert(0, answer);
            m_answers[sender] = array;
            i++;
            if (i == m_maxVotes) {
                break;
            }
        }
        for (const auto &key : m_answers.keys()) {
            if (m_answers[key].toArray().isEmpty()) {
                m_answers.remove(key);
            }
        }
    }
    Q_EMIT answersChanged();
}

QString PollHandler::question() const
{
    if (m_pollStartEvent == nullptr) {
        return {};
    }
    return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_L1)["question"_L1].toObject()["body"_L1].toString();
}

QJsonArray PollHandler::options() const
{
    if (m_pollStartEvent == nullptr) {
        return {};
    }
    return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_L1)["answers"_L1].toArray();
}

QJsonObject PollHandler::answers() const
{
    return m_answers;
}

QJsonObject PollHandler::counts() const
{
    QJsonObject counts;
    for (const auto &answer : m_answers) {
        for (const auto &id : answer.toArray()) {
            counts[id.toString()] = counts[id.toString()].toInt() + 1;
        }
    }
    return counts;
}

QString PollHandler::kind() const
{
    if (m_pollStartEvent == nullptr) {
        return {};
    }
    return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_L1)["kind"_L1].toString();
}

void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
{
    Q_ASSERT(eventId.length() > 0);
    Q_ASSERT(answerId.length() > 0);
    auto room = dynamic_cast<NeoChatRoom *>(parent());
    if (room == nullptr) {
        qWarning() << "PollHandler is empty, cannot send an answer.";
        return;
    }
    QStringList ownAnswers;
    for (const auto &answer : m_answers[room->localMember().id()].toArray()) {
        ownAnswers += answer.toString();
    }
    if (ownAnswers.contains(answerId)) {
        ownAnswers.erase(std::remove_if(ownAnswers.begin(), ownAnswers.end(), [answerId](const auto &it) {
            return answerId == it;
        }));
    } else {
        while (ownAnswers.size() >= m_maxVotes) {
            ownAnswers.pop_front();
        }
        ownAnswers.insert(0, answerId);
    }

    const auto &response = room->post<PollResponseEvent>(eventId, ownAnswers);
    handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime());
}

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

int PollHandler::answerCount() const
{
    return m_answers.size();
}

#include "moc_pollhandler.cpp"
