/*
 * Copyright (C) 2014-2022 CZ.NIC
 *
 * This program 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QMutexLocker>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlRecord>

#include "src/datovka_shared/compat_qt/variant.h" /* nullVariantWhenIsNull */
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/datovka_shared/log/log.h"
#include "src/io/account_db.h"
#include "src/io/account_db_tables.h"
#include "src/io/dbs.h"

#ifdef Q_COMPILER_RVALUE_REFS
#  define macroStdMove(x) ::std::move(x)
#else /* Q_COMPILER_RVALUE_REFS */
#  define macroStdMove(x) (x)
#endif /* Q_COMPILER_RVALUE_REFS */

DbEntry::DbEntry(void)
    : QMap<QString, QVariant>()
{
}

bool DbEntry::setValue(const QString &key, const QVariant &value)
{
	/* Don't perform any check against database table. */
	this->insert(key, value);
	return true;
}

bool DbEntry::hasValue(const QString &key) const
{
	return this->contains(key);
}

const QVariant DbEntry::value(const QString &key,
    const QVariant &defaultValue) const
{
	return m_parentType::value(key, defaultValue);
}

DbEntry AccountDb::accountEntry(const QString &key) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;
	DbEntry aEntry;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "SELECT ";
	for (int i = 0; i < (accntinfTbl.knownAttrs.size() - 1); ++i) {
		queryStr += accntinfTbl.knownAttrs[i].first + ", ";
	}
	queryStr += accntinfTbl.knownAttrs.last().first + " ";
	queryStr += "FROM account_info WHERE key = :key";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive() && query.first()) {
		QSqlRecord rec = query.record();
		for (int i = 0; i < accntinfTbl.knownAttrs.size(); ++i) {
			QVariant value = query.value(rec.indexOf(
			    accntinfTbl.knownAttrs[i].first));
			if (!value.isNull() && value.isValid()) {
				aEntry.setValue(accntinfTbl.knownAttrs[i].first,
				    value);
			}
		}
		return aEntry;
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return DbEntry();
}

DbEntry AccountDb::userEntry(const QString &key) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;
	DbEntry uEntry;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "SELECT ";
	for (int i = 0; i < (userinfTbl.knownAttrs.size() - 1); ++i) {
		queryStr += userinfTbl.knownAttrs[i].first + ", ";
	}
	queryStr += userinfTbl.knownAttrs.last().first + " ";
	queryStr += "FROM user_info WHERE key = :key";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive() && query.first()) {
		QSqlRecord rec = query.record();
		for (int i = 0; i < userinfTbl.knownAttrs.size(); ++i) {
			QVariant value = query.value(rec.indexOf(
			    userinfTbl.knownAttrs[i].first));
			if (!value.isNull() && value.isValid()) {
				uEntry.setValue(userinfTbl.knownAttrs[i].first,
				    value);
			}
		}
		return uEntry;
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return DbEntry();
}

const QString AccountDb::dbId(const QString &key,
    const QString &defaultValue) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "SELECT dbID FROM account_info WHERE key = :key";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		return query.value(0).toString();
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return defaultValue;
}

const QString AccountDb::senderNameGuess(const QString &key,
    const QString &defaultValue) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;
	QString name;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "SELECT firmName, pnFirstName, pnMiddleName, "
	    "pnLastName FROM account_info WHERE key = :key";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		/* firmName */
		name = query.value(0).toString();
		if (!name.isEmpty()) {
			return name;
		}
		/* pnFirstName */
		name = query.value(1).toString();
		if (!query.value(2).toString().isEmpty()) {
			/* pnMiddleName */
			name += " " + query.value(2).toString();
		}
		/* pnLastName */
		name += " " + query.value(3).toString();
		return name;
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return defaultValue;
}

QDateTime AccountDb::getPwdExpirFromDb(const QString &key) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "SELECT expDate FROM password_expiration_date "
	    "WHERE key = :key";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive()) {
		if (query.first() && query.isValid() &&
		    !query.value(0).toString().isEmpty()) {
			return dateTimeFromDbFormat(query.value(0).toString());
		}
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return QDateTime();
}

int AccountDb::pwdExpiresInDays(const QString &key, int days) const
{
	const QDateTime dbDateTime(getPwdExpirFromDb(key));
	if (dbDateTime.isNull()) {
		return -1;
	}

	const QDate dbDate = dbDateTime.date();
	if (Q_UNLIKELY(!dbDate.isValid())) {
		return -1;
	}

	qint64 daysTo = QDate::currentDate().daysTo(dbDate);
	if (daysTo > days) {
		return -1;
	}

	return daysTo;
}

bool AccountDb::setPwdExpirIntoDb(const QString &key, const QDateTime &date)
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "INSERT OR REPLACE INTO password_expiration_date "
	    "(key, expDate) VALUES (:key, :expDate)";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", nullVariantWhenIsNull(key));
	query.bindValue(":expDate", nullVariantWhenIsNull(qDateTimeToDbFormat(date)));
	if (query.exec()) {
		return true;
	} else {
		logErrorNL("Cannot execute SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return false;
}

bool AccountDb::insertAccountIntoDb(const QString &key,
    const Isds::DbOwnerInfo &dbOwnerInfo)
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "INSERT OR REPLACE INTO account_info "
	    "(key, dbID, dbType, ic, pnFirstName, pnMiddleName, "
	    "pnLastName, pnLastNameAtBirth, firmName, biDate, biCity, "
	    "biCounty, biState, adCity, adStreet, adNumberInStreet, "
	    "adNumberInMunicipality, adZipCode, adState, nationality, "
	    "identifier, registryCode, dbState, dbEffectiveOVM, "
	    "dbOpenAddressing)"
	    " VALUES "
	    "(:key, :dbID, :dbType, :ic, :pnFirstName, :pnMiddleName, "
	    ":pnLastName, :pnLastNameAtBirth, :firmName, :biDate, :biCity, "
	    ":biCounty, :biState, :adCity, :adStreet, :adNumberInStreet, "
	    ":adNumberInMunicipality, :adZipCode, :adState, :nationality, "
	    ":identifier, :registryCode, :dbState, :dbEffectiveOVM, "
	    ":dbOpenAddressing)";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", nullVariantWhenIsNull(key));
	query.bindValue(":dbID", nullVariantWhenIsNull(dbOwnerInfo.dbID()));
	query.bindValue(":dbType", nullVariantWhenIsNull(Isds::dbType2StrVariant(dbOwnerInfo.dbType())));
	query.bindValue(":ic", dbOwnerInfo.ic());
	query.bindValue(":pnFirstName", nullVariantWhenIsNull(dbOwnerInfo.personName().firstName()));
	query.bindValue(":pnMiddleName", nullVariantWhenIsNull(dbOwnerInfo.personName().middleName()));
	query.bindValue(":pnLastName", nullVariantWhenIsNull(dbOwnerInfo.personName().lastName()));
	query.bindValue(":pnLastNameAtBirth",
	    nullVariantWhenIsNull(dbOwnerInfo.personName().lastNameAtBirth()));
	query.bindValue(":firmName", nullVariantWhenIsNull(dbOwnerInfo.firmName()));
	query.bindValue(":biDate", nullVariantWhenIsNull(qDateToDbFormat(dbOwnerInfo.birthInfo().date())));
	query.bindValue(":biCity", nullVariantWhenIsNull(dbOwnerInfo.birthInfo().city()));
	query.bindValue(":biCounty", nullVariantWhenIsNull(dbOwnerInfo.birthInfo().county()));
	query.bindValue(":biState", nullVariantWhenIsNull(dbOwnerInfo.birthInfo().state()));
	query.bindValue(":adCity", nullVariantWhenIsNull(dbOwnerInfo.address().city()));
	query.bindValue(":adStreet", nullVariantWhenIsNull(dbOwnerInfo.address().street()));
	query.bindValue(":adNumberInStreet",
	    nullVariantWhenIsNull(dbOwnerInfo.address().numberInStreet()));
	query.bindValue(":adNumberInMunicipality",
	    nullVariantWhenIsNull(dbOwnerInfo.address().numberInMunicipality()));
	query.bindValue(":adZipCode", nullVariantWhenIsNull(dbOwnerInfo.address().zipCode()));
	query.bindValue(":adState", nullVariantWhenIsNull(dbOwnerInfo.address().state()));
	query.bindValue(":nationality", nullVariantWhenIsNull(dbOwnerInfo.nationality()));
	query.bindValue(":identifier", nullVariantWhenIsNull(dbOwnerInfo.identifier()));
	query.bindValue(":registryCode", nullVariantWhenIsNull(dbOwnerInfo.registryCode()));
	query.bindValue(":dbState",
	    Isds::dbState2Variant(dbOwnerInfo.dbState()));
	query.bindValue(":dbEffectiveOVM",
	    Isds::nilBool2Variant(dbOwnerInfo.dbEffectiveOVM()));
	query.bindValue(":dbOpenAddressing",
	    Isds::nilBool2Variant(dbOwnerInfo.dbOpenAddressing()));
	if (query.exec()) {
		return true;
	} else {
		logErrorNL("Cannot execute SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return false;
}

bool AccountDb::insertUserIntoDb(const QString &key,
    const Isds::DbUserInfo &dbUserInfo)
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "INSERT OR REPLACE INTO user_info "
	    "(key, userType, userPrivils, pnFirstName, pnMiddleName, "
	    "pnLastName, pnLastNameAtBirth, adCity, adStreet, "
	    "adNumberInStreet, adNumberInMunicipality, adZipCode, "
	    "adState, biDate, ic, firmName, caStreet, caCity, "
	    "caZipCode, caState)"
	    " VALUES "
	    "(:key, :userType, :userPrivils, :pnFirstName, "
	    ":pnMiddleName, :pnLastName, :pnLastNameAtBirth, :adCity, "
	    ":adStreet, :adNumberInStreet, :adNumberInMunicipality, "
	    ":adZipCode, :adState, :biDate, :ic, :firmName, :caStreet, "
	    ":caCity, :caZipCode, :caState)";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", nullVariantWhenIsNull(key));
	query.bindValue(":userType", nullVariantWhenIsNull(Isds::userType2Str(dbUserInfo.userType())));
	query.bindValue(":userPrivils",
	    Isds::privileges2Variant(dbUserInfo.userPrivils()));
	query.bindValue(":ic", dbUserInfo.ic());
	query.bindValue(":pnFirstName", nullVariantWhenIsNull(dbUserInfo.personName().firstName()));
	query.bindValue(":pnMiddleName", nullVariantWhenIsNull(dbUserInfo.personName().middleName()));
	query.bindValue(":pnLastName", nullVariantWhenIsNull(dbUserInfo.personName().lastName()));
	query.bindValue(":pnLastNameAtBirth",
	    nullVariantWhenIsNull(dbUserInfo.personName().lastNameAtBirth()));
	query.bindValue(":firmName", nullVariantWhenIsNull(dbUserInfo.firmName()));
	query.bindValue(":biDate", nullVariantWhenIsNull(qDateToDbFormat(dbUserInfo.biDate())));
	query.bindValue(":adCity", nullVariantWhenIsNull(dbUserInfo.address().city()));
	query.bindValue(":adStreet", nullVariantWhenIsNull(dbUserInfo.address().street()));
	query.bindValue(":adNumberInStreet",
	    nullVariantWhenIsNull(dbUserInfo.address().numberInStreet()));
	query.bindValue(":adNumberInMunicipality",
	    nullVariantWhenIsNull(dbUserInfo.address().numberInMunicipality()));
	query.bindValue(":adZipCode", nullVariantWhenIsNull(dbUserInfo.address().zipCode()));
	query.bindValue(":adState", nullVariantWhenIsNull(dbUserInfo.address().state()));
	query.bindValue(":caStreet", nullVariantWhenIsNull(dbUserInfo.caStreet()));
	query.bindValue(":caCity", nullVariantWhenIsNull(dbUserInfo.caCity()));
	query.bindValue(":caZipCode", nullVariantWhenIsNull(dbUserInfo.caZipCode()));
	query.bindValue(":caState", nullVariantWhenIsNull(dbUserInfo.caState()));
	if (query.exec()) {
		return true;
	} else {
		logErrorNL("Cannot execute SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return false;
}

bool AccountDb::deleteAccountInfo(const QString &key, bool transaction)
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	/* Delete account info from these tables */
	const QStringList tables = QStringList() << "password_expiration_date"
	    << "user_info" << "account_info";

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	if (transaction) {
		transaction = beginTransaction();
		if (Q_UNLIKELY(!transaction)) {
			logErrorNL("%s", "Cannot begin transaction.");
			goto fail;
		}
	}

	foreach (const QString &table, tables) {
		queryStr = "DELETE FROM " + table + " WHERE key = :key";
		if (Q_UNLIKELY(!query.prepare(queryStr))) {
			logErrorNL("Cannot prepare SQL query: %s.",
			    query.lastError().text().toUtf8().constData());
			goto fail;
		}
		query.bindValue(":key", key);
		if (Q_UNLIKELY(!query.exec())) {
			logErrorNL("Cannot execute SQL query: %s.",
			    query.lastError().text().toUtf8().constData());
			goto fail;
		}
	}

	if (transaction) {
		commitTransaction();
	}

	return true;

fail:
	if (transaction) {
		rollbackTransaction();
	}
	return false;
}

const Isds::DbOwnerInfo AccountDb::getOwnerInfo(const QString &key) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;
	Isds::Address address;
	Isds::BirthInfo biInfo;
	Isds::DbOwnerInfo dbOwnerInfo;
	Isds::PersonName personName;

	if (Q_UNLIKELY(!m_db.isOpen())) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "SELECT "
	    "dbID, dbType, ic, pnFirstName, pnMiddleName, pnLastName, "
	    "pnLastNameAtBirth, firmName, biDate, biCity, biCounty, biState, "
	    "adCity, adStreet, adNumberInStreet, adNumberInMunicipality, "
	    "adZipCode, adState, nationality, identifier, registryCode, "
	    "dbState, dbEffectiveOVM, dbOpenAddressing "
	    "FROM account_info WHERE key = :key";

	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		dbOwnerInfo.setDbID(query.value(0).toString());
		dbOwnerInfo.setDbType(Isds::strVariant2DbType(query.value(1)));
		dbOwnerInfo.setIc(query.value(2).toString());
		personName.setFirstName(query.value(3).toString());
		personName.setMiddleName(query.value(4).toString());
		personName.setLastName(query.value(5).toString());
		personName.setLastNameAtBirth(query.value(6).toString());
		dbOwnerInfo.setPersonName(macroStdMove(personName));
		dbOwnerInfo.setFirmName(query.value(7).toString());
		biInfo.setDate(dateFromDbFormat(query.value(8).toString()));
		biInfo.setCity(query.value(9).toString());
		biInfo.setCounty(query.value(10).toString());
		biInfo.setState(query.value(11).toString());
		dbOwnerInfo.setBirthInfo(macroStdMove(biInfo));
		address.setCity(query.value(12).toString());
		address.setStreet(query.value(13).toString());
		address.setNumberInStreet(query.value(14).toString());
		address.setNumberInMunicipality(query.value(15).toString());
		address.setZipCode(query.value(16).toString());
		address.setState(query.value(17).toString());
		dbOwnerInfo.setAddress(macroStdMove(address));
		dbOwnerInfo.setNationality(query.value(18).toString());
		dbOwnerInfo.setIdentifier(query.value(19).toString());
		dbOwnerInfo.setRegistryCode(query.value(20).toString());
		dbOwnerInfo.setDbState(Isds::variant2DbState(query.value(21)));
		dbOwnerInfo.setDbEffectiveOVM(
		    Isds::variant2NilBool(query.value(22)));
		dbOwnerInfo.setDbOpenAddressing(
		    Isds::variant2NilBool(query.value(23)));
		return dbOwnerInfo;
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}
fail:
	return Isds::DbOwnerInfo();
}

Isds::Type::Privileges AccountDb::userPrivileges(const QString &key) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT userPrivils FROM user_info WHERE key = :key";
	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		return Isds::variant2Privileges(query.value(0));
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}

fail:
	return Isds::Type::PRIVIL_NONE;
}

QList<QString> AccountDb::sameBoxKeys(const QString &key) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	queryStr = "SELECT key FROM account_info WHERE dbID = "
	    "(SELECT dbID FROM account_info WHERE key = :key)";
	if (Q_UNLIKELY(!query.prepare(queryStr))) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":key", key);
	if (query.exec() && query.isActive()) {
		QList<QString> keys;
		query.first();
		while (query.isValid()) {
			keys.append(query.value(0).toString());
			query.next();
		}
		return keys;
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: %s.",
		    query.lastError().text().toUtf8().constData());
	}

fail:
	return QList<QString>();
}

static
const QString keySuffix("___True");

QString AccountDb::keyFromLogin(const QString &login)
{
	if (Q_UNLIKELY(login.isEmpty())) {
		Q_ASSERT(0);
		return QString();
	}
	return login + keySuffix;
}

QString AccountDb::loginFromKey(const QString &key)
{
	if (Q_UNLIKELY(key.isEmpty())) {
		Q_ASSERT(0);
		return QString();
	}

	if (Q_UNLIKELY(!key.endsWith(keySuffix))) {
		Q_ASSERT(0);
		return QString();
	}

	QString login(key.left(key.size() - keySuffix.size()));
	if (Q_UNLIKELY(login.isEmpty())) {
		Q_ASSERT(0);
		return QString();
	}

	return login;
}

bool AccountDb::deleteDTInfoFromDb(bool test_env, const QString &dbID) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	if (!m_db.isOpen()) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}
	queryStr = "DELETE FROM dt_info "
	    "WHERE test_env = :test_env AND dbID = :dbID";
	if (!query.prepare(queryStr)) {
		logErrorNL("Cannot prepare SQL query: %s",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":test_env", test_env);
	query.bindValue(":dbID", dbID);
	if (!query.exec()) {
		logErrorNL("Cannot execute SQL query: %s",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	return true;
fail:
	return false;
}

bool AccountDb::insertDTInfoIntoDb(bool test_env, const QString &dbID,
    const Isds::DTInfoOutput &dtInfo)
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;

	if (!m_db.isOpen()) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "INSERT OR REPLACE INTO dt_info "
	    "(test_env, dbID, actDTType, actDTCapacity, actDTFrom, "
	    "actDTTo, actDTCapUsed, futDTType, futDTCapacity, futDTFrom, "
	    "futDTTo, futDTPaid) VALUES "
	    "(:test_env, :dbID, :actDTType, :actDTCapacity, :actDTFrom, "
	    ":actDTTo, :actDTCapUsed, :futDTType, :futDTCapacity, :futDTFrom, "
	    ":futDTTo, :futDTPaid)";

	if (!query.prepare(queryStr)) {
		logErrorNL("Cannot prepare SQL query: %s",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":test_env", test_env);
	query.bindValue(":dbID", nullVariantWhenIsNull(dbID));
	query.bindValue(":actDTType", Isds::dtType2Variant(dtInfo.actDTType()));
	query.bindValue(":actDTCapacity",
	    Isds::nonNegativeLong2Variant(dtInfo.actDTCapacity()));
	query.bindValue(":actDTFrom", nullVariantWhenIsNull(qDateToDbFormat(dtInfo.actDTFrom())));
	query.bindValue(":actDTTo", nullVariantWhenIsNull(qDateToDbFormat(dtInfo.actDTTo())));
	query.bindValue(":actDTCapUsed",
	    Isds::nonNegativeLong2Variant(dtInfo.actDTCapUsed()));
	query.bindValue(":futDTType",
	    Isds::dtType2Variant(dtInfo.futDTType()));
	query.bindValue(":futDTCapacity",
	    Isds::nonNegativeLong2Variant(dtInfo.futDTCapacity()));
	query.bindValue(":futDTFrom", nullVariantWhenIsNull(qDateToDbFormat(dtInfo.futDTFrom())));
	query.bindValue(":futDTTo", nullVariantWhenIsNull(qDateToDbFormat(dtInfo.futDTTo())));
	query.bindValue(":futDTPaid",
	    Isds::dtPaidState2Variant(dtInfo.futDTPaid()));
	if (query.exec()) {
		return true;
	} else {
		logErrorNL("Cannot execute SQL query: %s",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
fail:
	return false;
}

Isds::DTInfoOutput AccountDb::getDTInfo(bool test_env,
    const QString &dbID) const
{
	QMutexLocker locker(&m_lock);
	QSqlQuery query(m_db);
	QString queryStr;
	Isds::DTInfoOutput dtInfo;

	if (!m_db.isOpen()) {
		logErrorNL("%s", "Account database seems not to be open.");
		goto fail;
	}

	queryStr = "SELECT "
	    "actDTType, actDTCapacity, actDTFrom, actDTTo, actDTCapUsed, "
	    "futDTType, futDTCapacity, futDTFrom, futDTTo, futDTPaid "
	    "FROM dt_info WHERE test_env = :test_env AND dbID = :dbID";

	if (!query.prepare(queryStr)) {
		logErrorNL("Cannot prepare SQL query: %s.",
		    query.lastError().text().toUtf8().constData());
		goto fail;
	}
	query.bindValue(":test_env", test_env);
	query.bindValue(":dbID", dbID);
	if (query.exec() && query.isActive() &&
	    query.first() && query.isValid()) {
		dtInfo.setActDTType(
		    Isds::variant2DTType(query.value(0)));
		dtInfo.setActDTCapacity(
		    Isds::variant2nonNegativeLong(query.value(1)));
		dtInfo.setActDTFrom(dateFromDbFormat(query.value(2).toString()));
		dtInfo.setActDTTo(dateFromDbFormat(query.value(3).toString()));
		dtInfo.setActDTCapUsed(
		    Isds::variant2nonNegativeLong(query.value(4)));
		dtInfo.setFutDTType(
		    Isds::variant2DTType(query.value(5)));
		dtInfo.setFutDTCapacity(
		    Isds::variant2nonNegativeLong(query.value(6)));
		dtInfo.setFutDTFrom(dateFromDbFormat(query.value(7).toString()));
		dtInfo.setFutDTTo(dateFromDbFormat(query.value(8).toString()));
		dtInfo.setFutDTPaid(
		    Isds::variant2DTPaidState(query.value(9)));
		return dtInfo;
	} else {
		logErrorNL("Cannot execute SQL query and/or read SQL data: "
		    "%s.", query.lastError().text().toUtf8().constData());
	}
fail:
	return Isds::DTInfoOutput();
}

QList<class SQLiteTbl *> AccountDb::listOfTables(void) const
{
	QList<class SQLiteTbl *> tables;
	tables.append(&accntinfTbl);
	tables.append(&userinfTbl);
	tables.append(&pwdexpdtTbl);
	tables.append(&dtinfTbl);
	return tables;
}
