#ifndef SECURABLE_CHANNEL_HPP
#define SECURABLE_CHANNEL_HPP

#include <variant>
#include <optional>

#include <libfilezilla/socket.hpp>
#include <libfilezilla/tls_layer.hpp>
#include <libfilezilla/tls_info.hpp>
#include <libfilezilla/tls_system_trust_store.hpp>
#include <libfilezilla/uri.hpp>
#include <libfilezilla/encryption.hpp>

#include "socket_stack.hpp"
#include "util/filesystem.hpp"
#include "acme/cert_info.hpp"
#include "mpl/with_index.hpp"
#include "mpl/index_of.hpp"
#include "enum_names.hpp"

namespace fz {

enum class securable_socket_state {
	failed_handshake = -6,
	wrong_alpn = -5,
	invalid_socket_state = -4,
	failed_setting_certificate_file = -3,
	session_socket_not_secure = -2,
	session_not_resumed = -1,
	insecure,
	about_to_secure,
	securing,
	secured
};

FZ_ENUM_NAMES_DEFINE_FOR(
	securable_socket_state::failed_handshake,
	securable_socket_state::wrong_alpn,
	securable_socket_state::invalid_socket_state,
	securable_socket_state::failed_setting_certificate_file,
	securable_socket_state::session_socket_not_secure,
	securable_socket_state::session_not_resumed,
	securable_socket_state::insecure,
	securable_socket_state::about_to_secure,
	securable_socket_state::securing,
	securable_socket_state::secured
)

class securable_socket: public socket_stack_interface
{
	struct user_provided_cert_info
	{
		native_string key_path;
		native_string certs_path;
		native_string password;

		struct extra
		{
			std::string fingerprint{};
			std::string distinguished_name{};
			std::vector<std::string> hostnames{};
			fz::datetime activation_time{};
			fz::datetime expiration_time{};

			template <typename Archive>
			friend void serialize(Archive &ar, securable_socket::user_provided_cert_info::extra &e);
		};

		template <typename Archive>
		friend void serialize(Archive &ar, securable_socket::user_provided_cert_info &ci);
	};

	struct autogenerated_cert_info
	{
		std::string fingerprint;

		struct extra
		{
			std::string distinguished_name{};
			std::vector<std::string> hostnames{};
			fz::datetime activation_time{};
			fz::datetime expiration_time{};

			template <typename Archive>
			friend void serialize(Archive &ar, securable_socket::autogenerated_cert_info::extra &e);
		};

		template <typename Archive>
		friend void serialize(Archive &ar, securable_socket::autogenerated_cert_info &ci);
	};

	struct uploaded_cert_info
	{
		native_string key_path;
		native_string certs_path;
		native_string password;

		struct extra
		{
			std::string fingerprint{};
			std::string distinguished_name{};
			std::vector<std::string> hostnames{};
			fz::datetime activation_time{};
			fz::datetime expiration_time{};

			template <typename Archive>
			friend void serialize(Archive &ar, securable_socket::uploaded_cert_info::extra &e);
		};

		template <typename Archive>
		friend void serialize(Archive &ar, securable_socket::uploaded_cert_info &ci);
	};

	struct acme_cert_info
	{
		std::string account_id;
		std::vector<std::string> hostnames;
		bool autorenew = true;

		explicit operator bool() const
		{
			return !account_id.empty() && !hostnames.empty();
		}

		struct extra
		{
			std::string fingerprint{};
			std::string distinguished_name{};
			acme::extra_account_info account{};
			fz::datetime activation_time{};
			fz::datetime expiration_time{};

			template <typename Archive>
			friend void serialize(Archive &ar, acme_cert_info::extra &e);
		};


		template <typename Archive>
		friend void serialize(Archive &ar, securable_socket::acme_cert_info &ci);
	};

	struct exported_cert_info
	{
		std::variant<
			std::monostate,
			user_provided_cert_info,
			autogenerated_cert_info,
			acme_cert_info,
			uploaded_cert_info
			> original;

		std::string certs;
		std::string key; ///< If empty, then the key is contained within certs. In PEM format

	private:
		friend securable_socket;

		user_provided_cert_info *user_provided() { return std::get_if<user_provided_cert_info>(&original); }
		autogenerated_cert_info *autogenerated() { return std::get_if<autogenerated_cert_info>(&original); }
		acme_cert_info *acme() { return std::get_if<acme_cert_info>(&original); }
		uploaded_cert_info *uploaded() { return std::get_if<uploaded_cert_info>(&original); }

		const user_provided_cert_info *user_provided() const { return std::get_if<user_provided_cert_info>(&original); }
		const autogenerated_cert_info *autogenerated() const { return std::get_if<autogenerated_cert_info>(&original); }
		const acme_cert_info *acme() const { return std::get_if<acme_cert_info>(&original); }
		const uploaded_cert_info *uploaded() const { return std::get_if<uploaded_cert_info>(&original); }

		template <typename Archive>
		friend void serialize(Archive &ar, securable_socket::exported_cert_info &ci);
	};

	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::user_provided_cert_info &ci);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::uploaded_cert_info &ci);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::autogenerated_cert_info &ci);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::acme_cert_info &ci);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::exported_cert_info &ci);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::autogenerated_cert_info::extra &e);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::user_provided_cert_info::extra &e);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::uploaded_cert_info::extra &e);
	template <typename Archive>
	friend void serialize(Archive &ar, securable_socket::acme_cert_info::extra &e);

public:
	struct serializer;
	friend serializer;

	struct certs_and_key {
		fz::tls_param certs;
		fz::tls_param key;
		native_string key_password;

		struct sources {
			struct provided
			{};

			struct autogenerated
			{};

			struct acme
			{
				std::string account_id;
				bool autorenew{};
			};

			using any = std::variant<
				provided,
				autogenerated,
				acme
			>;

			static any from_id(std::size_t id)
			{
				return mpl::with_index<std::variant_size_v<any>>(id, [&](auto i) -> any {
					return any(std::in_place_index<i>);
				});
			}

			template <typename T>
			static std::size_t id_of() {
				return mpl::index_of_v<any, T>;
			}
		};
	};

	struct omni_cert_info: certs_and_key
	{
		template <typename C = tls_param, typename K = tls_param, typename P = native_string, typename S = sources::any>
		omni_cert_info(C && c, K && k, P && p, S && s)
			: certs_and_key{std::forward<C>(c), std::forward<K>(k), std::forward<P>(p)}
			, source{std::forward<S>(s)}
		{}

		template <typename CK = certs_and_key, typename S = sources::any>
		omni_cert_info(CK && ck, S && s)
			: certs_and_key{std::forward<CK>(ck)}
			, source{std::forward<S>(s)}
		{}

		omni_cert_info() = default;

		struct extra
		{
			std::string fingerprint{};
			std::string distinguished_name{};
			std::vector<std::string> hostnames{};
			fz::datetime activation_time{};
			fz::datetime expiration_time{};
		};

		sources::any source;

		bool is_valid() const
		{
			return certs && key;
		}

		explicit operator bool() const
		{
			return is_valid();
		}

		const sources::provided *provided() const
		{
			return std::get_if<sources::provided>(&source);
		}

		const sources::autogenerated *autogenerated() const
		{
			return std::get_if<sources::autogenerated>(&source);
		}

		const sources::acme *acme() const
		{
			return std::get_if<sources::acme>(&source);
		}

		sources::provided *provided()
		{
			return std::get_if<sources::provided>(&source);
		}

		sources::autogenerated *autogenerated()
		{
			return std::get_if<sources::autogenerated>(&source);
		}

		sources::acme *acme()
		{
			return std::get_if<sources::acme>(&source);
		}
	};


	// If cert_info holds a exported_cert_info type, when set_root_path is invoked
	// Then cert_info is transformed into holding another, best fitting, type,
	// and the certs and key are written to the proper files.
	struct cert_info: std::variant<
		std::monostate,
		user_provided_cert_info,
		autogenerated_cert_info,
		acme_cert_info,
		exported_cert_info,
		uploaded_cert_info,
		omni_cert_info
	>
	{
		struct extra: std::variant<
			std::monostate,
			user_provided_cert_info::extra,
			autogenerated_cert_info::extra,
			acme_cert_info::extra,
			uploaded_cert_info::extra,
			omni_cert_info::extra
		>{
			using variant::variant;

			bool has_value() const
			{
				return !std::holds_alternative<std::monostate>(*this);
			}

			explicit operator bool() const
			{
				return has_value();
			}

			bool expired() const;

			omni_cert_info::extra *omni() { return std::get_if<omni_cert_info::extra>(this); }
			const omni_cert_info::extra *omni() const { return std::get_if<omni_cert_info::extra>(this); }

		private:
			friend securable_socket;

			user_provided_cert_info::extra *user_provided() { return std::get_if<user_provided_cert_info::extra>(this); }
			autogenerated_cert_info::extra *autogenerated() { return std::get_if<autogenerated_cert_info::extra>(this); }
			acme_cert_info::extra *acme() { return std::get_if<acme_cert_info::extra>(this); }
			uploaded_cert_info::extra *uploaded() { return std::get_if<uploaded_cert_info::extra>(this); }

			const user_provided_cert_info::extra *user_provided() const { return std::get_if<user_provided_cert_info::extra>(this); }
			const autogenerated_cert_info::extra *autogenerated() const { return std::get_if<autogenerated_cert_info::extra>(this); }
			const acme_cert_info::extra *acme() const { return std::get_if<acme_cert_info::extra>(this); }
			const uploaded_cert_info::extra *uploaded() const { return std::get_if<uploaded_cert_info::extra>(this); }
		};

		using variant::variant;

		cert_info() = default;
		cert_info(const cert_info &) = default;
		cert_info(cert_info &&) = default;
		cert_info &operator=(const cert_info &) = default;
		cert_info &operator=(cert_info &&) = default;

		bool is_valid() const
		{
			return std::get_if<std::monostate>(this) == nullptr;
		}

		explicit operator bool() const
		{
			return is_valid();
		}

		bool set_root_path(const util::fs::native_path &root_path, logger_interface *logger = nullptr);

		util::fs::native_path key_path() const;
		util::fs::native_path certs_path() const;

		fz::const_tls_param_ref key() const;
		fz::const_tls_param_ref certs() const;

		native_string key_password() const;

		std::vector<x509_certificate> load_certs(fz::logger_interface *logger = nullptr) const;
		extra load_extra(fz::logger_interface *logger = nullptr) const;

		void dump(logger_interface &logger, bool only_sha256);
		static cert_info generate_selfsigned(fz::tls_param key, const util::fs::native_path &root_path, logger_interface &logger = get_null_logger(), native_string const& password = {}, std::string const& distinguished_name = {}, std::vector<std::string> const& hostnames = {});

		cert_info generate_exported() const;

		void remove();

		omni_cert_info *omni() { return std::get_if<omni_cert_info>(this); }
		const omni_cert_info *omni() const { return std::get_if<omni_cert_info>(this); }

		std::string fingerprint(const extra &extra) const;

	private:
		friend securable_socket;

		bool resolve_paths(fz::logger_interface *logger = nullptr);

		user_provided_cert_info *user_provided() { return std::get_if<user_provided_cert_info>(this); }
		autogenerated_cert_info *autogenerated() { return std::get_if<autogenerated_cert_info>(this); }
		acme_cert_info *acme() { return std::get_if<acme_cert_info>(this); }
		uploaded_cert_info *uploaded() { return std::get_if<uploaded_cert_info>(this); }
		exported_cert_info *exported() { return std::get_if<exported_cert_info>(this); }

		const user_provided_cert_info *user_provided() const { return std::get_if<user_provided_cert_info>(this); }
		const autogenerated_cert_info *autogenerated() const { return std::get_if<autogenerated_cert_info>(this); }
		const acme_cert_info *acme() const { return std::get_if<acme_cert_info>(this); }
		const uploaded_cert_info *uploaded() const { return std::get_if<uploaded_cert_info>(this); }
		const exported_cert_info *exported() const { return std::get_if<exported_cert_info>(this); }

		util::fs::native_path root_path_;
		fz::tls_param resolved_key_;
		fz::tls_param resolved_certs_;
	};

	struct info
	{
		cert_info cert;
		tls_ver min_tls_ver = tls_ver::v1_2;
	};

	struct session_info {
		using algorithm_warnings_t = tls_session_info::algorithm_warnings_t;

		/// The symmetric algorithm used to encrypt all exchanged application data
		std::string cipher;

		/// The MAC used for integrity-protect and authenticate the exchanged application data
		std::string mac;

		/// TLS version
		std::string tls_version;

		/// Key exchange algorithm
		std::string key_exchange;

		/// Warnings about old algorithms used, which are considered weak
		algorithm_warnings_t algorithm_warnings;
	};

	struct securer {
		~securer();

		securer(const securer &) = delete;
		securer(securer &&) = delete;
		securer &operator=(const securer &) = delete;
		securer &operator=(securer &&) = delete;

		operator bool() const
		{
			return owner_.securable_state_ >= securable_socket_state::about_to_secure;
		}

		securable_socket_state get_state() const
		{
			return owner_.securable_state_;
		}

	private:
		friend securable_socket;

		securer(securable_socket &owner,
				bool make_server,
				tls_ver min_tls_ver,
				const securable_socket::cert_info *cert_info,
				tls_system_trust_store *trust_store,
				securable_socket *socket_to_get_tls_session_from,
				std::string_view preamble,
				std::vector<std::string> alpns,
				bool alpn_mandatory);

		securable_socket &owner_;
		bool make_server_{};
		securable_socket *socket_to_get_tls_session_from_{};
		std::string preamble_{};
		std::vector<std::string> alpns_{};
		bool alpn_mandatory_;
	};

	securable_socket(event_loop &loop, event_handler *event_handler, std::unique_ptr<socket> socket_, logger_interface &logger);
	securable_socket(event_loop &loop, event_handler *event_handler, std::unique_ptr<socket_stack> socket_stack, logger_interface &logger);
	~securable_socket() override;

	securer make_secure_server(tls_ver min_tls_ver, const cert_info &cert_info, securable_socket *socket_to_get_tls_session_from = {}, std::string_view preamble = {}, std::vector<std::string> alpns = {}, bool alpn_mandatory = false);
	securer make_secure_client(tls_ver min_tls_ver, const cert_info *cert_info = {}, tls_system_trust_store *trust_store = {}, securable_socket *socket_to_get_tls_session_from = {}, std::vector<std::string> alpns = {}, bool alpn_mandatory = false);

	securable_socket_state get_securable_state() const;
	bool is_secure() const
	{
		return get_securable_state() == securable_socket_state::secured;
	}

	std::optional<session_info> get_session_info() const;

	void set_verification_result(bool trusted);

	int new_session_ticket();

	std::string get_alpn() const;

	void set_unexpected_eof_cb(std::function<bool()> cb);

	// socket_stack_interface interface
public:
	void push(std::unique_ptr<socket_layer> socket_layer) override
	{
		socket_stack_->push(std::move(socket_layer));
	}

	void pop() override
	{
		socket_stack_->pop();
	}

	socket_interface &top() const override
	{
		return socket_stack_->top();
	}

	address_type address_family() const override
	{
		return socket_stack_->address_family();
	}

	std::string local_ip(bool strip_zone_index = false) const override
	{
		return socket_stack_->local_ip(strip_zone_index);
	}

	int local_port(int &error) const override
	{
		return socket_stack_->local_port(error);
	}

	std::string peer_ip() const override {
		return socket_stack_->peer_ip();
	}

	int flags() const override
	{
		return socket_stack_->flags();
	}

	void set_flags(int flags, bool enable) override
	{
		socket_stack_->set_flags(flags, enable);
	}

	void set_flags(int flags) override
	{
		socket_stack_->set_flags(flags);
	}

	void set_keepalive_interval(duration const& d) override
	{
		socket_stack_->set_keepalive_interval(d);
	}

	int set_buffer_sizes(int size_receive, int size_send) override
	{
		return socket_stack_->set_buffer_sizes(size_receive, size_send);
	}

	bool bind(std::string const& address) override
	{
		return socket_stack_->bind(address);
	}

	// socket_interface interface
public:
	int read(void *buffer, unsigned int size, int &error) override {
		return socket_stack_->read(buffer, size, error);
	}

	int write(const void *buffer, unsigned int size, int &error) override {
		return socket_stack_->write(buffer, size, error);
	}

	void set_event_handler(event_handler *pEvtHandler, fz::socket_event_flag retrigger_block = {}) override {
		event_handler_ = pEvtHandler;
		return socket_stack_->set_event_handler(pEvtHandler, retrigger_block);
	}

	native_string peer_host() const override {
		return socket_stack_->peer_host();
	}

	int peer_port(int &error) const override {
		return socket_stack_->peer_port(error);
	}

	int connect(const native_string &host, unsigned int port, address_type family = address_type::unknown) override {
		return socket_stack_->connect(host, port, family);
	}

	socket_state get_state() const override {
		return socket_stack_->get_state();
	}

	int shutdown() override;

	int shutdown_read() override;

private:
	event_loop &event_loop_;
	event_handler *event_handler_{};
	logger_interface &logger_;

	std::unique_ptr<socket_stack> socket_stack_{};
	tls_layer *tls_layer_{};
	securable_socket_state securable_state_{securable_socket_state::insecure};

	std::function<bool()> eof_cb_;
};

std::pair<std::string, std::string> split_key_and_cert(std::string_view blob);
std::vector<std::string> get_hostnames(const std::vector<x509_certificate::subject_name> &subjects);
std::vector<std::string> get_hostnames(const_tls_param_ref certs, logger_interface *logger = nullptr);

struct blob_obfuscator
{
	static bool is_obfuscated(std::string_view blob);
	static bool is_obfuscated(const fz::tls_param &param);
	static bool is_obfuscated(const fz::securable_socket::cert_info &info);
	static std::string_view get_obfuscated_blob_id(std::string_view blob);
	static std::string get_obfuscated_blob_id(std::string &&blob);

	static std::pair<const std::string *, std::string_view> get_obfuscated_blob(const securable_socket::cert_info &info);

	bool obfuscate(fz::securable_socket::cert_info &info);
	bool deobfuscate(fz::securable_socket::cert_info &info);
	bool deobfuscate(fz::tls_param &param);
	bool deobfuscate(std::string &blob);

	blob_obfuscator();

private:
	symmetric_key key_;
};


}
#endif // SECURABLE_CHANNEL_HPP
