/****************************************************************************
 *   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/accountmodel.h"

// new LRC
#include "api/lrc.h"
#include "api/contactmodel.h"
#include "api/conversationmodel.h"
#include "api/peerdiscoverymodel.h"
#include "api/callmodel.h"
#include "api/codecmodel.h"
#include "api/devicemodel.h"
#include "api/behaviorcontroller.h"
#include "api/datatransfermodel.h"
#include "authority/storagehelper.h"
#include "callbackshandler.h"
#include "database.h"

// old LRC
#include "api/profile.h"
#include "qtwrapper/conversions_wrap.hpp"

// Dbus
#include "dbus/configurationmanager.h"

// daemon
#include <account_const.h>

// qt
#include <QtGui/QPixmap>
#include <QtGui/QImage>
#include <QtCore/QBuffer>
#include <QJsonDocument>
#include <QTimer>

#include <atomic>

namespace lrc {

using namespace authority;
using namespace api;

class AccountModelPimpl : public QObject
{
    Q_OBJECT
public:
    AccountModelPimpl(AccountModel& linked,
                      Lrc& lrc,
                      const CallbacksHandler& callbackHandler,
                      const BehaviorController& behaviorController);
    ~AccountModelPimpl();

    using AccountInfoDbMap = std::map<QString, std::pair<account::Info, std::shared_ptr<Database>>>;

    AccountModel& linked;
    Lrc& lrc;
    const CallbacksHandler& callbacksHandler;
    const BehaviorController& behaviorController;
    QList<QString> accountIdList;
    AccountInfoDbMap accounts;

    void emitDataChanged(const QString& accountId)
    {
        auto row = accountIdList.indexOf(accountId);
        if (row >= 0) {
            const auto idx = linked.index(row);
            Q_EMIT linked.dataChanged(idx, idx);
        }
    }

    // Synchronization tools
    std::atomic_bool username_changed;
    QString new_username;
    int resetDepth_ {0};

    void beginAccountsReset()
    {
        if (resetDepth_++ == 0)
            linked.beginResetModel();
    }

    void endAccountsReset()
    {
        if (--resetDepth_ == 0)
            linked.endResetModel();
    }

    /**
     * Add the profile information from an account to the db then add it to accounts.
     * @param accountId
     * @note this method get details for an account from the daemon.
     */
    void addToAccounts(const QString& accountId);

    /**
     * Remove account from accounts list. Emit accountRemoved.
     * @param accountId
     */
    void removeFromAccounts(const QString& accountId);

    /**
     * Sync changes to the accounts list with the lrc.
     */
    void updateAccounts();

    /**
     * Update accountInfo with details from daemon
     * @param account       account to update
     */
    void updateAccountDetails(account::Info& account);

    /**
     * get a modifiable account information associated to an accountId.
     * @param accountId.
     * @return a account::Info& structure.
     */
    account::Info& getAccountInfo(const QString& accountId);

public Q_SLOTS:

    /**
     * Emit accountStatusChanged.
     * @param accountId
     * @param status
     */
    void slotAccountStatusChanged(const QString& accountID, const api::account::Status status);

    /**
     * Emit deviceAuthStateChanged.
     * @param accountId
     * @param state
     * @param details map
     */
    void slotDeviceAuthStateChanged(const QString& accountID, int state, const MapStringString& details);

    /**
     * Emit addDeviceStateChanged.
     * @param accountId
     * @param operationId
     * @param state
     * @param details
     */
    void slotAddDeviceStateChanged(const QString& accountID,
                                   uint32_t operationId,
                                   int state,
                                   const MapStringString& details);

    /**
     * @param accountId
     * @param details
     */
    void slotAccountDetailsChanged(const QString& accountID, const MapStringString& details);

    /**
     * @param accountId
     * @param details
     */
    void slotVolatileAccountDetailsChanged(const QString& accountID, const MapStringString& details);

    /**
     * Emit nameRegistrationEnded
     * @param accountId
     * @param status
     * @param name
     */
    void slotNameRegistrationEnded(const QString& accountId, int status, const QString& name);

    /**
     * Emit registeredNameFound
     * @param accountId
     * @param requestedName the name requested
     * @param status
     * @param address
     * @param registeredName the name found, have same normalized form as requestedName
     */
    void slotRegisteredNameFound(const QString& accountId,
                                 const QString& requestedName,
                                 int status,
                                 const QString& address,
                                 const QString& registeredName);

    /**
     * Emit migrationEnded
     * @param accountId
     * @param ok
     */
    void slotMigrationEnded(const QString& accountId, bool ok);
    /**
     * Emit accountProfileReceived
     * @param accountId
     * @param displayName
     * @param userPhoto
     */
    void slotAccountProfileReceived(const QString& accountId, const QString& displayName, const QString& userPhoto);
    /**
     * Emit new position
     * @param accountId
     * @param peerId
     * @param body
     * @param timestamp
     * @param daemonId
     */
    void slotNewPosition(const QString& accountId,
                         const QString& peerId,
                         const QString& body,
                         const uint64_t& timestamp,
                         const QString& daemonId) const;
};

AccountModel::AccountModel(Lrc& lrc,
                           const CallbacksHandler& callbacksHandler,
                           const BehaviorController& behaviorController)
    : QAbstractListModel(nullptr)
    , pimpl_(std::make_unique<AccountModelPimpl>(*this, lrc, callbacksHandler, behaviorController))
{}

AccountModel::~AccountModel() {}

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

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

    auto accountId = pimpl_->accountIdList.at(index.row());
    try {
        auto& accountInfo = getAccountInfo(accountId);
        switch (role) {
        case AccountList::Alias:
            return QVariant(bestNameForAccount(accountId));
        case AccountList::Username:
            return QVariant(bestIdForAccount(accountId));
        case AccountList::Type:
            return QVariant(static_cast<int>(accountInfo.profileInfo.type));
        case AccountList::Status:
            return QVariant(static_cast<int>(accountInfo.status));
        case AccountList::Enabled:
            return QVariant(accountInfo.enabled);
        case AccountList::NotificationCount:
            return QVariant(static_cast<int>(accountInfo.conversationModel->notificationsCount()));
        case AccountList::ID:
            return QVariant(accountInfo.id);
        case AccountList::Uri:
            return QVariant(accountInfo.profileInfo.uri);
        case AccountList::BotOwner:
            return QVariant(accountInfo.profileInfo.botOwner);
        }
    } catch (...) {
    }
    return {};
}

QHash<int, QByteArray>
AccountModel::roleNames() const
{
    return {
        {AccountList::Alias, "Alias"},
        {AccountList::Username, "Username"},
        {AccountList::Type, "Type"},
        {AccountList::Status, "Status"},
        {AccountList::Enabled, "Enabled"},
        {AccountList::NotificationCount, "NotificationCount"},
        {AccountList::ID, "ID"},
        {AccountList::Uri, "Uri"},
        {AccountList::BotOwner, "BotOwner"},
    };
}

const QStringList&
AccountModel::getAccountList() const
{
    return pimpl_->accountIdList;
}

size_t
AccountModel::getAccountCount() const
{
    return pimpl_->accountIdList.size();
}

void
AccountModel::setAccountEnabled(const QString& accountId, bool enabled) const
{
    auto& accountInfo = pimpl_->getAccountInfo(accountId);
    accountInfo.enabled = enabled;
    ConfigurationManager::instance().sendRegister(accountId, enabled);
}

void
AccountModel::setAccountConfig(const QString& accountId, const account::ConfProperties_t& confProperties) const
{
    auto& accountInfo = pimpl_->getAccountInfo(accountId);
    auto& configurationManager = ConfigurationManager::instance();
    MapStringString details = confProperties.toDetails();
    // Set values from Info. No need to include ID and TYPE. SIP accounts may modify the USERNAME
    // TODO: move these into the ConfProperties_t struct ?
    using namespace libjami::Account;
    details[ConfProperties::ENABLED] = accountInfo.enabled ? QString("true") : QString("false");
    details[ConfProperties::ALIAS] = accountInfo.profileInfo.alias;
    details[ConfProperties::DISPLAYNAME] = accountInfo.profileInfo.alias;
    details[ConfProperties::TYPE] = (accountInfo.profileInfo.type == profile::Type::JAMI) ? QString(ProtocolNames::RING)
                                                                                          : QString(ProtocolNames::SIP);
    if (accountInfo.profileInfo.type == profile::Type::JAMI) {
        details[ConfProperties::USERNAME] = accountInfo.profileInfo.uri;
    } else if (accountInfo.profileInfo.type == profile::Type::SIP) {
        VectorMapStringString finalCred;

        MapStringString credentials;
        credentials[ConfProperties::USERNAME] = confProperties.username;
        credentials[ConfProperties::PASSWORD] = confProperties.password;
        credentials[ConfProperties::REALM] = confProperties.realm.isEmpty() ? "*" : confProperties.realm;

        auto credentialsVec = confProperties.credentials;
        credentialsVec[0] = credentials;
        for (auto const& i : credentialsVec) {
            QMap<QString, QString> credMap;
            for (auto const& j : i.toStdMap()) {
                credMap[j.first] = j.second;
            }
            finalCred.append(credMap);
        }

        VectorMapStringString oldCredentials = ConfigurationManager::instance().getCredentials(accountId);
        if (oldCredentials.empty() || finalCred.empty()
            || oldCredentials[0][ConfProperties::PASSWORD] != finalCred[0][ConfProperties::PASSWORD]
            || oldCredentials[0][ConfProperties::REALM] != finalCred[0][ConfProperties::REALM]
            || oldCredentials[0][ConfProperties::USERNAME] != finalCred[0][ConfProperties::USERNAME])
            ConfigurationManager::instance().setCredentials(accountId, finalCred);
        details[ConfProperties::USERNAME] = confProperties.username;
        accountInfo.confProperties.credentials.swap(credentialsVec);
    }
    configurationManager.setAccountDetails(accountId, details);
}

account::ConfProperties_t
AccountModel::getAccountConfig(const QString& accountId) const
{
    return getAccountInfo(accountId).confProperties;
}

void
AccountModel::setAlias(const QString& accountId, const QString& alias, bool save)
{
    auto& accountInfo = pimpl_->getAccountInfo(accountId);
    if (accountInfo.profileInfo.alias == alias)
        return;
    accountInfo.profileInfo.alias = alias;

    if (save)
        ConfigurationManager::instance().updateProfile(accountId,
                                                       alias,
                                                       "",
                                                       "",
                                                       "",
                                                       5); // flag out of range to avoid updating avatar
    Q_EMIT profileUpdated(accountId);
}

void
AccountModel::setBotAccount(const QString& accountId, const QString& botOwner)
{
    auto& accountInfo = pimpl_->getAccountInfo(accountId);
    ConfigurationManager::instance().updateProfile(accountId, accountInfo.profileInfo.alias, "", "", botOwner, 0);
}

void
AccountModel::setAvatar(const QString& accountId, const QString& avatar, bool save, int flag)
{
    auto& accountInfo = pimpl_->getAccountInfo(accountId);
    if (accountInfo.profileInfo.avatar == avatar)
        return;
    accountInfo.profileInfo.avatar = avatar;
    if (save)
        ConfigurationManager::instance().updateProfile(accountId, accountInfo.profileInfo.alias, avatar, "PNG", "", flag);
    Q_EMIT profileUpdated(accountId);
}

bool
AccountModel::registerName(const QString& accountId, const QString& password, const QString& username)
{
    return ConfigurationManager::instance().registerName(accountId, username, "password", password);
}

bool
AccountModel::exportToFile(const QString& accountId, const QString& path, const QString& password) const
{
    return ConfigurationManager::instance().exportToFile(accountId, path, "password", password);
}

bool
AccountModel::provideAccountAuthentication(const QString& accountId, const QString& credentialsFromUser) const
{
    return ConfigurationManager::instance().provideAccountAuthentication(accountId,
                                                                         credentialsFromUser,
                                                                         credentialsFromUser.isEmpty() ? ""
                                                                                                       : "password");
}

int32_t
AccountModel::addDevice(const QString& accountId, const QString& token) const
{
    return ConfigurationManager::instance().addDevice(accountId, token);
}

bool
AccountModel::confirmAddDevice(const QString& accountId, uint32_t operationId) const
{
    return ConfigurationManager::instance().confirmAddDevice(accountId, operationId);
}

bool
AccountModel::cancelAddDevice(const QString& accountId, uint32_t operationId) const
{
    return ConfigurationManager::instance().cancelAddDevice(accountId, operationId);
}

void
AccountModel::removeAccount(const QString& accountId) const
{
    auto account = pimpl_->accounts.find(accountId);
    if (account != pimpl_->accounts.end()) {
        account->second.second->close();
    }

    // Close db here for its removal
    ConfigurationManager::instance().removeAccount(accountId);
}

bool
AccountModel::changeAccountPassword(const QString& accountId,
                                    const QString& currentPassword,
                                    const QString& newPassword) const
{
    return ConfigurationManager::instance().changeAccountPassword(accountId, currentPassword, newPassword);
}

const account::Info&
AccountModel::getAccountInfo(const QString& accountId) const
{
    auto accountInfo = pimpl_->accounts.find(accountId);
    if (accountInfo == pimpl_->accounts.end())
        throw std::out_of_range("AccountModel::getAccountInfo, unable to find account " + accountId.toStdString());

    return accountInfo->second.first;
}

AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
                                     Lrc& lrc,
                                     const CallbacksHandler& callbacksHandler,
                                     const BehaviorController& behaviorController)
    : linked(linked)
    , lrc {lrc}
    , behaviorController(behaviorController)
    , callbacksHandler(callbacksHandler)
    , username_changed(false)
{
    accountIdList = ConfigurationManager::instance().getAccountList();

    // NOTE: If the daemon is down, but dbus answered, id can contains
    // "Remote peer disconnected", "The name is not activable", etc.
    // So avoid to migrate useless directories.
    for (const auto& id : accountIdList)
        if (id.indexOf(" ") != -1) {
            qWarning() << "Invalid dbus answer. Daemon not running";
            return;
        }

    for (const auto& id : accountIdList) {
        addToAccounts(id);
    }

    connect(&callbacksHandler, &CallbacksHandler::accountsChanged, this, &AccountModelPimpl::updateAccounts);
    connect(&callbacksHandler,
            &CallbacksHandler::accountStatusChanged,
            this,
            &AccountModelPimpl::slotAccountStatusChanged);
    connect(&callbacksHandler,
            &CallbacksHandler::accountDetailsChanged,
            this,
            &AccountModelPimpl::slotAccountDetailsChanged);
    connect(&callbacksHandler,
            &CallbacksHandler::volatileAccountDetailsChanged,
            this,
            &AccountModelPimpl::slotVolatileAccountDetailsChanged);
    connect(&callbacksHandler,
            &CallbacksHandler::deviceAuthStateChanged,
            &linked,
            &AccountModel::deviceAuthStateChanged);
    connect(&callbacksHandler, &CallbacksHandler::addDeviceStateChanged, &linked, &AccountModel::addDeviceStateChanged);
    connect(&callbacksHandler,
            &CallbacksHandler::nameRegistrationEnded,
            this,
            &AccountModelPimpl::slotNameRegistrationEnded);
    connect(&callbacksHandler,
            &CallbacksHandler::registeredNameFound,
            this,
            &AccountModelPimpl::slotRegisteredNameFound);
    connect(&callbacksHandler, &CallbacksHandler::migrationEnded, this, &AccountModelPimpl::slotMigrationEnded);
    connect(&callbacksHandler, &CallbacksHandler::newPosition, this, &AccountModelPimpl::slotNewPosition);
}

AccountModelPimpl::~AccountModelPimpl() {}

void
AccountModelPimpl::updateAccounts()
{
    ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
    const auto previousAccountIdListSize = accountIdList.size();
    accountIdList = configurationManager.getAccountList();

    // If this is just a reordering of the accounts, just notify the view.
    if (accountIdList.size() == previousAccountIdListSize) {
        beginAccountsReset();
        endAccountsReset();
        Q_EMIT linked.accountsReordered();
        return;
    }

    LC_DBG << "Syncing account list";

    beginAccountsReset();

    // Detect removed accounts
    QStringList toBeRemoved;
    for (auto& it : accounts) {
        auto& accountInfo = it.second.first;
        if (!accountIdList.contains(accountInfo.id)) {
            qDebug() << QString("detected account removal %1").arg(accountInfo.id);
            toBeRemoved.push_back(accountInfo.id);
        }
    }

    for (auto it = toBeRemoved.begin(); it != toBeRemoved.end(); ++it) {
        removeFromAccounts(*it);
    }

    // Detect new accounts
    for (auto& id : accountIdList) {
        auto account = accounts.find(id);
        // NOTE: If the daemon is down, but dbus answered, id can contains
        // "Remote peer disconnected", "The name is not activable", etc.
        // So avoid to create useless directories.
        if (account == accounts.end() && !id.isEmpty() && id.indexOf(" ") == -1) {
            qDebug() << QString("detected new account %1").arg(id);
            addToAccounts(id);
            auto updatedAccount = accounts.find(id);
            if (updatedAccount == accounts.end()) {
                endAccountsReset();
                return;
            }
            if (updatedAccount->second.first.profileInfo.type == profile::Type::SIP) {
                Q_EMIT linked.accountAdded(id);
            }
        }
    }

    endAccountsReset();
}

void
AccountModelPimpl::updateAccountDetails(account::Info& accountInfo)
{
    // Fill account::Info struct with details from daemon
    MapStringString details = ConfigurationManager::instance().getAccountDetails(accountInfo.id);
    accountInfo.fromDetails(details);

    // Fill account::Info::confProperties credentials
    VectorMapStringString credGet = ConfigurationManager::instance().getCredentials(accountInfo.id);
    VectorMapStringString credToStore;
    for (auto const& i : std::vector<MapStringString>(credGet.begin(), credGet.end())) {
        MapStringString credMap;
        for (auto const& j : i.toStdMap()) {
            credMap[j.first] = j.second;
        }
        credToStore.push_back(credMap);
    }

    accountInfo.confProperties.credentials.swap(credToStore);
}

account::Info&
AccountModelPimpl::getAccountInfo(const QString& accountId)
{
    auto account = accounts.find(accountId);
    if (account == accounts.end()) {
        throw std::out_of_range("AccountModelPimpl::getAccountInfo, unable to find " + accountId.toStdString());
    }
    return account->second.first;
}

void
AccountModelPimpl::slotAccountStatusChanged(const QString& accountID, const api::account::Status status)
{
    if (status == api::account::Status::STATUS_INVALID) {
        Q_EMIT linked.invalidAccountDetected(accountID);
        return;
    }
    auto it = accounts.find(accountID);

    // If account is not in the map yet, don't add it, it is updateAccounts's job
    if (it == accounts.end()) {
        return;
    }

    auto& accountInfo = it->second.first;

    if (accountInfo.profileInfo.type != profile::Type::SIP) {
        if (status != api::account::Status::INITIALIZING && accountInfo.status == api::account::Status::INITIALIZING) {
            // Detect when a new account is generated (keys are ready). During
            // the generation, an account got the "INITIALIZING" status.
            // When keys are generated, the status will change.
            // The account is already added and initialized. Just update details from daemon
            updateAccountDetails(accountInfo);
            // This will load swarms as the account was not loaded before.
            accountInfo.contactModel->initContacts();
            accountInfo.conversationModel->initConversations();

            // Replay conversation state once more after activation so request
            // propagation has time to settle without requiring a full restart.
            QTimer::singleShot(1000,
                               accountInfo.conversationModel.get(),
                               [conversationModel = accountInfo.conversationModel.get()]() {
                                   if (conversationModel) {
                                       conversationModel->initConversations();
                                   }
                               });
            Q_EMIT linked.accountAdded(accountID);
        } else if (!accountInfo.profileInfo.uri.isEmpty()) {
            accountInfo.status = status;
            Q_EMIT linked.accountStatusChanged(accountID);
            emitDataChanged(accountID);
        }
    } else {
        accountInfo.status = status;
        Q_EMIT linked.accountStatusChanged(accountID);
        emitDataChanged(accountID);
    }
}

void
AccountModelPimpl::slotAccountDetailsChanged(const QString& accountId, const MapStringString& details)
{
    auto account = accounts.find(accountId);
    if (account == accounts.end()) {
        qWarning() << Q_FUNC_INFO << ": unable to find " << accountId;
        return;
    }
    auto& accountInfo = account->second.first;
    accountInfo.fromDetails(details);
    if (username_changed) {
        username_changed = false;
        accountInfo.registeredName = new_username;
        Q_EMIT linked.profileUpdated(accountId);
        emitDataChanged(accountId);
    }
    // TODO: Remove accountStatusChanged here.
    Q_EMIT linked.accountStatusChanged(accountId);
    emitDataChanged(accountId);
    Q_EMIT linked.accountDetailsChanged(accountId);
    emitDataChanged(accountId);
}

void
AccountModelPimpl::slotVolatileAccountDetailsChanged(const QString& accountId, const MapStringString& details)
{
    auto account = accounts.find(accountId);
    if (account == accounts.end()) {
        LC_DBG << Q_FUNC_INFO << ": unable to find " << accountId;
        return;
    }
    auto& accountInfo = account->second.first;

    auto new_usernameIt = details.find(libjami::Account::VolatileProperties::REGISTERED_NAME);
    if (new_usernameIt == details.end())
        return;
    accountInfo.registeredName = new_usernameIt.value();

    auto new_deviceId = details.find(libjami::Account::ConfProperties::DEVICE_ID);
    if (new_deviceId != details.end())
        accountInfo.confProperties.deviceId = new_deviceId.value();

    auto currentProxyServer = details.find(libjami::Account::ConfProperties::PROXY_SERVER);
    if (currentProxyServer != details.end())
        accountInfo.confProperties.currentProxyServer = currentProxyServer.value();

    Q_EMIT linked.profileUpdated(accountId);
    emitDataChanged(accountId);
}

void
AccountModelPimpl::slotDeviceAuthStateChanged(const QString& accountId, int state, const MapStringString& details)
{
    // implement business logic here
    // can be bypassed with a signal to signal
    Q_EMIT linked.deviceAuthStateChanged(accountId, state, details);
}

void
AccountModelPimpl::slotAddDeviceStateChanged(const QString& accountId,
                                             uint32_t operationId,
                                             int state,
                                             const MapStringString& details)
{
    Q_EMIT linked.addDeviceStateChanged(accountId, operationId, state, details);
}

void
AccountModelPimpl::slotNameRegistrationEnded(const QString& accountId, int status, const QString& name)
{
    account::RegisterNameStatus convertedStatus = account::RegisterNameStatus::RNS_INVALID;
    switch (status) {
    case 0: {
        convertedStatus = account::RegisterNameStatus::SUCCESS;
        auto account = accounts.find(accountId);
        if (account != accounts.end() && account->second.first.registeredName.isEmpty()) {
            auto conf = linked.getAccountConfig(accountId);
            username_changed = true;
            new_username = name;
            linked.setAccountConfig(accountId, conf);
        }
        break;
    }
    case 1:
        convertedStatus = account::RegisterNameStatus::WRONG_PASSWORD;
        break;
    case 2:
        convertedStatus = account::RegisterNameStatus::RNS_INVALID_NAME;
        break;
    case 3:
        convertedStatus = account::RegisterNameStatus::ALREADY_TAKEN;
        break;
    case 4:
        convertedStatus = account::RegisterNameStatus::NETWORK_ERROR;
        break;
    default:
        break;
    }
    Q_EMIT linked.nameRegistrationEnded(accountId, convertedStatus, name);
}

void
AccountModelPimpl::slotRegisteredNameFound(const QString& accountId,
                                           const QString& requestedName,
                                           int status,
                                           const QString& address,
                                           const QString& registeredName)
{
    account::LookupStatus convertedStatus = account::LookupStatus::LOOKUP_INVALID;
    switch (status) {
    case 0:
        convertedStatus = account::LookupStatus::SUCCESS;
        break;
    case 1:
        convertedStatus = account::LookupStatus::LOOKUP_INVALID_NAME;
        break;
    case 2:
        convertedStatus = account::LookupStatus::NOT_FOUND;
        break;
    case 3:
        convertedStatus = account::LookupStatus::ERROR;
        break;
    default:
        break;
    }
    Q_EMIT linked.registeredNameFound(accountId, requestedName, convertedStatus, address, registeredName);
}

void
AccountModelPimpl::slotMigrationEnded(const QString& accountId, bool ok)
{
    if (ok) {
        auto it = accounts.find(accountId);
        if (it == accounts.end()) {
            addToAccounts(accountId);
            return;
        }
        auto& accountInfo = it->second.first;
        updateAccountDetails(accountInfo);
        // Contacts and conversations are normally initialized by the ContactModelPimpl and
        // ConversationModelPimpl constructors. However, if a migration is triggered when the
        // daemon first tries loading an account, then the account won't be loaded at the time
        // the constructors are called, so the daemon will send empty contact and conversation
        // lists to the client. Initializing the models once the migration is done ensures that
        // the user will be able to see the conversations they had prior to the migration
        // without having to restart the application.
        accountInfo.contactModel->initContacts();
        accountInfo.conversationModel->initConversations();
    }
    Q_EMIT linked.migrationEnded(accountId, ok);
}

void
AccountModelPimpl::slotNewPosition(const QString& accountId,
                                   const QString& peerId,
                                   const QString& body,
                                   const uint64_t& timestamp,
                                   const QString& daemonId) const
{
    Q_EMIT linked.newPosition(accountId, peerId, body, timestamp, daemonId);
}

void
AccountModelPimpl::slotAccountProfileReceived(const QString& accountId,
                                              const QString& displayName,
                                              const QString& userPhoto)
{
    LC_WARN << accountId << displayName;
    auto account = accounts.find(accountId);
    if (account == accounts.end() || userPhoto.isEmpty())
        return;
    // NOTE: This signal is still used for JAMS account where the avatar is
    // retrieved from the server. In this case we MUST save it.
    auto& accountInfo = account->second.first;
    accountInfo.profileInfo.avatar = userPhoto;
    accountInfo.profileInfo.alias = displayName;
    storage::vcard::setProfile(accountInfo.id, accountInfo.profileInfo);
    Q_EMIT linked.profileUpdated(accountId);
    emitDataChanged(accountId);
}

void
AccountModelPimpl::addToAccounts(const QString& accountId)
{
    auto appPath = storage::getPath();
    auto dbName = accountId + "/history";

    // Create and load the database.
    auto db = std::make_shared<Database>(dbName, appPath);
    try {
        db->load();
    } catch (const std::exception& e) {
        LC_WARN << e.what();
        return;
    }

    // Create the profiles path if necessary.
    QDir profilesDir(appPath + accountId + "/profiles");
    if (!profilesDir.exists()) {
        profilesDir.mkpath(".");
    }

    auto it = accounts.emplace(accountId, std::make_pair(account::Info(), db));
    if (!it.second) {
        LC_WARN << "Failed to add new account: id already present in map";
        return;
    }

    // Initialize the profile.
    account::Info& newAccInfo = (it.first)->second.first;
    newAccInfo.id = accountId;
    newAccInfo.profileInfo.avatar = storage::getAccountAvatar(accountId);
    updateAccountDetails(newAccInfo);

    // Initialize models for this account.
    newAccInfo.accountModel = &linked;
    newAccInfo.callModel = std::make_unique<CallModel>(newAccInfo, lrc, callbacksHandler, behaviorController);
    newAccInfo.contactModel = std::make_unique<ContactModel>(newAccInfo, *db, callbacksHandler, behaviorController);
    newAccInfo.conversationModel = std::make_unique<ConversationModel>(newAccInfo,
                                                                       lrc,
                                                                       *db,
                                                                       callbacksHandler,
                                                                       behaviorController);
    newAccInfo.peerDiscoveryModel = std::make_unique<PeerDiscoveryModel>(callbacksHandler, accountId);
    newAccInfo.deviceModel = std::make_unique<DeviceModel>(newAccInfo, callbacksHandler);
    newAccInfo.codecModel = std::make_unique<CodecModel>(newAccInfo, callbacksHandler);
    newAccInfo.dataTransferModel = std::make_unique<DataTransferModel>();
}

void
AccountModelPimpl::removeFromAccounts(const QString& accountId)
{
    /* Update db before waiting for the client to stop using the structs is fine
       as long as we don't free anything */
    auto account = accounts.find(accountId);
    if (account == accounts.end()) {
        return;
    }
    auto& accountInfo = account->second.first;
    if (accountInfo.profileInfo.type == profile::Type::SIP) {
        auto accountDir = QDir(storage::getPath() + accountId);
        accountDir.removeRecursively();
    }

    /* Inform client about account removal. Do *not* free account structures
       before we are sure that the client stopped using it, otherwise we might
       get into use-after-free troubles. */
    accountIdList.removeOne(accountId);
    Q_EMIT linked.accountRemoved(accountId);

    // Now we can free them
    accounts.erase(accountId);
}

void
account::Info::fromDetails(const MapStringString& details)
{
    using namespace libjami::Account;
    const MapStringString volatileDetails = ConfigurationManager::instance().getVolatileAccountDetails(id);

    // General
    if (details[ConfProperties::TYPE] != "")
        profileInfo.type = details[ConfProperties::TYPE] == QString(ProtocolNames::RING) ? profile::Type::JAMI
                                                                                         : profile::Type::SIP;
    registeredName = profileInfo.type == profile::Type::JAMI ? volatileDetails[VolatileProperties::REGISTERED_NAME]
                                                             : "";
    profileInfo.alias = details[ConfProperties::DISPLAYNAME];
    enabled = toBool(details[ConfProperties::ENABLED]);
    status = lrc::api::account::to_status(volatileDetails[libjami::Account::ConfProperties::Registration::STATUS]);
    confProperties.mailbox = details[ConfProperties::MAILBOX];
    confProperties.dtmfType = details[ConfProperties::DTMF_TYPE];
    confProperties.autoAnswer = toBool(details[ConfProperties::AUTOANSWER]);
    confProperties.denySecondCall = toBool(details[ConfProperties::DENYSECONDCALL]);
    confProperties.sendReadReceipt = toBool(details[ConfProperties::SENDREADRECEIPT]);
    confProperties.sendComposing = toBool(details[ConfProperties::SENDCOMPOSING]);
    confProperties.isRendezVous = toBool(details[ConfProperties::ISRENDEZVOUS]);
    confProperties.activeCallLimit = toInt(details[ConfProperties::ACTIVE_CALL_LIMIT]);
    confProperties.hostname = details[ConfProperties::HOSTNAME];
    profileInfo.uri = (profileInfo.type == profile::Type::JAMI and details[ConfProperties::USERNAME].contains("ring:"))
                          ? QString(details[ConfProperties::USERNAME]).remove(QString("ring:"))
                          : details[ConfProperties::USERNAME];
    confProperties.username = details[ConfProperties::USERNAME];
    confProperties.routeset = details[ConfProperties::ROUTE];
    confProperties.password = details[ConfProperties::PASSWORD];
    confProperties.realm = details[ConfProperties::REALM];
    confProperties.localInterface = details[ConfProperties::LOCAL_INTERFACE];
    confProperties.deviceId = volatileDetails[ConfProperties::DEVICE_ID];
    profileInfo.botOwner = storage::getBotOwner(id);
    confProperties.deviceName = details[ConfProperties::DEVICE_NAME];
    confProperties.publishedSameAsLocal = toBool(details[ConfProperties::PUBLISHED_SAMEAS_LOCAL]);
    confProperties.localPort = toInt(details[ConfProperties::LOCAL_PORT]);
    confProperties.publishedPort = toInt(details[ConfProperties::PUBLISHED_PORT]);
    confProperties.registrationExpire = toInt(details[ConfProperties::Registration::EXPIRE]);
    confProperties.publishedAddress = details[ConfProperties::PUBLISHED_ADDRESS];
    confProperties.userAgent = details[ConfProperties::USER_AGENT];
    confProperties.upnpEnabled = toBool(details[ConfProperties::UPNP_ENABLED]);
    confProperties.hasCustomUserAgent = toBool(details[ConfProperties::HAS_CUSTOM_USER_AGENT]);
    confProperties.allowIncoming = toBool(details[ConfProperties::ALLOW_CERT_FROM_HISTORY])
                                   || toBool(details[ConfProperties::ALLOW_CERT_FROM_CONTACT])
                                   || toBool(details[ConfProperties::ALLOW_CERT_FROM_TRUSTED]);
    confProperties.allowIPAutoRewrite = toBool(details[ConfProperties::ACCOUNT_IP_AUTO_REWRITE]);
    confProperties.archivePassword = details[ConfProperties::ARCHIVE_PASSWORD];
    confProperties.archiveHasPassword = toBool(details[ConfProperties::ARCHIVE_HAS_PASSWORD]);
    confProperties.archivePath = details[ConfProperties::ARCHIVE_PATH];
    confProperties.proxyEnabled = toBool(details[ConfProperties::PROXY_ENABLED]);
    confProperties.proxyServer = details[ConfProperties::PROXY_SERVER];
    confProperties.currentProxyServer = volatileDetails[ConfProperties::PROXY_SERVER];
    confProperties.proxyPushToken = details[ConfProperties::PROXY_PUSH_TOKEN];
    confProperties.dhtPort = toInt(details[ConfProperties::DHT_PORT]);
    confProperties.peerDiscovery = toBool(details[ConfProperties::DHT_PEER_DISCOVERY]);
    confProperties.accountDiscovery = toBool(details[ConfProperties::ACCOUNT_PEER_DISCOVERY]);
    confProperties.accountPublish = toBool(details[ConfProperties::ACCOUNT_PUBLISH]);
    confProperties.keepAliveEnabled = toBool(details[ConfProperties::KEEP_ALIVE_ENABLED]);
    confProperties.bootstrapListUrl = QString(details[ConfProperties::BOOTSTRAP_LIST_URL]);
    confProperties.dhtProxyListUrl = QString(details[ConfProperties::DHT_PROXY_LIST_URL]);
    confProperties.proxyListEnabled = toBool(details[ConfProperties::PROXY_LIST_ENABLED]);
    confProperties.defaultModerators = QString(details[ConfProperties::DEFAULT_MODERATORS]);
    confProperties.localModeratorsEnabled = toBool(details[ConfProperties::LOCAL_MODERATORS_ENABLED]);
    // Audio
    confProperties.Audio.audioPortMax = toInt(details[ConfProperties::Audio::PORT_MAX]);
    confProperties.Audio.audioPortMin = toInt(details[ConfProperties::Audio::PORT_MIN]);
    // Video
    confProperties.Video.videoEnabled = toBool(details[ConfProperties::Video::ENABLED]);
    confProperties.Video.videoPortMax = toInt(details[ConfProperties::Video::PORT_MAX]);
    confProperties.Video.videoPortMin = toInt(details[ConfProperties::Video::PORT_MIN]);
    // STUN
    confProperties.STUN.server = details[ConfProperties::STUN::SERVER];
    confProperties.STUN.enable = toBool(details[ConfProperties::STUN::ENABLED]);
    // TURN
    confProperties.TURN.server = details[ConfProperties::TURN::SERVER];
    confProperties.TURN.enable = toBool(details[ConfProperties::TURN::ENABLED]);
    confProperties.TURN.username = details[ConfProperties::TURN::SERVER_UNAME];
    confProperties.TURN.password = details[ConfProperties::TURN::SERVER_PWD];
    confProperties.TURN.realm = details[ConfProperties::TURN::SERVER_REALM];
    // Presence
    confProperties.Presence.presencePublishSupported = toBool(details[ConfProperties::Presence::SUPPORT_PUBLISH]);
    confProperties.Presence.presenceSubscribeSupported = toBool(details[ConfProperties::Presence::SUPPORT_SUBSCRIBE]);
    confProperties.Presence.presenceEnabled = toBool(details[ConfProperties::Presence::ENABLED]);
    // Ringtone
    confProperties.Ringtone.ringtonePath = details[ConfProperties::Ringtone::PATH];
    confProperties.Ringtone.ringtoneEnabled = toBool(details[ConfProperties::Ringtone::ENABLED]);
    // SRTP
    confProperties.SRTP.keyExchange = details[ConfProperties::SRTP::KEY_EXCHANGE].isEmpty()
                                          ? account::KeyExchangeProtocol::NONE
                                          : account::KeyExchangeProtocol::SDES;
    confProperties.SRTP.enable = toBool(details[ConfProperties::SRTP::ENABLED]);
    // TLS
    confProperties.TLS.listenerPort = toInt(details[ConfProperties::TLS::LISTENER_PORT]);
    confProperties.TLS.enable = details[ConfProperties::TYPE] == QString(ProtocolNames::RING)
                                    ? true
                                    : toBool(details[ConfProperties::TLS::ENABLED]);
    confProperties.TLS.port = toInt(details[ConfProperties::TLS::PORT]);
    confProperties.TLS.certificateListFile = details[ConfProperties::TLS::CA_LIST_FILE];
    confProperties.TLS.certificateFile = details[ConfProperties::TLS::CERTIFICATE_FILE];
    confProperties.TLS.privateKeyFile = details[ConfProperties::TLS::PRIVATE_KEY_FILE];
    confProperties.TLS.password = details[ConfProperties::TLS::PASSWORD];
    confProperties.TLS.verifyServer = toBool(details[ConfProperties::TLS::VERIFY_SERVER]);
    confProperties.TLS.verifyClient = toBool(details[ConfProperties::TLS::VERIFY_CLIENT]);
    confProperties.TLS.requireClientCertificate = toBool(details[ConfProperties::TLS::REQUIRE_CLIENT_CERTIFICATE]);
    confProperties.TLS.disableSecureDlgCheck = toBool(details[ConfProperties::TLS::DISABLE_SECURE_DLG_CHECK]);
    // DHT
    confProperties.DHT.PublicInCalls = toBool(details[ConfProperties::DHT::PUBLIC_IN_CALLS]);
    confProperties.DHT.AllowFromTrusted = toBool(details[ConfProperties::DHT::ALLOW_FROM_TRUSTED]);
    // Nameserver
    confProperties.Nameserver.uri = details[ConfProperties::Nameserver::URI];
    confProperties.Nameserver.account = details[ConfProperties::Nameserver::ACCOUNT];
    // Jams
    confProperties.managerUri = details[ConfProperties::MANAGER_URI];
    confProperties.managerUsername = details[ConfProperties::MANAGER_USERNAME];
    // uiCustomization
    QJsonDocument doc = QJsonDocument::fromJson(details[ConfProperties::UI_CUSTOMIZATION].toUtf8());
    if (!doc.isNull() && doc.isObject()) {
        confProperties.uiCustomization = doc.object();
    }
}

MapStringString
account::ConfProperties_t::toDetails() const
{
    using namespace libjami::Account;
    MapStringString details;
    // General
    details[ConfProperties::MAILBOX] = this->mailbox;
    details[ConfProperties::DTMF_TYPE] = this->dtmfType;
    details[ConfProperties::AUTOANSWER] = toQString(this->autoAnswer);
    details[ConfProperties::DENYSECONDCALL] = toQString(this->denySecondCall);
    details[ConfProperties::SENDREADRECEIPT] = toQString(this->sendReadReceipt);
    details[ConfProperties::SENDCOMPOSING] = toQString(this->sendComposing);
    details[ConfProperties::ISRENDEZVOUS] = toQString(this->isRendezVous);
    details[ConfProperties::ACTIVE_CALL_LIMIT] = toQString(this->activeCallLimit);
    details[ConfProperties::HOSTNAME] = this->hostname;
    details[ConfProperties::ROUTE] = this->routeset;
    details[ConfProperties::PASSWORD] = this->password;
    details[ConfProperties::REALM] = this->realm;
    details[ConfProperties::DEVICE_ID] = this->deviceId;
    details[ConfProperties::DEVICE_NAME] = this->deviceName;
    details[ConfProperties::LOCAL_INTERFACE] = this->localInterface;
    details[ConfProperties::PUBLISHED_SAMEAS_LOCAL] = toQString(this->publishedSameAsLocal);
    details[ConfProperties::LOCAL_PORT] = toQString(this->localPort);
    details[ConfProperties::PUBLISHED_PORT] = toQString(this->publishedPort);
    details[ConfProperties::Registration::EXPIRE] = toQString(this->registrationExpire);
    details[ConfProperties::PUBLISHED_ADDRESS] = this->publishedAddress;
    details[ConfProperties::USER_AGENT] = this->userAgent;
    details[ConfProperties::UPNP_ENABLED] = toQString(this->upnpEnabled);
    details[ConfProperties::HAS_CUSTOM_USER_AGENT] = toQString(this->hasCustomUserAgent);
    details[ConfProperties::ALLOW_CERT_FROM_HISTORY] = toQString(this->allowIncoming);
    details[ConfProperties::ALLOW_CERT_FROM_CONTACT] = toQString(this->allowIncoming);
    details[ConfProperties::ALLOW_CERT_FROM_TRUSTED] = toQString(this->allowIncoming);
    details[ConfProperties::ACCOUNT_IP_AUTO_REWRITE] = toQString(this->allowIPAutoRewrite);
    details[ConfProperties::ARCHIVE_PASSWORD] = this->archivePassword;
    details[ConfProperties::ARCHIVE_HAS_PASSWORD] = toQString(this->archiveHasPassword);
    details[ConfProperties::ARCHIVE_PATH] = this->archivePath;
    // ConfProperties::DEVICE_NAME name is set with DeviceModel interface
    details[ConfProperties::PROXY_ENABLED] = toQString(this->proxyEnabled);
    details[ConfProperties::PROXY_SERVER] = this->proxyServer;
    details[ConfProperties::PROXY_PUSH_TOKEN] = this->proxyPushToken;
    details[ConfProperties::DHT_PORT] = toQString(this->dhtPort);
    details[ConfProperties::DHT_PEER_DISCOVERY] = toQString(this->peerDiscovery);
    details[ConfProperties::ACCOUNT_PEER_DISCOVERY] = toQString(this->accountDiscovery);
    details[ConfProperties::ACCOUNT_PUBLISH] = toQString(this->accountPublish);
    details[ConfProperties::KEEP_ALIVE_ENABLED] = toQString(this->keepAliveEnabled);
    details[ConfProperties::BOOTSTRAP_LIST_URL] = this->bootstrapListUrl;
    details[ConfProperties::PROXY_LIST_ENABLED] = toQString(this->proxyListEnabled);
    details[ConfProperties::DHT_PROXY_LIST_URL] = this->dhtProxyListUrl;
    details[ConfProperties::DEFAULT_MODERATORS] = this->defaultModerators;
    details[ConfProperties::LOCAL_MODERATORS_ENABLED] = toQString(this->localModeratorsEnabled);
    // Audio
    details[ConfProperties::Audio::PORT_MAX] = toQString(this->Audio.audioPortMax);
    details[ConfProperties::Audio::PORT_MIN] = toQString(this->Audio.audioPortMin);
    // Video
    details[ConfProperties::Video::ENABLED] = toQString(this->Video.videoEnabled);
    details[ConfProperties::Video::PORT_MAX] = toQString(this->Video.videoPortMax);
    details[ConfProperties::Video::PORT_MIN] = toQString(this->Video.videoPortMin);
    // STUN
    details[ConfProperties::STUN::SERVER] = this->STUN.server;
    details[ConfProperties::STUN::ENABLED] = toQString(this->STUN.enable);
    // TURN
    details[ConfProperties::TURN::SERVER] = this->TURN.server;
    details[ConfProperties::TURN::ENABLED] = toQString(this->TURN.enable);
    details[ConfProperties::TURN::SERVER_UNAME] = this->TURN.username;
    details[ConfProperties::TURN::SERVER_PWD] = this->TURN.password;
    details[ConfProperties::TURN::SERVER_REALM] = this->TURN.realm;
    // Presence
    details[ConfProperties::Presence::SUPPORT_PUBLISH] = toQString(this->Presence.presencePublishSupported);
    details[ConfProperties::Presence::SUPPORT_SUBSCRIBE] = toQString(this->Presence.presenceSubscribeSupported);
    details[ConfProperties::Presence::ENABLED] = toQString(this->Presence.presenceEnabled);
    // Ringtone
    details[ConfProperties::Ringtone::PATH] = this->Ringtone.ringtonePath;
    details[ConfProperties::Ringtone::ENABLED] = toQString(this->Ringtone.ringtoneEnabled);
    // SRTP
    details[ConfProperties::SRTP::KEY_EXCHANGE] = this->SRTP.keyExchange == account::KeyExchangeProtocol::NONE ? ""
                                                                                                               : "sdes";
    details[ConfProperties::SRTP::ENABLED] = toQString(this->SRTP.enable);
    // TLS
    details[ConfProperties::TLS::LISTENER_PORT] = toQString(this->TLS.listenerPort);
    details[ConfProperties::TLS::ENABLED] = toQString(this->TLS.enable);
    details[ConfProperties::TLS::PORT] = toQString(this->TLS.port);
    details[ConfProperties::TLS::CA_LIST_FILE] = this->TLS.certificateListFile;
    details[ConfProperties::TLS::CERTIFICATE_FILE] = this->TLS.certificateFile;
    details[ConfProperties::TLS::PRIVATE_KEY_FILE] = this->TLS.privateKeyFile;
    details[ConfProperties::TLS::PASSWORD] = this->TLS.password;
    details[ConfProperties::TLS::VERIFY_SERVER] = toQString(this->TLS.verifyServer);
    details[ConfProperties::TLS::VERIFY_CLIENT] = toQString(this->TLS.verifyClient);
    details[ConfProperties::TLS::REQUIRE_CLIENT_CERTIFICATE] = toQString(this->TLS.requireClientCertificate);
    details[ConfProperties::TLS::DISABLE_SECURE_DLG_CHECK] = toQString(this->TLS.disableSecureDlgCheck);
    // DHT
    details[ConfProperties::DHT::PUBLIC_IN_CALLS] = toQString(this->DHT.PublicInCalls);
    details[ConfProperties::DHT::ALLOW_FROM_TRUSTED] = toQString(this->DHT.AllowFromTrusted);
    // Nameserver
    details[ConfProperties::Nameserver::URI] = this->Nameserver.uri;
    details[ConfProperties::Nameserver::ACCOUNT] = this->Nameserver.account;
    // Manager
    details[ConfProperties::MANAGER_URI] = this->managerUri;
    details[ConfProperties::MANAGER_USERNAME] = this->managerUsername;
    // UI Customization
    QJsonDocument doc(this->uiCustomization);
    details[ConfProperties::UI_CUSTOMIZATION] = doc.toJson(QJsonDocument::Compact);

    return details;
}

QString
AccountModel::createNewAccount(profile::Type type,
                               const MapStringString& config,
                               const QString& displayName,
                               const QString& archivePath,
                               const QString& password,
                               const QString& uri)
{
    // Get the template for the account type to prefill the details
    MapStringString details = type == profile::Type::SIP ? ConfigurationManager::instance().getAccountTemplate("SIP")
                                                         : ConfigurationManager::instance().getAccountTemplate("RING");

    // Add the supplied config to the details
    if (!config.isEmpty()) {
        for (MapStringString::const_iterator it = config.begin(); it != config.end(); it++) {
            details[it.key()] = it.value();
        }
    }

    using namespace libjami::Account;

    // Add the rest of the details if we are not creating an ephemeral account for linking
    // in which case the ARCHIVE_URL was set to "jami-auth" or the MANAGER_URI was set to
    // the account manager URI in the case of a remote account manager connection
    if (details[ConfProperties::ARCHIVE_URL].isEmpty() && details[ConfProperties::MANAGER_URI].isEmpty()) {
        details[ConfProperties::TYPE] = type == profile::Type::SIP ? "SIP" : "RING";
        details[ConfProperties::DISPLAYNAME] = displayName;
        details[ConfProperties::ALIAS] = displayName;
        details[ConfProperties::UPNP_ENABLED] = "true";
        details[ConfProperties::ARCHIVE_PASSWORD] = password;
        details[ConfProperties::ARCHIVE_PATH] = archivePath;

        // Override the username with the provided URI if it's a SIP account
        if (type == profile::Type::SIP) {
            details[ConfProperties::USERNAME] = uri;
        }
    }

    // Actually add the account and return the account ID
    QString accountId = ConfigurationManager::instance().addAccount(details);
    return accountId;
}

QString
AccountModel::connectToAccountManager(const QString& username,
                                      const QString& password,
                                      const QString& serverUri,
                                      const MapStringString& config)
{
    MapStringString details = config;
    using namespace libjami::Account;
    details[ConfProperties::TYPE] = "RING";
    details[ConfProperties::MANAGER_URI] = serverUri;
    details[ConfProperties::MANAGER_USERNAME] = username;
    details[ConfProperties::ARCHIVE_PASSWORD] = password;
    return createNewAccount(profile::Type::JAMI, details);
}

QString
AccountModel::createDeviceImportAccount()
{
    // auto details = ConfigurationManager::instance().getAccountTemplate("RING");
    MapStringString details;
    using namespace libjami::Account;
    details[ConfProperties::TYPE] = "RING";
    details[ConfProperties::ARCHIVE_URL] = "jami-auth";
    return createNewAccount(profile::Type::JAMI, details);
}

void
AccountModel::setTopAccount(const QString& accountId)
{
    bool found = false;
    QString order = {};

    const QStringList accountIds = ConfigurationManager::instance().getAccountList();
    for (auto& id : accountIds) {
        if (id == accountId) {
            found = true;
        } else {
            order += id + "/";
        }
    }
    if (found) {
        order = accountId + "/" + order;
    }
    ConfigurationManager::instance().setAccountsOrder(order);
}

QString
AccountModel::accountVCard(const QString& accountId, bool compressImage) const
{
    return storage::vcard::profileToVcard(getAccountInfo(accountId).profileInfo, compressImage);
}

const QString
AccountModel::bestNameForAccount(const QString& accountID) const
{
    // Order: alias, registeredName, uri
    auto& accountInfo = getAccountInfo(accountID);

    auto alias = accountInfo.profileInfo.alias.simplified();
    auto registeredName = accountInfo.registeredName.simplified();
    auto infoHash = accountInfo.profileInfo.uri.simplified();

    if (alias.isEmpty()) {
        if (registeredName.isEmpty())
            return infoHash;
        else
            return registeredName;
    }
    return alias;
}

const QString
AccountModel::bestIdForAccount(const QString& accountID) const
{
    // Order: registeredName, uri after best name
    //        return empty string if duplicated with best name
    auto& accountInfo = getAccountInfo(accountID);

    auto registeredName = accountInfo.registeredName.simplified();
    auto infoHash = accountInfo.profileInfo.uri.simplified();

    return registeredName.isEmpty() ? infoHash : registeredName;
}

void
AccountModel::setDefaultModerator(const QString& accountID, const QString& peerURI, const bool& state)
{
    ConfigurationManager::instance().setDefaultModerator(accountID, peerURI, state);
}

QStringList
AccountModel::getDefaultModerators(const QString& accountID)
{
    return ConfigurationManager::instance().getDefaultModerators(accountID);
}

void
AccountModel::enableLocalModerators(const QString& accountID, const bool& isModEnabled)
{
    ConfigurationManager::instance().enableLocalModerators(accountID, isModEnabled);
}

bool
AccountModel::isLocalModeratorsEnabled(const QString& accountID)
{
    return ConfigurationManager::instance().isLocalModeratorsEnabled(accountID);
}

void
AccountModel::setAllModerators(const QString& accountID, const bool& allModerators)
{
    ConfigurationManager::instance().setAllModerators(accountID, allModerators);
}

bool
AccountModel::isAllModerators(const QString& accountID)
{
    return ConfigurationManager::instance().isAllModerators(accountID);
}

int
AccountModel::notificationsCount() const
{
    auto total = 0;
    for (const auto& [_id, account] : pimpl_->accounts) {
        total += account.first.conversationModel->notificationsCount();
    }
    return total;
}

void
AccountModel::reloadHistory()
{
    for (const auto& [_id, account] : pimpl_->accounts) {
        account.first.conversationModel->reloadHistory();
    }
}

QString
AccountModel::avatar(const QString& accountId) const
{
    return storage::avatar(accountId);
}

} // namespace lrc

#include "api/moc_accountmodel.cpp"
#include "accountmodel.moc"
