#include "ConnectionController.h"
#include "MasterController.h"
#include "ConnectionManagerWindow.h"
#include "ConnectionListModel.h"
#include "utils/PasswordManager.h"
#include "DatabaseWindow.h"
#include "BackupDialog.h"
#include "PasswordPromptDialog.h"
#include "ConnectionConfigurationWidget.h"
#include <QSqlQuery>
#include <QInputDialog>
#include <QMessageBox>
#include <QTimer>

ConnectionController::ConnectionController(MasterController *parent)
	: QObject(parent)
	, m_masterController(parent)
	, m_relockTimer(new QTimer(this))
{
	m_relockTimer->setSingleShot(true);
	m_relockTimer->setTimerType(Qt::VeryCoarseTimer);
	// Force signal to go through queue so when the password manager is relocked after 0msec
	// the code that retrieves the password is garanteed to run before the signal is handled
	// because only after the password is retrieved the loop has a chance to run.
	m_relockTimer->callOnTimeout(this, &ConnectionController::relock, Qt::QueuedConnection);
}

ConnectionController::~ConnectionController()
{
	delete m_connectionManagerWindow;
	delete m_connectionTreeModel;
}

void ConnectionController::init()
{
	m_passwordManager = std::make_shared<PasswordManager>();

	m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase());
	m_connectionTreeModel->load();

	m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr);
}

void ConnectionController::showConnectionManager()
{
	m_connectionManagerWindow->show();
}

void ConnectionController::openSqlWindowForConnection(QModelIndex index)
{
	auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
	if (config) {

		if (retrieveConnectionPassword(*config)) {
			m_connectionTreeModel->save(*config);

            openSqlWindowForConnection(*config);
		}
	}
}

void ConnectionController::openSqlWindowForConnection(const ConnectionConfig &config)
{
    // TODO instead of directly openening the mainwindow
    // do async connect and only open window when we have
    // working connection
    auto w = new DatabaseWindow(m_masterController, nullptr);
    w->setAttribute( Qt::WA_DeleteOnClose );
    w->setConfig(config);
    w->showMaximized();
}

void ConnectionController::openBackupDlgForConnection(QModelIndex index)
{
	auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
	if (config) {
		if (retrieveConnectionPassword(*config)) {
			m_connectionTreeModel->save(*config);
            auto w = new BackupDialog(nullptr);
			w->setAttribute( Qt::WA_DeleteOnClose );
			w->setConfig(*config);
			w->show();
		}
	}
}

void ConnectionController::createConnection(ConnectionConfig *init)
{
	ConnectionConfig cc;
    if (init)
    {
        cc = *init;
        cc.setUuid(QUuid());
    }
    editConfig(cc);
}

void ConnectionController::editConnection(QModelIndex index)
{
	auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
	if (config) {
        editConfig(*config);
    }
}

void ConnectionController::editCopy(QModelIndex index)
{
    auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
    if (config) {
        auto cc = *config;
        cc.setUuid(QUuid());
        cc.setEncodedPassword({}); // maybe we should decode en reencode?
        editConfig(cc);
    }
}

void ConnectionController::editConfig(ConnectionConfig &cc)
{
    ConnectionConfigurationWidget::editExistingInWindow(this, cc,
            [this] (ConnectionConfigurationWidget &w) -> void { saveConnection(w); });
}

void ConnectionController::saveConnection(ConnectionConfigurationWidget &w)
{
    auto cc = w.data();
    auto grp = w.group();
    bool isNew = cc.uuid().isNull();
    if (isNew)
    {
        cc.setUuid(QUuid::createUuid());
    }
    if (w.savePasswordEnabled())
    {
        if (isNew || w.passwordIsChanged())
            encryptPassword(cc);
    }
    else
    {
        cc.setEncodedPassword({});
    }
    m_connectionTreeModel->save(grp, cc);
}

void ConnectionController::addGroup()
{
	auto result = QInputDialog::getText(nullptr, tr("Add new connection group"),
						  tr("Group name"));
	if (!result.isEmpty()) {
        try
        {
            m_connectionTreeModel->addGroup(result);
        }
        catch (const SQLiteException &ex) {
			QMessageBox::critical(nullptr, tr("Add group failed"),
								  tr("Failed to add group.\n") +
                                  QString(ex.what()));
		}
	}
}

void ConnectionController::removeGroup(QModelIndex index)
{
	auto group = ConnectionTreeModel::getGroupFromModelIndex(index);
	if (group) {
		auto btn = QMessageBox::question(nullptr, tr("Connection group"),
				tr("Remove the selected group and all connections contained in the group?"),
					QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
							QMessageBox::NoButton);
		if (btn == QMessageBox::Yes) {
			m_connectionTreeModel->removeGroup(index.row());
		}
	}
}

std::shared_ptr<PasswordManager> ConnectionController::passwordManager()
{
	return m_passwordManager;
}

bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
{
    auto enc_pwd = cc.encodedPassword();
    if (!enc_pwd.isEmpty())
    {
        std::string pw;
        bool result = retrieveFromPasswordManager(getPskId(cc.uuid()),
                    std::string_view(enc_pwd.data(), enc_pwd.size()) , pw);
        if (result)
        {
            cc.setPassword(QString::fromUtf8(pw.data(), pw.size()));
            return true;
        }
    }
    // Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord
    // ook aan de gebruiker vragen zoals hier gebeurd.
    QString str = cc.makeLongDescription();
    auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
    dlg->setCaption(tr("Connection password prompt"));
    dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
    int exec_result = dlg->exec();

    if (exec_result == QDialog::Accepted)
    {
        auto password = dlg->password();
        cc.setPassword(password);
        if (dlg->saveChecked())
            encryptPassword(cc);

        return true;
    }
    return false;
}

bool ConnectionController::retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password)
{
    try
    {
        if (!UnlockPasswordManagerIfNeeded())
            return false;

        password = m_passwordManager->decrypt(password_id, enc_password);
        return true;
    }
    catch (const PasswordManagerException &)
    {
        return false;
    }
}

bool ConnectionController::encryptPassword(ConnectionConfig &cc)
{
    if (!UnlockPasswordManagerIfNeeded())
        return false;

    std::string password = cc.password().toStdString();
    std::string password_id = getPskId(cc.uuid());
    std::string enc_password = m_passwordManager->encrypt(password_id, password);
    cc.setEncodedPassword({ enc_password.data(), static_cast<int>(enc_password.size()) });
    return true;
}

bool ConnectionController::decodeConnectionPassword(QUuid id, QByteArray encoded, QString &out_password)
{
    std::string password_id = getPskId(id);
    std::string enc(encoded.data(), encoded.size());
    std::string password;
    bool res = retrieveFromPasswordManager(password_id, enc, password);
    if (res)
        out_password = QString::fromStdString(password);
    return res;
}

void ConnectionController::resetPasswordManager()
{
    SQLiteConnection& user_cfg_db = m_masterController->userConfigDatabase();
    SQLiteTransaction tx(user_cfg_db);
    m_passwordManager->resetMasterPassword(user_cfg_db);
    m_connectionTreeModel->clearAllPasswords();
    tx.Commit();
}

bool ConnectionController::UnlockPasswordManagerIfNeeded()
{
    auto&& user_cfg_db = m_masterController->userConfigDatabase();
    if (m_passwordManager->initialized(user_cfg_db))
    {
        if (!m_passwordManager->locked())
            return true;

        while (true)
        {
            PassphraseResult pp_result = PassphrasePrompt();
            if (!pp_result.success)
                break;

            // user gave OK, if succeeds return true otherwise loop a prompt for password again.
            if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase))
            {
                setRelockTimer(pp_result.rememberForMinutes);
                return true;
            }
        }
    }
    else
    {
        InitializePasswordManager();
        return true;
    }

    return false;
}

void ConnectionController::setRelockTimer(int rem_minutes)
{
    if (rem_minutes >= 0) {
        int timeout = rem_minutes * 60 * 1000; /// rem is in minutes, timeout in millisec
        m_relockTimer->start(timeout);
    }
}

PassphraseResult ConnectionController::PassphrasePrompt()
{
    auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::RememberPassword, nullptr);
    dlg->setCaption(tr("Unlock password manager"));
    dlg->setDescription(tr("Enter password for password manager"));
    int exec_result = dlg->exec();
    bool ok = (exec_result == QDialog::Accepted);

    PassphraseResult result;
    result.success = ok;
    if (ok) {
        result.passphrase = dlg->password();
        result.rememberForMinutes = dlg->remember();
    }
    return result;
}

bool ConnectionController::InitializePasswordManager()
{
    // Ask user for passphrase + confirmation, clearly instruct this is first setup
    // create
    auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::ConfirmPassword, nullptr);
    dlg->setCaption(tr("Password manager setup"));
    dlg->setDescription(tr("Enter a strong password for password manager initialization. A strong key will be "
                           "derived from your password and it will be impossible to recover anything from the "
                           "password manager without the password you enter here."));
    int exec_result = dlg->exec();
    if (exec_result == QDialog::Accepted) {
        QString passphrase = dlg->password();
        auto&& user_cfg_db = m_masterController->userConfigDatabase();
        if (m_passwordManager->createDatabase(user_cfg_db, passphrase))
            return true;
    }
    return false;
}

std::string ConnectionController::getPskId(QUuid connectionid)
{
	std::string id = "dbpw/";
    id += connectionid.toString().toUtf8().data();
	return id;
}

void ConnectionController::relock()
{
	m_passwordManager->closeDatabase();
}



