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

#include <wx/listctrl.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/splitter.h>
#include <wx/valtext.h>
#include <wx/checkbox.h>
#include <wx/msgdlg.h>
#include <wx/combo.h>
#include <wx/checklst.h>
#include <wx/rearrangectrl.h>
#include <wx/simplebook.h>
#include <wx/statline.h>
#include <wx/evtloop.h>
#include <wx/popupwin.h>

#include "rearrangeditemspickerctrl.hpp"
#include "userseditor.hpp"
#include "mounttableeditor.hpp"
#include "userslist.hpp"
#include "filterseditor.hpp"
#include "credentialseditor.hpp"
#include "limitseditor.hpp"
#include "realmseditor.hpp"
#include "locale.hpp"
#include "glue.hpp"

#include "helpers.hpp"

namespace {

enum page: size_t {
	enabled  = 0,
	disabled = 1
};

}

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

	SetName(wxS("wxfzftpuserseditor"));

	wxHBox(this, 0) = {
		{ wxSizerFlags(0).Expand(), users_list_ = new UsersList(this) },
		{ wxSizerFlags(1).Expand(), book_ = wxCreate<wxNavigationEnabled<wxSimplebook>>(this) | [&](auto p) {
			wxPage<wxNotebook>(p, wxS("*Enabled*"), false, wxID_ANY) |[&](wxNotebook *p) {
				wxPage(p,  _S("General"), true) |[&](auto p) {
					wxVBox(p) = {
						enabled_check_ = new wxCheckBox(p, wxID_ANY, _S("User is enabled")),
						new wxStaticLine(p),

						wxLabel(p, _S("Aut&hentication:")),
						credentials_editor_ = wxCreate<CredentialsEditor>(p),

						wxLabel(p, _S("Me&mber of groups:")),
						groups_book_ = wxCreate<wxNavigationEnabled<wxSimplebook>>(p) |[&](auto p) {
							 groups_chooser_ = wxPage<RearrangedItemsPickerCtrl>(p, wxS("*Enabled*"), true);
						},

						mount_table_label_ = wxLabel(p, _S("Mount p&oints:")),
						wxSizerFlags(3) >>= mount_table_editor_ = wxCreate<MountTableEditor>(p),

						wxLabel(p, _S("Descr&iption:")),
						wxSizerFlags(1) >>= description_editor_ = new wxTextCtrl(p, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE)
					};
				};

				wxPage(p, _S("Filters")) |[&](auto p) {
					wxVBox(p) = filters_editor_ = wxCreate<FiltersEditor>(p);
				};

				wxPage(p, _S("Limits")) |[&](auto p) {
					wxVBox(p) = {
						limits_editor_ = wxCreate<LimitsEditor>(p),
					};
				};

				wxPage(p, _S("Protocol policies")) |[&](auto p) {
					wxVBox(p) = {
						realms_editor_ = wxCreate<RealmsEditor>(p, RealmsEditor::for_users),
					};
				};
			};

			wxPage(p, wxS("*Disabled*"), true) |[&](auto p) {
				wxVBox(p) = {
					wxEmptySpace,
					wxSizerFlags(0).Align(wxALIGN_CENTER) >>= wxLabel(p, _S("Select or add a user in the list to the left")),
					wxEmptySpace
				};
			};
		}}
	};

	users_list_->Bind(UsersList::Event::AboutToDeselectItem, [this](auto &ev){
		if (!Validate() || !TransferDataFromWindow())
			ev.Veto();
		else
			ev.Skip();
	});

	users_list_->Bind(UsersList::Event::SelectedItem, [this](auto &ev){
		ev.Skip();

		TransferDataToWindow();
	});


	credentials_editor_->Bind(CredentialsEditor::Event::ChangedMode, [this](auto &ev) {
		mount_table_editor_->SetUserLogsInWithSystemCredentials(
			ev.GetMode() == CredentialsEditor::AuthMode::SystemPassword
		);
	});

	return true;
}

void UsersEditor::SetGroups(const fz::authentication::file_based_authenticator::groups &groups)
{
	fz::transformed_view group_names(groups, [](auto &g) -> const std::string& {
		return g.first;
	});

	std::vector<std::string> items;
	items.reserve(groups.size());

	for (const auto &n: group_names) {
		items.push_back(n);
	}

	groups_chooser_->SetAvailableItems(std::move(items));
}

void UsersEditor::SetServerPathFormat(fz::util::fs::path_format server_path_format)
{
	mount_table_editor_->SetNativePathFormat(server_path_format);
}

void UsersEditor::SetGetIdpUserInfoFunc(GetIdpUserInfoFunc func)
{
	get_idp_user_info_func_ = std::move(func);
}

void UsersEditor::SetGetIdpPropertiesFunc(CredentialsEditor::GetIdpPropertiesFunc func)
{
	credentials_editor_->SetGetIdpPropertiesFunc(std::move(func));
};

void UsersEditor::SetUsers(fz::authentication::file_based_authenticator::users &users, std::string server_name)
{
	users_ = &users;
	server_name_ = server_name;
	users_list_->SetMap(users);

	// Map used to detect duplicate UIDs when enabling users
	uidmap_.clear();
	for (auto &u: users) {
		auto &ue = u.second;
		if (ue.enabled && ue.identity && !ue.identity->uid.empty()) {
			uidmap_[ue.identity->idp][ue.identity->uid] = &u;
		}
	}
}

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

	current_user_ = users_list_->GetSelectedItem();

	if (!current_user_)
		return true;

	auto &u = current_user_->second;

	if (u.identity &&
		fz::authentication::file_based_authenticator::username_for_idp(u.identity->idp) != current_user_->first &&
		get_idp_user_info_func_)
	{
		if (enabled_check_->GetValue()) {
			if (u.identity->uid.empty()) {
				auto res = get_idp_user_info_func_(current_user_->first, u.identity->idp, u.identity->parameters);

				if (res) {
					auto [it, emplaced] = uidmap_[u.identity->idp].emplace(res->uid, current_user_);
					if (emplaced) {
						u.identity->uid = std::move(res->uid);
					}
					else {
						res = fz::unexpected(_F("Both local users %s and %s map to the same %s account (UID: %s)",
												current_user_->first, it->second->first, u.identity->idp, res->uid));
					}
				}

				if (!res) {
					wxMsg::Error(_S("User %s cannot be enabled."), current_user_->first)
						.Ext(_S("Reason: %s."), res.error());

					enabled_check_->SetValue(false);
					return false;
				}
			}
		}
		else {
			uidmap_[u.identity->idp].erase(u.identity->uid);
		}
	}

	groups_chooser_->GetActiveItems(u.groups);

	u.description = fz::to_utf8(description_editor_->GetValue());
	u.enabled = enabled_check_->GetValue();

	std::array<fz::authentication::methods_set*, 2> otps{};

	if (no_auth_)
		u.methods = { {} };
	else {
		u.methods = { fz::authentication::method::password() };
		otps[0] = &u.methods[0];
	}

	return true;
}

bool UsersEditor::TransferDataToWindow()
{
	current_user_ = users_list_->GetSelectedItem();

	if (!current_user_) {
		credentials_editor_->SetCredentials(nullptr, nullptr, nullptr, {});

		groups_chooser_->ClearActiveItems();
		mount_table_editor_->SetTable(nullptr);
		filters_editor_->SetIps(nullptr, nullptr);
		limits_editor_->SetSpeedLimits(nullptr);
		limits_editor_->SetTvfsLimits(nullptr);
		limits_editor_->SetSessionCountLimit(nullptr);
		realms_editor_->SetRealms(nullptr);
		description_editor_->ChangeValue(wxEmptyString);
	}
	else {
		auto &u = current_user_->second;

		if (!u.credentials.is_valid_for(u.methods)) {
			u.enabled = false;
			u.methods = u.credentials.get_most_secure_methods();

			// Delay, don't do dialogs in TransferDataToWindow
			CallAfter([name = current_user_->first]() {
				wxMsg::Error(wxT("User \"%s\" configuration has become inconsisent."), name).Ext(wxT(
					"This user has been disabled for security.\n"
					"\n"
					"Before re-enabling this user, please check that the authentication methods and credentials are set as intended."
				));
			});
		}

		// If the user is disabled, precautiously erase its UID, so that enabling it again will force the retrieval of updated information.
		if (!u.enabled && u.identity) {
			u.identity->uid.clear();
		}

		enabled_check_->SetValue(u.enabled);
		no_auth_ = !u.methods.is_auth_necessary();

		credentials_editor_->SetCredentials(&u.identity, &u.credentials, &no_auth_, std::string(current_user_->first).append("@").append(server_name_));

		groups_chooser_->SetActiveItems(u.groups);
		mount_table_editor_->SetTable(&u.mount_table);
		mount_table_editor_->SetUserLogsInWithSystemCredentials(u.credentials.password.get_impersonation());
		filters_editor_->SetIps(&u.disallowed_ips, &u.allowed_ips);
		limits_editor_->SetSpeedLimits(&u.rate_limits);
		limits_editor_->SetTvfsLimits(&u.session_open_limits);
		limits_editor_->SetSessionCountLimit(&u.session_count_limit);
		realms_editor_->SetRealms(&u.realms);
		description_editor_->ChangeValue(fz::to_wxString(u.description));
	}

	wxPanel::TransferDataToWindow();

	book_->ChangeSelection(current_user_ ? page::enabled : page::disabled);

	return true;
}
