/*
 * Copyright (C) 2022 Ubports Foundation.
 *
 * Authors:
 *  Lionel Duboeuf <lduboeuf@ouvaton.org>
 *
 * This file is part of lomiri-history-service.
 *
 * lomiri-history-service is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * lomiri-history-service 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 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 "mmsstoragemanager_p.h"
#include "types.h"

#include <QCryptographicHash>

#define STORAGE_PATH "history-service/attachments"

namespace History
{

MmsStorageManager::MmsStorageManager()
{
    QString location = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
    QDir dir(location);
    if (!dir.mkpath(STORAGE_PATH)) {
        qCritical() << "Failed to create attachment dir" << STORAGE_PATH;
    } else {
        mStoragePath = dir.absoluteFilePath(STORAGE_PATH);
        qDebug() << " MMS Storage set to" << mStoragePath;
    }
}

MmsStorageManager *MmsStorageManager::instance()
{
    static MmsStorageManager* manager = new MmsStorageManager();
    return manager;
}

/**
 * @brief MmsStorageManager::mmsStoragePath
 * Build a complete file path from
 * the storage root path and encrypted parts from the input parameters
 * e.g: /home/phablet/.local/share/history-service/attachments/xxxaccountIdxxx/xxxthreadIdxxx/xxxeventIdxxx
 * if optional parameters are not set, build the path accordingly: mmsStoragePath("accountId") will return /home/phablet/.local/share/history-service/attachments/xxxaccountIdxxx
 * @param accountId
 * @param threadId
 * @param eventId
 * @return absolute filePath
 */
QString MmsStorageManager::mmsStoragePath(const QString &accountId, const QString &threadId, const QString &eventId) const
{
    if (mStoragePath.isEmpty()) {
        return QString();
    }

    QString normalizedAccountId = QString(QCryptographicHash::hash(accountId.toLatin1(), QCryptographicHash::Md5).toHex());
    QString normalizedThreadId = QString(QCryptographicHash::hash(threadId.toLatin1(), QCryptographicHash::Md5).toHex());

    QString subPath;
    if (threadId.isEmpty()) {
        subPath = QString("/%1").arg(normalizedAccountId);
    } else if (eventId.isEmpty()) {
        subPath = QString("/%1/%2").arg(normalizedAccountId, normalizedThreadId);
    } else {
        QString normalizedEventId = QString(QCryptographicHash::hash(eventId.toLatin1(), QCryptographicHash::Md5).toHex());
        subPath = QString("/%1/%2/%3").arg(normalizedAccountId, normalizedThreadId, normalizedEventId);
    }

    return QString("%1%2").arg(mStoragePath).arg(subPath);
}

/**
 * @brief MmsStorageManager::removeAttachments
 * Remove attachments stored at accountId/threadId/eventId
 * if eventId is omitted, remove the accountId/threadId and all childs
 * @param accountId
 * @param threadId
 * @param eventId
 * @return true if no issues
 */
bool MmsStorageManager::removeAttachments(const QString &accountId, const QString &threadId, const QString &eventId)
{
    QString mmsStoragePath = this->mmsStoragePath(accountId, threadId, eventId);
    if (mmsStoragePath.isNull()) {
        qCritical() << "Failed to access attachment directory ";
        return false;
    }
    QDir dir(mmsStoragePath);
    return dir.removeRecursively();
}

/**
 * @brief MmsStorageManager::removeAttachments
 * From a list of absolute file path, remove them
 * @param attachments
 * @return true if went fine
 */
bool MmsStorageManager::removeAttachments(QList<QString> attachments)
{
    bool ok = true;
    QStringList eventIdsPath;
    QStringList threadIdsPath;
    // extract eventId and thread paths
    Q_FOREACH(const QString &filePath, attachments) {

        QFile file(filePath);
        QString eventIdPath = QFileInfo(file).dir().path();
        if (!eventIdsPath.contains(eventIdPath)) {
            eventIdsPath << eventIdPath;
        }
        QString threadPath = QFileInfo(eventIdPath).dir().path();
        if (!threadIdsPath.contains(threadPath)) {
            threadIdsPath << threadPath;
        }
    }

    // start by removing eventIds path
    Q_FOREACH(const QString &dirToRemove, eventIdsPath) {
        QDir dir(dirToRemove);
        ok &= dir.removeRecursively();
    }

    // now we can remove threads path if they are empty
    Q_FOREACH(const QString &dirToRemove, threadIdsPath) {
        QDir dir(dirToRemove);
        if (dir.isEmpty()) {
            qDebug() << "removed thread path:" << dirToRemove;
            ok &= dir.removeRecursively();
        }
    }

    return ok;
}

/**
 * @brief MmsStorageManager::removeAttachmentsFromThreads
 * From a Thread list, remove all ../accountId/threadId directory and childs
 * @param threads
 * @return true if went fine
 */
bool MmsStorageManager::removeAttachmentsFromThreads(const QList<QVariantMap> &threads)
{
    bool ok = true;
    Q_FOREACH(const QVariantMap &thread, threads) {

        History::EventType type = (History::EventType) thread[History::FieldType].toInt();
        if (type == History::EventTypeText){
            QString accountId = thread[History::FieldAccountId].toString();
            QString threadId = thread[History::FieldThreadId].toString();

            if (!this->removeAttachments(accountId, threadId)) {
                qWarning() << "Failed to remove attachment directory for threadId" << threadId;
                ok = false;
            }
        }
    }

    return ok;
}

/**
 * @brief MmsStorageManager::removeAttachmentsFromEvents
 * From a Event list, remove all ../accountId/threadId/eventId directory and childs
 * @param events
 * @return
 */
bool MmsStorageManager::removeAttachmentsFromEvents(const QList<QVariantMap> &events)
{
    bool ok = true;
    Q_FOREACH(const QVariantMap &event, events) {

        History::EventType type = (History::EventType) event[History::FieldType].toInt();
        if (type == History::EventTypeText){

            QString accountId = event[History::FieldAccountId].toString();
            QString threadId = event[History::FieldThreadId].toString();
            QString eventId = event[History::FieldEventId].toString();

            if (!this->removeAttachments(accountId, threadId, eventId)){
                qWarning() << "Failed to remove attachment directory for eventId" << eventId;
                ok = false;
            }
        }
    }
    return ok;
}

/**
 * @brief MmsStorageManager::saveAttachment
 * store content part, to ../accountId/threadId/eventId/xxx target
 * @param part
 * @param accountId
 * @param threadId
 * @param eventId
 * @return absolute saved filename
 */
QString MmsStorageManager::saveAttachment(const Tp::MessagePart &part, const QString &accountId, const QString &threadId, const QString &eventId) const
{
    QString mmsStoragePath = this->mmsStoragePath(accountId, threadId, eventId);
    if (mmsStoragePath.isNull()) {
        qWarning() << "Failed to access attachment directory ";
        return QString();
    }
    QDir dir(mmsStoragePath);
    if (!dir.mkpath(mmsStoragePath)) {
        qWarning() << "Failed to create attachment directory" << mmsStoragePath;
        return QString();
    }

    QString fileName = part["identifier"].variant().toString();
    //filename may have forbidden characters, strip them
    fileName.replace(QRegularExpression("[" + QRegularExpression::escape("\\/:?\"<>|") + "]"), "");

    // we can have empty attachmentId, provide a fallback name
    if (fileName.isEmpty()) {
        dir.setFilter( QDir::AllEntries | QDir::NoDotAndDotDot );
        fileName = QString::number( dir.count() + 1);
    }

    QFile file(mmsStoragePath + "/" + fileName);
    if (!file.open(QIODevice::WriteOnly)) {
        qWarning() << "Failed to save attachment to" << mmsStoragePath << "/" << fileName ;
        return QString();
    } else {
        file.write(part["content"].variant().toByteArray());
        file.close();
    }

    return file.fileName();
}

/**
 * @brief MmsStorageManager::storageRootPath
 * Get the absolute directory path of the mms storage
 * @return absolute mms storage directory
 */
QString MmsStorageManager::storageRootPath() const
{
    return mStoragePath;
}

}
