#include <libfilezilla/glue/wx.hpp>
#include <libfilezilla/util.hpp>
#include <libfilezilla/encode.hpp>
#include <libfilezilla/string.hpp>

#include "credentialseditor.hpp"
#include "locale.hpp"
#include "glue.hpp"
#include "passwordeditor.hpp"

#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include <wx/valtext.h>
#include <wx/choicebk.h>
#include <wx/combobox.h>
#include <wx/simplebook.h>
#include <wx/statline.h>
#include <wx/button.h>
#include <wx/richmsgdlg.h>

#include "helpers.hpp"
#include "textvalidatorex.hpp"

#include "../filezilla/authentication/system_identity_provider.hpp"

namespace {


enum {
	creds_noauth      = 0,
	creds_local_user  = 1,
	creds_system_user = 2,
};

}

CredentialsEditor::~CredentialsEditor()
{
}

bool CredentialsEditor::Create(wxWindow *parent)
{
	if (!wxPanel::Create(parent, nullID)) {
		return false;
	}

	SetName(GetParent()->GetName() + wxS("/CredentialsEditor"));

	wxVBox(this, 0) = {
		creds_choices_ = wxCreate<wxChoicebook>(this, nullID) | [&](wxChoicebook *p) {
			wxPage(wxValidateOnlyIfCurrent)(p, _S("Do not require authentication")) | [&](auto p) {
				wxTransferDataToWindow(p, [&] {
					update_none_idp_properties();
					Event::ChangedMode.Process(this, this, AuthMode::NoAuth);
					return true;
				});
			};

			password_editor_ = wxPage<PasswordEditor>(wxValidateOnlyIfCurrent)(p, _S("Require a password to log in"), true, false);
			wxTransferDataToWindow(password_editor_, [this] {
				update_local_idp_properties();
				Event::ChangedMode.Process(this, this, AuthMode::OwnPassword);
				return true;
			});

			wxPage(wxValidateOnlyIfCurrent)(p, _S("Use system credentials to log in")) |[&] (auto p) {
				wxVBox(p, 0) = {
					system_user_impersonate_ = new wxCheckBox(p, wxID_ANY, _S("Use system credentials also for accessing &files and directories"))
				};

				wxTransferDataToWindow(p, [&] {
					update_system_idp_properties(system_user_impersonate_);
					Event::ChangedMode.Process(this, this, AuthMode::SystemPassword);
					return true;
				});
			};
		},
	};

	SetCredentials({}, nullptr, nullptr, {});

	return true;
}

void CredentialsEditor::SetCredentials(opt_identity_profile *identity, fz::authentication::credentials *credentials, bool *no_auth, const std::string &user_name)
{
	identity_ = identity;
	credentials_ = credentials;
	no_auth_ = no_auth;
	user_name_ = user_name;

	if (!identity || !credentials || !no_auth) {
		creds_choices_->SetSelection(creds_noauth);
		creds_choices_->Disable();
		password_editor_->SetPassword(nullptr);

		return;
	}

	creds_choices_->Enable();

	auto has_any_idp = [&]() {
		return identity->has_value();
	};

	auto has_idp = [&](const std::string &name) {
		return has_any_idp() && identity->value().idp == name;
	};

	auto is_idp_user = [&](const std::string &name) {
		return has_idp(name) && fz::starts_with(user_name, fz::authentication::file_based_authenticator::username_for_idp(name));
	};

	if (is_idp_user(fz::authentication::system_identity_provider::name)) {
		creds_choices_->ChangeSelection(creds_system_user);
		creds_choices_->GetChoiceCtrl()->Disable();
	}
	else {
		creds_choices_->GetChoiceCtrl()->Enable();

		if (*no_auth) {
			creds_choices_->ChangeSelection(creds_noauth);
		}
		else
		if (has_idp(fz::authentication::system_identity_provider::name)) {
			creds_choices_->ChangeSelection(creds_system_user);
		}
		else {
			if (auto *p = credentials_->password.get()) {
				password_ = *p;
			}
			else {
				password_.reset();
			}

			creds_choices_->ChangeSelection(creds_local_user);
		}
	}

	creds_choices_->TransferDataToWindow();
}

void CredentialsEditor::SetGetIdpPropertiesFunc(GetIdpPropertiesFunc func)
{
	get_idp_properties_func_ = std::move(func);
}

const std::optional<fz::expected<fz::authentication::identity_provider::user_properties, wxString>> &CredentialsEditor::GetIdpProperties() const
{
	return idp_properties_;
}

bool CredentialsEditor::TransferDataFromWindow()
{
	if (!wxPanel::TransferDataFromWindow())
		return false;

	if (!credentials_)
		return true;

	auto set_impersonation = [&](bool impersonate_filesystem) {
		credentials_->password.impersonate(!impersonate_filesystem);
	};

	auto set_idp = [&](const std::string &name) {
		if (!identity_->has_value()) {
			identity_->emplace();
		}

		if (identity_->value().idp != name) {
			identity_->value().idp = name;
			identity_->value().uid.clear();
		}
	};

	if (creds_choices_->GetSelection() == creds_local_user) {
		wxASSERT_MSG(password_, "Invalid state for password_");

		credentials_->password = *password_;
		identity_->reset();
	}
	else
	if (creds_choices_->GetSelection() == creds_system_user) {
		set_idp(fz::authentication::system_identity_provider::name);
		set_impersonation(system_user_impersonate_->GetValue());
	}

	if (no_auth_) {
		*no_auth_ = creds_choices_->GetSelection() == 0;
	}

	return true;
}

bool CredentialsEditor::update_idp_properties(const std::string &idp, const fz::authentication::identity_provider::parameters &params)
{
	if (!get_idp_properties_func_) {
		return false;
	}

	idp_properties_ = get_idp_properties_func_(idp, params);
	if (!idp_properties_->has_value()) {
		wxMsg::Error(_S("Could not retrieve user properties.")).Ext(idp_properties_->error());
		return false;
	}

	return true;
}


bool CredentialsEditor::update_system_idp_properties(wxCheckBox *impersonate_check)
{
	if (update_idp_properties(fz::authentication::system_identity_provider::name, {})) {
		return act_on_idp_properties(impersonate_check);
	}

	return false;
}

bool CredentialsEditor::update_local_idp_properties()
{
	idp_properties_.emplace();
	return act_on_idp_properties(nullptr);
}

bool CredentialsEditor::update_none_idp_properties()
{
	idp_properties_.reset();
	return act_on_idp_properties(nullptr);
}

bool CredentialsEditor::act_on_idp_properties(wxCheckBox *impersonate_check)
{
	if (!idp_properties_) {
		password_.reset();
		if (impersonate_check) {
			impersonate_check->Disable();
		}
		return true;
	}

	if (!idp_properties_->has_value()) {
		return false;
	}

	if (impersonate_check) {
		if (idp_properties_->value().password && idp_properties_->value().password->get_impersonation()) {
			impersonate_check->Enable();
			if (auto i = credentials_->password.get_impersonation()) {
				impersonate_check->SetValue(!i->login_only);
			}
			else {
				impersonate_check->SetValue(false);
			}
		}
		else {
			impersonate_check->Disable();
			impersonate_check->SetValue(false);
		}
	}

	// If password is not provided by the IDP, we let the user add one.
	if (!idp_properties_->value().password) {
		if (!password_) {
			password_ = fz::authentication::password::none();
		}
		password_editor_->SetPassword(&*password_);
	}
	// Else, we don't.
	else {
		password_editor_->SetPassword(nullptr);
	}

	return true;
}
