#include "ConnectionConfigurationWidget.h"
#include "SslModeModel.h"
#include "ConnectionConfig.h"
#include "ConnectionController.h"
#include "ConnectionListModel.h"
#include "Pgsql_Connection.h"
#include "Pgsql_PgException.h"
#include "util.h"
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDataWidgetMapper>
#include <QDebug>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QFuture>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QtConcurrent>

#define SET_OBJECT_NAME(var) var->setObjectName(#var)



void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *connection_controller, const ConnectionConfig &cfg,
                                                         std::function<void(ConnectionConfigurationWidget&)> save_func)
{
	try {
        auto w = new ConnectionConfigurationWidget(connection_controller);
		w->setData(cfg);

		auto btn_hbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);

		auto vbox = new QVBoxLayout;
		vbox->addWidget(w);
		vbox->addWidget(btn_hbox);

		auto win = new QDialog;
		win->setWindowTitle(tr("Edit connection configuration"));
		win->setLayout(vbox);
		win->setAttribute( Qt::WA_DeleteOnClose, true );

        QObject::connect(btn_hbox, &QDialogButtonBox::accepted, [w, win, save_func] () {
                save_func(*w);
                win->accept();
            });
		QObject::connect(btn_hbox, &QDialogButtonBox::rejected, [win] ()
			{
				win->reject();
			});

		win->show();
	}
	catch (const std::exception &ex) {
		qDebug() << "exception: " << QString::fromUtf8(ex.what());
	}
}

ConnectionConfigurationWidget::ConnectionConfigurationWidget(
        ConnectionController *connection_controller,
        QWidget *parent)
	: QWidget(parent)
    , m_connectionController(connection_controller)
    , m_connectionModel(connection_controller->getConnectionTreeModel())
{
	lblGroup = new QLabel;
	cmbbxGroup = new QComboBox;
    cmbbxGroup->setModel(m_connectionModel);
	cmbbxGroup->setModelColumn(0);
	lblGroup->setBuddy(cmbbxGroup);

	lblName = new QLabel;
	SET_OBJECT_NAME(lblName);
	edtName = new QLineEdit ;
	SET_OBJECT_NAME(edtName);
	lblName->setBuddy(edtName);

	lblHost = new QLabel;
	SET_OBJECT_NAME(lblHost);
	edtHost = new QLineEdit;
	SET_OBJECT_NAME(edtHost);
	lblHost->setBuddy(edtHost);

	lblPort = new QLabel;
	SET_OBJECT_NAME(lblPort);
	spinPort = new QSpinBox;
	SET_OBJECT_NAME(spinPort);
	spinPort->setRange(1, std::numeric_limits<uint16_t>::max());
	lblPort->setBuddy(spinPort);

	lblUser = new QLabel;
	SET_OBJECT_NAME(lblUser);
	edtUser = new QLineEdit;
	SET_OBJECT_NAME(edtUser);
	lblUser->setBuddy(edtUser);

    lblPassword = new QLabel;
    SET_OBJECT_NAME(lblPassword);
    edtPassword = new QLineEdit;
    SET_OBJECT_NAME(edtPassword);
    edtPassword->setEchoMode(QLineEdit::PasswordEchoOnEdit);
    connect(edtPassword, &QLineEdit::textEdited, this, &ConnectionConfigurationWidget::passwordEdited);

    cbSavePassword = new QCheckBox;
    SET_OBJECT_NAME(cbSavePassword);

	lblDbName = new QLabel;
	SET_OBJECT_NAME(lblDbName);
    cmbDbname = new QComboBox;
    cmbDbname->setEditable(true); // allows to enter the database name even if test failed.
    cmbDbname->setInsertPolicy(QComboBox::InsertAlphabetically);

	lblSsl = new QLabel;
	SET_OBJECT_NAME(lblSsl);
	cmbbxSsl = new QComboBox;
	SET_OBJECT_NAME(cmbbxSsl);
	cmbbxSsl->setModelColumn(0);
	auto ssl_model = new SslModeModel(this);
	cmbbxSsl->setModel(ssl_model);
    cmbbxSsl->setEditable(true);
	lblSsl->setBuddy(cmbbxSsl);

	lblCert = new QLabel;
	SET_OBJECT_NAME(lblCert);
	edtCert = new QLineEdit;
	SET_OBJECT_NAME(edtCert);
	lblCert->setBuddy(edtCert);

	lblKey = new QLabel;
	SET_OBJECT_NAME(lblKey);
	edtKey = new QLineEdit;
	SET_OBJECT_NAME(edtKey);
	lblKey->setBuddy(edtKey);

	lblRootCert = new QLabel;
	SET_OBJECT_NAME(lblRootCert);
	edtRootCert = new QLineEdit;
	SET_OBJECT_NAME(edtRootCert);
	lblRootCert->setBuddy(edtRootCert);

	lblCrl = new QLabel;
	SET_OBJECT_NAME(lblCrl);
	edtCrl = new QLineEdit;
	SET_OBJECT_NAME(edtCrl);
	lblCrl->setBuddy(edtCrl);

    btnTest = new QPushButton;
    connect(btnTest, &QPushButton::clicked, this, &ConnectionConfigurationWidget::testConnection);

    lblResult = new QLabel;

	formLayout = new QFormLayout;

	formLayout->addRow(lblGroup, cmbbxGroup);
	formLayout->addRow(lblName, edtName);
	formLayout->addRow(lblHost, edtHost);
	formLayout->addRow(lblPort, spinPort);
	formLayout->addRow(lblUser, edtUser);
    formLayout->addRow(lblPassword, edtPassword);
    formLayout->addRow(nullptr, cbSavePassword);
    formLayout->addRow(lblDbName, cmbDbname);
	formLayout->addRow(lblSsl, cmbbxSsl);
	formLayout->addRow(lblCert, edtCert);
	formLayout->addRow(lblKey, edtKey);
	formLayout->addRow(lblRootCert, edtRootCert);
	formLayout->addRow(lblCrl, edtCrl);
    formLayout->addRow(btnTest, lblResult);
    setLayout(formLayout);

	retranslateUi();
}

void ConnectionConfigurationWidget::retranslateUi()
{
	lblGroup->setText(QApplication::translate("ConnectionConfigurationWidget", "&Group", nullptr));
	lblName->setText(QApplication::translate("ConnectionConfigurationWidget", "&Name", nullptr));
	lblHost->setText(QApplication::translate("ConnectionConfigurationWidget", "&Host", nullptr));
	lblPort->setText(QApplication::translate("ConnectionConfigurationWidget", "&Port", nullptr));
	lblUser->setText(QApplication::translate("ConnectionConfigurationWidget", "&Username", nullptr));
    lblPassword->setText(QApplication::translate("ConnectionConfigurationWidget", "Password", nullptr));
    cbSavePassword->setText(QApplication::translate("ConnectionConfigurationWidget", "&Save password", nullptr));
    lblDbName->setText(QApplication::translate("ConnectionConfigurationWidget", "Database", nullptr));
	lblSsl->setText(QApplication::translate("ConnectionConfigurationWidget", "&SSL mode", nullptr));
	lblCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Certificate", nullptr));
	lblKey->setText(QApplication::translate("ConnectionConfigurationWidget", "&Key", nullptr));
	lblRootCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Root cert.", nullptr));
	lblCrl->setText(QApplication::translate("ConnectionConfigurationWidget", "Revocation &list", nullptr));
    btnTest->setText(QApplication::translate("ConnectionConfigurationWidget", "Test", nullptr));
}

void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg)
{
	auto group = cfg.parent();
	if (group) {
		auto group_idx = m_connectionModel->findGroup(group->conngroup_id);
		cmbbxGroup->setCurrentIndex(group_idx);
	}

	m_uuid = cfg.uuid();
	edtName->setText(cfg.name());
	edtHost->setText(cfg.host());
	spinPort->setValue(cfg.port());
	edtUser->setText(cfg.user());
    edtPassword->setText("");

    cmbDbname->setCurrentText(cfg.dbname());
    //cmbbxSsl->setCurrentIndex(static_cast<int>(cfg.sslMode()));
    cmbbxSsl->setCurrentText(cfg.getParameter("sslmode"));
	edtCert->setText(cfg.sslCert());
	edtKey->setText(cfg.sslKey());
	edtRootCert->setText(cfg.sslRootCert());
	edtCrl->setText(cfg.sslCrl());

    encodedPassword = cfg.encodedPassword();
    cbSavePassword->setCheckState(
        encodedPassword.isEmpty()
                ? Qt::CheckState::Unchecked
                : Qt::CheckState::Checked
    );

    passwordChanged = false;
}

ConnectionConfig ConnectionConfigurationWidget::data() const
{
	ConnectionConfig cfg;
	cfg.setUuid(m_uuid);
	cfg.setName(edtName->text());
	cfg.setHost(edtHost->text());
	cfg.setPort(static_cast<uint16_t>(spinPort->value()));
	cfg.setUser(edtUser->text());
    cfg.setPassword(edtPassword->text());
    cfg.setDbname(cmbDbname->currentText());
	cfg.setSslMode(static_cast<SslMode>(cmbbxSsl->currentIndex()));
	cfg.setSslCert(edtCert->text());
	cfg.setSslKey(edtKey->text());
	cfg.setSslRootCert(edtRootCert->text());
	cfg.setSslCrl(edtCrl->text());
    cfg.setEncodedPassword(encodedPassword); // this could be old, it will be overriden later when there is a new password to save.
    return cfg;
}

QString ConnectionConfigurationWidget::group() const
{
    return cmbbxGroup->currentText();
}

bool ConnectionConfigurationWidget::savePassword() const
{
    return cbSavePassword->isChecked() && !edtPassword->text().isEmpty() && passwordChanged;
}

bool ConnectionConfigurationWidget::clearPassword() const
{
    return !cbSavePassword->isChecked();
}

void ConnectionConfigurationWidget::testConnection()
{
    QString password = edtPassword->text();
    if (password.isEmpty() && !encodedPassword.isEmpty()) {
        m_connectionController->decodeConnectionPassword(m_uuid, encodedPassword, password);
    }

    auto cc = data();
    cc.setPassword(password);

    auto qthis = QPointer(this);
    QFuture<void> result = QtConcurrent::run(
        [cc]
        {
            return TestConnection(cc);
        }
    ).then(qApp,
        [qthis](TestConnectionResult result)
        {
            if (qthis)
                qthis.data()->handleTestResult(result);
        }
    );
}

void ConnectionConfigurationWidget::handleTestResult(TestConnectionResult result)
{
    lblResult->setText(result.message());
    QString current = cmbDbname->currentText();
    cmbDbname->clear();
    for (auto &dbn : result.databases())
        cmbDbname->addItem(dbn);
    cmbDbname->setCurrentText(current);
}

void ConnectionConfigurationWidget::passwordEdited(const QString &)
{
    passwordChanged = true;
}

