#ifndef FZ_AUTHENTICATION_IDENTITY_PROVIDER_HPP
#define FZ_AUTHENTICATION_IDENTITY_PROVIDER_HPP

#include "password_with_impersonation.hpp"
#include "method.hpp"
#include "error.hpp"

#include "../tvfs/mount.hpp"

namespace fz::authentication
{

class identity_provider
{
public:
	struct parameter
	{
		std::string type;
		std::string value;

		template <typename Archive>
		void serialize(Archive &ar)
		{
			using namespace fz::serialization;

			ar.attribute(type, "type")(
				nvp(value, "")
			);
		}
	};

	struct parameters: std::vector<parameter>
	{
		using vector::vector;

		struct multi_value
		{
			std::optional<bool> as_bool() const;
			const std::string &as_string() const;

			multi_value() = default;
			explicit multi_value(const std::string &value)
				: value_(&value)
			{}

			explicit operator bool() const
			{
				return value_ != nullptr;
			}

			operator const std::string &() const
			{
				return as_string();
			}

		private:
			const std::string *value_{};
		};

		multi_value get(std::string_view type) const
		{
			auto it = std::find_if(begin(), end(), [&](auto &p) {
				return p.type == type;
			});

			if (it != end()) {
				return multi_value(it->value);
			}

			return {};
		}

		void set(std::string_view type, std::string value)
		{
			auto it = std::find_if(begin(), end(), [&](auto &p) {
				return p.type == type;
			});

			if (it == end()) {
				push_back(parameter{std::string(type), std::move(value)});
			}
			else {
				it->value = std::move(value);
			}
		}

		void erase(std::string_view type)
		{
			auto it = std::find_if(begin(), end(), [&](auto &p) {
				return p.type == type;
			});

			if (it != end()) {
				static_cast<std::vector<parameter>&>(*this).erase(it);
			}
		}

	};

	struct user_properties
	{
		std::optional<password::with_impersonation> password;
		std::optional<tvfs::mount_table> mount_table;
		std::optional<std::string> description;
		std::optional<available_methods> methods;
		std::optional<std::vector<std::string>> groups;

		template <typename Archive>
		void serialize(Archive &ar)
		{
			using namespace fz::serialization;

			ar(
				nvp(password, "password"),
				nvp(mount_table, "mount_table"),
				nvp(description, "description"),
				nvp(groups, "groups"),
				nvp(methods, "methods")
			);
		}
	};

	struct user_info
	{

		// Unique identifier within the scope of a given identity provider
		std::string uid;

		// The account name representing the user in the context of a given identity provider.
		// Might differ from the login string passed to get_auth_data_for_user.
		// It can be used as the username argument of the impersonation_token constructor.
		native_string account;

		// Optional user's properties retrieved by the identity provider.
		user_properties properties;

		template <typename Archive>
		void serialize(Archive &ar)
		{
			using namespace fz::serialization;

			ar(
				nvp(uid, "uid"),
				nvp(account, "account"),
				nvp(properties, "properties")
			);
		}
	};

	virtual ~identity_provider(){}
	virtual authentication::error get_user_info(std::string_view login, const parameters &parameters, user_info &uinfo) = 0;
	virtual const std::string &get_name() const = 0;

	/// \returns the properties of the default IDP user.
	virtual user_properties get_default_user_properties() const = 0;

	/// \returns a user_properties structure whose members must be probed for engagement in order to determine what property *any* user will have.
	virtual std::optional<user_properties> get_properties(const parameters &parameters) const = 0;
};

}

#endif // FZ_AUTHENTICATION_IDENTITY_PROVIDER_HPP
