/****************************************************************************
 *   Copyright (C) 2017-2026 Savoir-faire Linux Inc.                        *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Lesser General Public             *
 *   License as published by the Free Software Foundation; either           *
 *   version 2.1 of the License, or (at your option) any later version.     *
 *                                                                          *
 *   This library is distributed in the hope that it will be useful,        *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
 *   Lesser General Public License for more details.                        *
 *                                                                          *
 *   You should have received a copy of the GNU General Public License      *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 ***************************************************************************/
#include "api/devicemodel.h"

#include "api/accountmodel.h"
#include "callbackshandler.h"
#include "dbus/configurationmanager.h"

#include <account_const.h>

#include <QObject>

#include <list>
#include <mutex>

namespace lrc {

using namespace api;

class DeviceModelPimpl : public QObject
{
    Q_OBJECT
public:
    DeviceModelPimpl(DeviceModel& linked, const CallbacksHandler& callbacksHandler);
    ~DeviceModelPimpl();

    const CallbacksHandler& callbacksHandler;
    DeviceModel& linked;

    std::mutex devicesMtx_;
    QString currentDeviceId_;
    QList<Device> devices_;
public Q_SLOTS:
    /**
     * Listen from CallbacksHandler to get when a device name changed or a device is added
     * @param accountId interaction receiver.
     * @param devices A map of device IDs with corresponding labels.
     */
    void slotKnownDevicesChanged(const QString& accountId, const MapStringString devices);

    /**
     * update devices_ when a device is revoked
     * @param accountId
     * @param deviceId
     * @param status SUCCESS = 0, WRONG_PASSWORD = 1, UNKNOWN_DEVICE = 2
     */
    void slotDeviceRevocationEnded(const QString& accountId, const QString& deviceId, const int status);
};

DeviceModel::DeviceModel(const account::Info& owner, const CallbacksHandler& callbacksHandler)
    : owner(owner)
    , pimpl_(std::make_unique<DeviceModelPimpl>(*this, callbacksHandler))
{}

DeviceModel::~DeviceModel() {}

int
DeviceModel::rowCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    return pimpl_->devices_.size();
}

QVariant
DeviceModel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid() || index.row() >= pimpl_->devices_.size())
        return {};

    const auto& device = pimpl_->devices_.at(index.row());
    switch (role) {
    case DeviceList::DeviceName:
        return device.name;
    case DeviceList::DeviceID:
        return device.id;
    case DeviceList::IsCurrent:
        return device.isCurrent;
    }
    return {};
}

QHash<int, QByteArray>
DeviceModel::roleNames() const
{
    return {
        {DeviceList::DeviceName, "DeviceName"},
        {DeviceList::DeviceID, "DeviceID"},
        {DeviceList::IsCurrent, "IsCurrent"},
    };
}

QList<Device>
DeviceModel::getAllDevices() const
{
    return pimpl_->devices_;
}

Device
DeviceModel::getDevice(const QString& id) const
{
    std::lock_guard<std::mutex> lock(pimpl_->devicesMtx_);
    auto i = std::find_if(pimpl_->devices_.begin(), pimpl_->devices_.end(), [id](const Device& d) {
        return d.id == id;
    });

    if (i == pimpl_->devices_.end())
        return {};

    return *i;
}

void
DeviceModel::revokeDevice(const QString& id, const QString& password)
{
    ConfigurationManager::instance().revokeDevice(owner.id, id, "password", password);
}

void
DeviceModel::setCurrentDeviceName(const QString& newName)
{
    // Update deamon config
    auto config = owner.accountModel->getAccountConfig(owner.id);
    config.deviceName = newName;
    owner.accountModel->setAccountConfig(owner.id, config);
    // Update model
    std::unique_lock<std::mutex> lock(pimpl_->devicesMtx_);
    for (int i = 0; i < pimpl_->devices_.size(); ++i) {
        auto& device = pimpl_->devices_[i];
        if (device.id == config.deviceId) {
            device.name = newName;
            lock.unlock();
            const auto idx = index(i);
            Q_EMIT dataChanged(idx, idx);
            Q_EMIT deviceUpdated(device.id);
            return;
        }
    }
}

DeviceModelPimpl::DeviceModelPimpl(DeviceModel& linked, const CallbacksHandler& callbacksHandler)
    : linked(linked)
    , callbacksHandler(callbacksHandler)
    , devices_({})
{
    const MapStringString aDetails = ConfigurationManager::instance().getVolatileAccountDetails(linked.owner.id);
    currentDeviceId_ = aDetails.value(libjami::Account::ConfProperties::DEVICE_ID);
    const MapStringString accountDevices = ConfigurationManager::instance().getKnownRingDevices(linked.owner.id);
    auto it = accountDevices.begin();
    while (it != accountDevices.end()) {
        {
            std::lock_guard<std::mutex> lock(devicesMtx_);
            auto device = Device {/* id= */ it.key(),
                                  /* name= */ it.value(),
                                  /* isCurrent= */ it.key() == currentDeviceId_};
            if (device.isCurrent) {
                currentDeviceId_ = it.key();
                devices_.push_back(device);
            } else {
                devices_.push_back(device);
            }
        }
        ++it;
    }

    connect(&callbacksHandler, &CallbacksHandler::knownDevicesChanged, this, &DeviceModelPimpl::slotKnownDevicesChanged);
    connect(&callbacksHandler,
            &CallbacksHandler::deviceRevocationEnded,
            this,
            &DeviceModelPimpl::slotDeviceRevocationEnded);
}

DeviceModelPimpl::~DeviceModelPimpl()
{
    disconnect(&callbacksHandler,
               &CallbacksHandler::knownDevicesChanged,
               this,
               &DeviceModelPimpl::slotKnownDevicesChanged);
    disconnect(&callbacksHandler,
               &CallbacksHandler::deviceRevocationEnded,
               this,
               &DeviceModelPimpl::slotDeviceRevocationEnded);
}

void
DeviceModelPimpl::slotKnownDevicesChanged(const QString& accountId, const MapStringString devices)
{
    if (accountId != linked.owner.id)
        return;
    auto devicesMap = devices;
    // Update current devices
    QStringList updatedDevices;
    {
        std::lock_guard<std::mutex> lock(devicesMtx_);
        for (auto& device : devices_) {
            if (devicesMap.find(device.id) != devicesMap.end()) {
                if (device.name != devicesMap[device.id]) {
                    updatedDevices.push_back(device.id);
                    device.name = devicesMap[device.id];
                }
                devicesMap.remove(device.id);
            }
        }
    }
    for (const auto& device : updatedDevices) {
        auto it = std::find_if(devices_.begin(), devices_.end(), [&device](const Device& d) { return d.id == device; });
        if (it != devices_.end()) {
            auto row = static_cast<int>(std::distance(devices_.begin(), it));
            const auto idx = linked.index(row);
            Q_EMIT linked.dataChanged(idx, idx);
        }
        Q_EMIT linked.deviceUpdated(device);
    }

    // Add new devices
    QStringList addedDevices;
    {
        std::lock_guard<std::mutex> lock(devicesMtx_);
        auto it = devicesMap.begin();
        while (it != devicesMap.end()) {
            auto pos = devices_.size();
            linked.beginInsertRows(QModelIndex(), pos, pos);
            devices_.push_back(Device {/* id= */ it.key(),
                                       /* name= */ it.value(),
                                       /* isCurrent= */ false});
            linked.endInsertRows();
            addedDevices.push_back(it.key());
            ++it;
        }
    }
    for (const auto& device : addedDevices)
        Q_EMIT linked.deviceAdded(device);
}

void
DeviceModelPimpl::slotDeviceRevocationEnded(const QString& accountId, const QString& deviceId, const int status)
{
    if (accountId != linked.owner.id)
        return;
    if (status == 0) {
        std::lock_guard<std::mutex> lock(devicesMtx_);
        auto it = std::find_if(devices_.begin(), devices_.end(), [deviceId](const Device& d) {
            return d.id == deviceId;
        });

        if (it != devices_.end()) {
            auto row = static_cast<int>(std::distance(devices_.begin(), it));
            linked.beginRemoveRows(QModelIndex(), row, row);
            devices_.erase(it);
            linked.endRemoveRows();
        }
    }

    switch (status) {
    case 0:
        Q_EMIT linked.deviceRevoked(deviceId, DeviceModel::Status::SUCCESS);
        break;
    case 1:
        Q_EMIT linked.deviceRevoked(deviceId, DeviceModel::Status::WRONG_PASSWORD);
        break;
    case 2:
        Q_EMIT linked.deviceRevoked(deviceId, DeviceModel::Status::UNKNOWN_DEVICE);
        break;
    default:
        break;
    }
}

} // namespace lrc

#include "devicemodel.moc"
#include "api/moc_devicemodel.cpp"
