#include "account.h"
#include <QCryptographicHash>
#include <QSysInfo>
#include <QCoreApplication>

#ifdef Q_OS_WIN
#include <windows.h>
#include <wincrypt.h>
#endif

namespace {

QByteArray localPasswordKey()
{
    QByteArray seed = QSysInfo::machineUniqueId();
    seed.append('|').append(qgetenv("USERNAME"));
    seed.append('|').append(qgetenv("USER"));
    seed.append('|').append(QCoreApplication::organizationName().toUtf8());
    seed.append('|').append(QCoreApplication::applicationName().toUtf8());
    return QCryptographicHash::hash(seed, QCryptographicHash::Sha256);
}

QString decryptLegacyPassword(const QString &encrypted)
{
    const QByteArray data = QByteArray::fromBase64(encrypted.toUtf8());
    static const QByteArray key = QByteArray::fromHex("0123456789ABCDEF0123456789ABCDEF");
    QByteArray result(data.size(), 0);
    for (int i = 0; i < data.size(); ++i) result[i] = data[i] ^ key.at(i % key.size());
    return QString::fromUtf8(result);
}

}

Account::Account(int id, const QString& email, const QString& displayName,
                 const QString& signature,
                 AccountType type, const QString& accessToken,
                 const QString& refreshToken,
                 const QDateTime& tokenExpires)
    : m_id(id)
    , m_email(email)
    , m_displayName(displayName)
    , m_signature(signature)
    , m_type(type)
    , m_accessToken(accessToken)
    , m_refreshToken(refreshToken)
    , m_tokenExpires(tokenExpires)
{
}

QString Account::encryptPassword(const QString& plainText)
{
    const QByteArray input = plainText.toUtf8();
#ifdef Q_OS_WIN
    DATA_BLOB source{static_cast<DWORD>(input.size()), reinterpret_cast<BYTE*>(const_cast<char*>(input.constData()))};
    DATA_BLOB protectedData{};
    if (CryptProtectData(&source, L"Wino Mail password", nullptr, nullptr, nullptr,
                         CRYPTPROTECT_UI_FORBIDDEN, &protectedData)) {
        const QByteArray output(reinterpret_cast<const char*>(protectedData.pbData), protectedData.cbData);
        LocalFree(protectedData.pbData);
        return QStringLiteral("dpapi:") + QString::fromLatin1(output.toBase64());
    }
#endif
    const QByteArray key = localPasswordKey();
    QByteArray result(input.size(), 0);
    for (int i = 0; i < input.size(); ++i) result[i] = input[i] ^ key.at(i % key.size());
    return QStringLiteral("local:") + QString::fromLatin1(result.toBase64());
}

QString Account::decryptPassword(const QString& encrypted)
{
    if (encrypted.startsWith(QStringLiteral("dpapi:"))) {
#ifdef Q_OS_WIN
        const QByteArray data = QByteArray::fromBase64(encrypted.mid(6).toLatin1());
        DATA_BLOB source{static_cast<DWORD>(data.size()), reinterpret_cast<BYTE*>(const_cast<char*>(data.constData()))};
        DATA_BLOB plain{};
        if (CryptUnprotectData(&source, nullptr, nullptr, nullptr, nullptr,
                               CRYPTPROTECT_UI_FORBIDDEN, &plain)) {
            const QString result = QString::fromUtf8(reinterpret_cast<const char*>(plain.pbData), plain.cbData);
            LocalFree(plain.pbData);
            return result;
        }
#endif
        return QString();
    }
    if (encrypted.startsWith(QStringLiteral("local:"))) {
        const QByteArray data = QByteArray::fromBase64(encrypted.mid(6).toLatin1());
        const QByteArray key = localPasswordKey();
        QByteArray result(data.size(), 0);
        for (int i = 0; i < data.size(); ++i) result[i] = data[i] ^ key.at(i % key.size());
        return QString::fromUtf8(result);
    }
    // Read passwords written by older versions once; all new writes use the
    // user-bound envelope above.
    return decryptLegacyPassword(encrypted);
}
