// Copyright Maarten L. Hekkelman, Radboud University 2008-2013.
//        Copyright Maarten L. Hekkelman, 2014-2026
//  Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file LICENSE_1_0.txt or copy at
//           http://www.boost.org/LICENSE_1_0.txt)

#pragma once

/// \file
/// definition of the zeep::http::server class

#include "zeep/http/access-control.hpp"
#include "zeep/http/asio.hpp"
#include "zeep/http/template-processor.hpp"

#include <chrono>
#include <cstdint>
#include <filesystem>
#include <list>
#include <memory>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <utility>

namespace zeep::http
{

class connection;
class controller;
class error_handler;
class reply;
class request;
class security_context;

/// \brief The libzeep HTTP server implementation. Originally based on example code found in boost::asio.
///
/// The server class is a simple, stand alone HTTP server. Call bind to make it listen to an address/port
/// combination. Add controller classes to do the actual work. These controllers will be tried in the order
/// at which they were added to see if they want to process a request.

class basic_server
{
  public:
	/// \brief Simple server, no security, no template processor
	basic_server();

	/// \brief Simple server, no security, create default template processor with \a docroot
	basic_server(std::string docroot)
		: basic_server()
	{
		set_template_processor(new template_processor(std::move(docroot)));
	}

	/// \brief server with a security context for limited access
	basic_server(security_context *s_ctxt);

	/// \brief server with a security context for limited access, create default template processor with \a docroot
	basic_server(security_context *s_ctxt, std::string docroot)
		: basic_server(s_ctxt)
	{
		set_template_processor(new template_processor(std::move(docroot)));
	}

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

	virtual ~basic_server();

	/// \brief Get the security context provided in the constructor
	[[nodiscard]] security_context &get_security_context() { return *m_security_context; }

	/// \brief Test if a security context was provided in the constructor
	[[nodiscard]] bool has_security_context() const { return m_security_context != nullptr; }

	/// \brief Set the set of allowed methods (default is "GET", "POST", "PUT", "OPTIONS", "HEAD", "DELETE")
	void set_allowed_methods(const std::set<std::string> &methods)
	{
		m_allowed_methods = methods;
	}

	/// \brief Get the set of allowed methods
	[[nodiscard]] std::set<std::string> get_allowed_methods() const
	{
		return m_allowed_methods;
	}

	/// \brief Set the access_control object
	void set_access_control(access_control *ac)
	{
		m_access_control.reset(ac);
	}

	/// \brief Fill in the OPTIONS for a request \a req into reply \a rep
	virtual void get_options_for_request(const request &req, reply &rep);

	/// \brief Set the CORS headers for a request \a req into reply \a rep
	virtual void set_access_control_headers(const request &req, reply &rep);

	/// \brief Set the context_name
	///
	/// The context name is used in constructing relative URL's that start with a forward slash
	void set_context_name(std::string context_name) { m_context_name = std::move(context_name); }

	/// \brief Get the context_name
	///
	/// The context name is used in constructing relative URL's that start with a forward slash
	[[nodiscard]] std::string get_context_name() const { return m_context_name; }

	/// \brief Add controller to the list of controllers
	///
	/// When a request is received, the list of controllers get a chance
	/// of handling it, in the order of which they were added to this server.
	/// If none of the controller handle the request the not_found error is returned.
	void add_controller(controller *c);

	/// \brief Add an error handler
	///
	/// Errors are handled by the error handler list. The last added handler
	/// is called first.
	void add_error_handler(error_handler *eh);

	/// \brief Set the template processor
	///
	/// A template processor handles loading templates and processing
	/// the contents.
	void set_template_processor(basic_template_processor *template_processor);

	/// \brief Get the template processor
	///
	/// A template processor handles loading templates and processing
	/// the contents. This will throw if the processor has not been set
	/// yet.
	[[nodiscard]] basic_template_processor &get_template_processor()
	{
		if (not m_template_processor)
			throw std::logic_error("Template processor not specified yet");
		return *m_template_processor;
	}

	/// \brief Get the template processor
	///
	/// A template processor handles loading templates and processing
	/// the contents. This will throw if the processor has not been set
	/// yet.
	[[nodiscard]] const basic_template_processor &get_template_processor() const
	{
		if (not m_template_processor)
			throw std::logic_error("Template processor not specified yet");
		return *m_template_processor;
	}

	/// \brief returns whether template processor has been set
	[[nodiscard]] bool has_template_processor() const
	{
		return m_template_processor != nullptr;
	}

	/// \brief Bind the server to address \a address and port \a port
	virtual void bind(std::string_view address, unsigned short port);

	/// \brief Run as many as \a nr_of_threads threads simultaneously
	virtual void run(int nr_of_threads);

	/// \brief Stop all threads and stop listening
	virtual void stop();

	/// \brief log_forwarded tells the HTTP server to use the last entry in X-Forwarded-For as client log entry
	void set_log_forwarded(bool v) { m_log_forwarded = v; }

	/// \brief returns the address as specified in bind
	[[nodiscard]] std::string get_address() const { return m_address; }

	/// \brief returns the port as specified in bind
	[[nodiscard]] uint16_t get_port() const { return m_port; }

	/// \brief get_io_context has to be public since we need it to call notify_fork from child code
	[[nodiscard]] virtual asio_ns::io_context &get_io_context() = 0;

	/// \brief get_executor has to be public since we need it to call notify_fork from child code
	[[nodiscard]] asio_ns::io_context::executor_type get_executor() { return get_io_context().get_executor(); }

  protected:
	/// \brief the default entry logger
	virtual void log_request(std::string_view client,
		const request &req, const reply &rep,
		std::chrono::system_clock::time_point start,
		std::string_view referer, std::string_view userAgent,
		std::string_view entry) noexcept;

  private:
	friend class preforked_server_base;
	friend class connection;

	virtual void handle_request(asio_ns::ip::tcp::socket &socket,
		request &req, reply &rep);

	void handle_accept(asio_system_ns::error_code ec);

	std::shared_ptr<asio_ns::ip::tcp::acceptor> m_acceptor;
	std::list<std::thread> m_threads;
	std::shared_ptr<connection> m_new_connection;
	std::string m_address;
	uint16_t m_port = 0;
	bool m_log_forwarded;
	std::string m_context_name; /// \brief This is required for proxied servers e.g.
	std::unique_ptr<security_context> m_security_context;
	std::unique_ptr<basic_template_processor> m_template_processor;
	std::list<controller *> m_controllers;
	std::list<error_handler *> m_error_handlers;
	std::set<std::string> m_allowed_methods;
	std::unique_ptr<access_control> m_access_control;
};

// --------------------------------------------------------------------
/// \brief The most often used server class, contains its own io_context.

class server : public basic_server
{
  public:
	/// \brief Simple server, no security, no template processor
	server() = default;

	/// \brief Simple server, no security, create default template processor with \a docroot
	server(std::string docroot)
		: basic_server(std::move(docroot))
	{
	}

	/// \brief server with a security context for limited access
	server(security_context *s_ctxt)
		: basic_server(s_ctxt)
	{
	}

	/// \brief server with a security context for limited access, create default template processor with \a docroot
	server(security_context *s_ctxt, std::string docroot)
		: basic_server(s_ctxt, std::move(docroot))
	{
	}

	~server() override
	{
		m_io_context.stop();
	}

	asio_ns::io_context &get_io_context() override
	{
		return m_io_context;
	}

	/// \brief Stop the server and also stop the io_context
	void stop() override
	{
		m_io_context.stop();
		basic_server::stop();
	}

  private:
	asio_ns::io_context m_io_context;
};

} // namespace zeep::http
