#include "accountdao.h"

qint64 AccountDao::insert(const Account& account)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare(
        "INSERT INTO Account (email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
        "incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
        "connUsername, connPassword, authMethod) "
        "VALUES (:email, :displayName, :signature, :type, :accessToken, :refreshToken, :tokenExpires, "
        ":incomingHost, :incomingPort, :incomingSsl, :outgoingHost, :outgoingPort, :outgoingSsl, "
        ":connUsername, :connPassword, :authMethod)");
    query.bindValue(":email", account.email());
    query.bindValue(":displayName", account.displayName());
    query.bindValue(":signature", account.signature());
    query.bindValue(":type", static_cast<int>(account.type()));
    query.bindValue(":accessToken", account.accessToken());
    query.bindValue(":refreshToken", account.refreshToken());
    query.bindValue(":tokenExpires", account.tokenExpires());
    const auto& settings = account.connectionSettings();
    query.bindValue(":incomingHost", settings.incomingHost);
    query.bindValue(":incomingPort", settings.incomingPort);
    query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
    query.bindValue(":outgoingHost", settings.outgoingHost);
    query.bindValue(":outgoingPort", settings.outgoingPort);
    query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
    query.bindValue(":connUsername", settings.username);
    query.bindValue(":connPassword", Account::encryptPassword(settings.password));
    query.bindValue(":authMethod", settings.authMethod);

    if (!query.exec()) {
        qWarning() << "Failed to insert account:" << query.lastError().text();
        return -1;
    }

    return query.lastInsertId().toLongLong();
}

bool AccountDao::update(const Account& account)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare(
        "UPDATE Account SET "
        "email = :email, "
        "displayName = :displayName, "
        "signature = :signature, "
        "type = :type, "
        "accessToken = :accessToken, "
        "refreshToken = :refreshToken, "
        "tokenExpires = :tokenExpires, "
        "incomingHost = :incomingHost, "
        "incomingPort = :incomingPort, "
        "incomingSsl = :incomingSsl, "
        "outgoingHost = :outgoingHost, "
        "outgoingPort = :outgoingPort, "
        "outgoingSsl = :outgoingSsl, "
        "connUsername = :connUsername, "
        "connPassword = :connPassword, "
        "authMethod = :authMethod "
        "WHERE id = :id");
    query.bindValue(":id", account.id());
    query.bindValue(":email", account.email());
    query.bindValue(":displayName", account.displayName());
    query.bindValue(":signature", account.signature());
    query.bindValue(":type", static_cast<int>(account.type()));
    query.bindValue(":accessToken", account.accessToken());
    query.bindValue(":refreshToken", account.refreshToken());
    query.bindValue(":tokenExpires", account.tokenExpires());
    const auto& settings = account.connectionSettings();
    query.bindValue(":incomingHost", settings.incomingHost);
    query.bindValue(":incomingPort", settings.incomingPort);
    query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
    query.bindValue(":outgoingHost", settings.outgoingHost);
    query.bindValue(":outgoingPort", settings.outgoingPort);
    query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
    query.bindValue(":connUsername", settings.username);
    query.bindValue(":connPassword", Account::encryptPassword(settings.password));
    query.bindValue(":authMethod", settings.authMethod);

    if (!query.exec()) {
        qWarning() << "Failed to update account:" << query.lastError().text();
        return false;
    }
    return true;
}

bool AccountDao::remove(int id)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare("DELETE FROM Account WHERE id = :id");
    query.bindValue(":id", id);

    if (!query.exec()) {
        qWarning() << "Failed to delete account:" << query.lastError().text();
        return false;
    }
    return true;
}

Account* AccountDao::findById(int id)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare(
        "SELECT id, email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
        "incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
        "connUsername, connPassword, authMethod "
        "FROM Account WHERE id = :id");
    query.bindValue(":id", id);

    if (!query.exec()) {
        qWarning() << "Failed to find account by id:" << query.lastError().text();
        return nullptr;
    }

    if (query.next()) {
        Account* account = new Account();
        account->setId(query.value("id").toInt());
        account->setEmail(query.value("email").toString());
        account->setDisplayName(query.value("displayName").toString());
        account->setSignature(query.value("signature").toString());
        account->setType(static_cast<AccountType>(query.value("type").toInt()));
        account->setAccessToken(query.value("accessToken").toString());
        account->setRefreshToken(query.value("refreshToken").toString());
        account->setTokenExpires(query.value("tokenExpires").toDateTime());
        Account::ConnectionSettings settings;
        settings.incomingHost = query.value("incomingHost").toString();
        settings.incomingPort = query.value("incomingPort").toInt();
        settings.incomingSsl = query.value("incomingSsl").toBool();
        settings.outgoingHost = query.value("outgoingHost").toString();
        settings.outgoingPort = query.value("outgoingPort").toInt();
        settings.outgoingSsl = query.value("outgoingSsl").toBool();
        settings.username = query.value("connUsername").toString();
        settings.password = Account::decryptPassword(query.value("connPassword").toString());
        settings.authMethod = query.value("authMethod").toString();
        account->setConnectionSettings(settings);
        return account;
    }

    return nullptr;
}

QVector<Account> AccountDao::findAll()
{
    QVector<Account> accounts;
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    if (!query.exec(
        "SELECT id, email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
        "incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
        "connUsername, connPassword, authMethod "
        "FROM Account")) {
        qWarning() << "Failed to fetch all accounts:" << query.lastError().text();
        return accounts;
    }

    while (query.next()) {
        Account acc;
        acc.setId(query.value("id").toInt());
        acc.setEmail(query.value("email").toString());
        acc.setDisplayName(query.value("displayName").toString());
        acc.setSignature(query.value("signature").toString());
        acc.setType(static_cast<AccountType>(query.value("type").toInt()));
        acc.setAccessToken(query.value("accessToken").toString());
        acc.setRefreshToken(query.value("refreshToken").toString());
        acc.setTokenExpires(query.value("tokenExpires").toDateTime());
        Account::ConnectionSettings settings;
        settings.incomingHost = query.value("incomingHost").toString();
        settings.incomingPort = query.value("incomingPort").toInt();
        settings.incomingSsl = query.value("incomingSsl").toBool();
        settings.outgoingHost = query.value("outgoingHost").toString();
        settings.outgoingPort = query.value("outgoingPort").toInt();
        settings.outgoingSsl = query.value("outgoingSsl").toBool();
        settings.username = query.value("connUsername").toString();
        settings.password = Account::decryptPassword(query.value("connPassword").toString());
        settings.authMethod = query.value("authMethod").toString();
        acc.setConnectionSettings(settings);
        accounts.append(acc);
    }
    return accounts;
}

Account* AccountDao::findByEmail(const QString& email)
{
    QSqlDatabase db = DatabaseManager::instance().database();
    QSqlQuery query(db);
    query.prepare(
        "SELECT id, email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
        "incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
        "connUsername, connPassword, authMethod "
        "FROM Account WHERE email = :email");
    query.bindValue(":email", email);

    if (!query.exec()) {
        qWarning() << "Failed to find account by email:" << query.lastError().text();
        return nullptr;
    }

    if (query.next()) {
        Account* acc = new Account();
        acc->setId(query.value("id").toInt());
        acc->setEmail(query.value("email").toString());
        acc->setDisplayName(query.value("displayName").toString());
        acc->setSignature(query.value("signature").toString());
        acc->setType(static_cast<AccountType>(query.value("type").toInt()));
        acc->setAccessToken(query.value("accessToken").toString());
        acc->setRefreshToken(query.value("refreshToken").toString());
        acc->setTokenExpires(query.value("tokenExpires").toDateTime());
        Account::ConnectionSettings settings;
        settings.incomingHost = query.value("incomingHost").toString();
        settings.incomingPort = query.value("incomingPort").toInt();
        settings.incomingSsl = query.value("incomingSsl").toBool();
        settings.outgoingHost = query.value("outgoingHost").toString();
        settings.outgoingPort = query.value("outgoingPort").toInt();
        settings.outgoingSsl = query.value("outgoingSsl").toBool();
        settings.username = query.value("connUsername").toString();
        settings.password = Account::decryptPassword(query.value("connPassword").toString());
        settings.authMethod = query.value("authMethod").toString();
        acc->setConnectionSettings(settings);
        return acc;
    }

    return nullptr;
}
