/*
 * Copyright 2024 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef RSPAMD_RSPAMD_CONTROL_H
#define RSPAMD_RSPAMD_CONTROL_H

#include "config.h"
#include "mem_pool.h"
#include "contrib/libev/ev.h"

G_BEGIN_DECLS

struct rspamd_main;
struct rspamd_worker;

enum rspamd_control_type {
	RSPAMD_CONTROL_STAT = 0,
	RSPAMD_CONTROL_RELOAD,
	RSPAMD_CONTROL_RERESOLVE,
	RSPAMD_CONTROL_RECOMPILE,
	RSPAMD_CONTROL_HYPERSCAN_LOADED,
	RSPAMD_CONTROL_LOG_PIPE,
	RSPAMD_CONTROL_FUZZY_STAT,
	RSPAMD_CONTROL_FUZZY_SYNC,
	RSPAMD_CONTROL_MONITORED_CHANGE,
	RSPAMD_CONTROL_CHILD_CHANGE,
	RSPAMD_CONTROL_FUZZY_BLOCKED,
	RSPAMD_CONTROL_WORKERS_SPAWNED,
	RSPAMD_CONTROL_COMPOSITES_STATS,
	RSPAMD_CONTROL_MULTIPATTERN_LOADED,
	RSPAMD_CONTROL_REGEXP_MAP_LOADED,
	RSPAMD_CONTROL_MEMORY_STAT,
	RSPAMD_CONTROL_FUZZY_HASH,
	RSPAMD_CONTROL_MAX
};

enum rspamd_srv_type {
	RSPAMD_SRV_SOCKETPAIR = 0,
	RSPAMD_SRV_HYPERSCAN_LOADED,
	RSPAMD_SRV_MONITORED_CHANGE,
	RSPAMD_SRV_LOG_PIPE,
	RSPAMD_SRV_ON_FORK,
	RSPAMD_SRV_HEARTBEAT,
	RSPAMD_SRV_HEALTH,
	RSPAMD_SRV_NOTICE_HYPERSCAN_CACHE,
	RSPAMD_SRV_FUZZY_BLOCKED,       /* Used to notify main process about a blocked ip */
	RSPAMD_SRV_WORKERS_SPAWNED,     /* Used to notify workers that all workers have been spawned */
	RSPAMD_SRV_MULTIPATTERN_LOADED, /* Multipattern HS compiled and ready */
	RSPAMD_SRV_REGEXP_MAP_LOADED,   /* Regexp map HS compiled and ready */
	RSPAMD_SRV_BUSY,                /* Worker is busy with long-running operation */
	RSPAMD_SRV_RECOMPILE_REQUEST,   /* Worker requests hs_helper to recompile (stale cache) */
};

enum rspamd_log_pipe_type {
	RSPAMD_LOG_PIPE_SYMBOLS = 0,
};

struct rspamd_control_command {
	enum rspamd_control_type type;
	uint64_t id;
	union {
		struct {
			unsigned int unused;
		} stat;
		struct {
			unsigned int unused;
		} reload;
		struct {
			unsigned int unused;
		} reresolve;
		struct {
			unsigned int unused;
		} recompile;
		struct {
			gboolean forced;
			char scope[64]; /* Scope name, NULL means all scopes */
		} hs_loaded;
		struct {
			char name[64];
		} mp_loaded;
		struct {
			/*
			 * Digest identifies the database in the cache, whilst the name is
			 * merely for logging: a map name is an URL and has no sane bound
			 */
			unsigned char digest[64]; /* rspamd_cryptobox_HASHBYTES */
			char name[128];
		} re_map_loaded;
		struct {
			char tag[32];
			gboolean alive;
			pid_t sender;
		} monitored_change;
		struct {
			enum rspamd_log_pipe_type type;
		} log_pipe;
		struct {
			unsigned int unused;
		} fuzzy_stat;
		struct {
			unsigned int unused;
		} fuzzy_sync;
		struct {
			unsigned char digest[64];
		} fuzzy_hash;
		struct {
			enum {
				rspamd_child_offline,
				rspamd_child_online,
				rspamd_child_terminated,
			} what;
			pid_t pid;
			unsigned int additional;
		} child_change;
		struct {
			union {
				struct sockaddr sa;
				struct sockaddr_in s4;
				struct sockaddr_in6 s6;
			} addr;
			sa_family_t af;
		} fuzzy_blocked;
		struct {
			unsigned int workers_count;
		} workers_spawned;
		struct {
			unsigned int unused;
		} composites_stats;
		struct {
			unsigned int unused;
		} memory_stat;
	} cmd;
};

struct rspamd_control_reply {
	enum rspamd_control_type type;
	uint64_t id;
	union {
		struct {
			unsigned int conns;
			double uptime;
			double utime;
			double systime;
			gulong maxrss;
		} stat;
		struct {
			unsigned int status;
		} reload;
		struct {
			unsigned int status;
		} reresolve;
		struct {
			unsigned int status;
		} recompile;
		struct {
			unsigned int status;
		} hs_loaded;
		struct {
			unsigned int status;
		} monitored_change;
		struct {
			unsigned int status;
		} log_pipe;
		struct {
			unsigned int status;
			char storage_id[MEMPOOL_UID_LEN];
		} fuzzy_stat;
		struct {
			unsigned int status;
			char storage_id[MEMPOOL_UID_LEN];
		} fuzzy_hash;
		struct {
			unsigned int status;
		} fuzzy_sync;
		struct {
			unsigned int status;
		} fuzzy_blocked;
		struct {
			unsigned int status;
		} workers_spawned;
		struct {
			uint64_t checked_slow;   /**< composites checked via slow path */
			uint64_t checked_fast;   /**< composites checked via inverted index */
			uint64_t matched;        /**< composites that matched */
			double time_slow_mean;   /**< EMA mean time in slow path (ms) */
			double time_slow_stddev; /**< EMA stddev time in slow path (ms) */
			double time_fast_mean;   /**< EMA mean time in fast path (ms) */
			double time_fast_stddev; /**< EMA stddev time in fast path (ms) */
		} composites_stats;
		struct {
			unsigned int status;
			uint64_t rss_kb;             /**< process resident set, KiB           */
			uint64_t lua_kb;             /**< Lua heap usage, KiB                 */
			uint64_t mempool_bytes;      /**< rspamd_mempool aggregate bytes      */
			uint64_t jemalloc_allocated; /**< jemalloc stats.allocated; 0 if N/A  */
		} memory_stat;
	} reply;
};

#define PAIR_ID_LEN 16

struct rspamd_srv_command {
	enum rspamd_srv_type type;
	uint64_t id;
	union {
		struct {
			int af;
			char pair_id[PAIR_ID_LEN];
			unsigned int pair_num;
		} spair;
		struct {
			gboolean forced;
			char scope[64]; /* Scope name, NULL means all scopes */
		} hs_loaded;
		struct {
			char tag[32];
			gboolean alive;
			pid_t sender;
		} monitored_change;
		struct {
			enum rspamd_log_pipe_type type;
		} log_pipe;
		struct {
			pid_t ppid;
			pid_t cpid;
			enum {
				child_create = 0,
				child_dead,
			} state;
		} on_fork;
		struct {
			unsigned int status;
			/* TODO: add more fields */
		} heartbeat;
		struct {
			unsigned int status;
		} health;
		/* Used when a worker loads a valid hyperscan file */
		struct {
			char filename[80]; /* Just the filename: 64 hex hash + .hs.unser + null */
		} hyperscan_cache_file;
		/* Send when one worker has blocked some IP address */
		struct {
			union {
				struct sockaddr sa;
				struct sockaddr_in s4;
				struct sockaddr_in6 s6;
			} addr;
			sa_family_t af;
		} fuzzy_blocked;
		/* Sent when all workers have been spawned */
		struct {
			unsigned int workers_count;
		} workers_spawned;
		/* Sent when a multipattern hyperscan db is compiled */
		struct {
			char name[64];
		} mp_loaded;
		/* Sent when a regexp map hyperscan db is compiled */
		struct {
			/*
			 * Digest identifies the database in the cache, whilst the name is
			 * merely for logging: a map name is an URL and has no sane bound
			 */
			unsigned char digest[64]; /* rspamd_cryptobox_HASHBYTES */
			char name[128];
		} re_map_loaded;
		struct {
			gboolean is_busy;
			char reason[32];
		} busy;
		struct {
			unsigned int unused;
		} recompile_request;
	} cmd;
};

struct rspamd_srv_reply {
	enum rspamd_srv_type type;
	uint64_t id;
	union {
		struct {
			int code;
		} spair;
		struct {
			int forced;
		} hs_loaded;
		struct {
			int status;
		};
		struct {
			enum rspamd_log_pipe_type type;
		} log_pipe;
		struct {
			int status;
		} on_fork;
		struct {
			int status;
		} heartbeat;
		struct {
			unsigned int status;
			unsigned int workers_count;
			unsigned int scanners_count;
			unsigned int workers_hb_lost;
		} health;
		struct {
			int unused;
		} hyperscan_cache_file;
		struct {
			int unused;
		} fuzzy_blocked;
		struct {
			int status;
		} workers_spawned;
		struct {
			int status;
		} recompile_request;
	} reply;
};

typedef gboolean (*rspamd_worker_control_handler)(struct rspamd_main *rspamd_main,
												  struct rspamd_worker *worker,
												  int fd,
												  int attached_fd,
												  struct rspamd_control_command *cmd,
												  gpointer ud);

typedef void (*rspamd_srv_reply_handler)(struct rspamd_worker *worker,
										 struct rspamd_srv_reply *rep, int rep_fd,
										 gpointer ud);

/**
 * Process client socket connection
 */
void rspamd_control_process_client_socket(struct rspamd_main *rspamd_main,
										  int fd, rspamd_inet_addr_t *addr);

/**
 * Register default handlers for a worker
 */
void rspamd_control_worker_add_default_cmd_handlers(struct rspamd_worker *worker,
													struct ev_loop *ev_base);

/**
 * Register custom handler for a specific control command for this worker
 */
void rspamd_control_worker_add_cmd_handler(struct rspamd_worker *worker,
										   enum rspamd_control_type type,
										   rspamd_worker_control_handler handler,
										   gpointer ud);

/**
 * Start watching on srv pipe
 */
void rspamd_srv_start_watching(struct rspamd_main *srv,
							   struct rspamd_worker *worker,
							   struct ev_loop *ev_base);


/**
 * Send command to srv pipe and read reply calling the specified callback at the
 * end
 */
void rspamd_srv_send_command(struct rspamd_worker *worker,
							 struct ev_loop *ev_base,
							 struct rspamd_srv_command *cmd,
							 int attached_fd,
							 rspamd_srv_reply_handler handler,
							 gpointer ud);

/**
 * Broadcast srv cmd from rspamd_main to workers
 * @param rspamd_main
 * @param cmd
 * @param except_pid
 */
void rspamd_control_broadcast_srv_cmd(struct rspamd_main *rspamd_main,
									  struct rspamd_control_command *cmd,
									  pid_t except_pid);

/**
 * Returns command from a specified string (case insensitive)
 * @param str
 * @return
 */
enum rspamd_control_type rspamd_control_command_from_string(const char *str);

/**
 * Returns command name from it's type
 * @param cmd
 * @return
 */
const char *rspamd_control_command_to_string(enum rspamd_control_type cmd);

const char *rspamd_srv_command_to_string(enum rspamd_srv_type cmd);

/**
 * Create a new control events pending hash table
 * @return opaque pointer to khash
 */
void *rspamd_control_pending_new(void);

/**
 * Destroy control events pending hash table
 * @param p opaque pointer to khash
 */
void rspamd_control_pending_destroy(void *p);

/**
 * Remove all pending control events
 * @param p opaque pointer to khash
 */
void rspamd_control_pending_remove_all(void *p);

void rspamd_worker_set_busy(struct rspamd_worker *worker,
							struct ev_loop *event_loop,
							const char *reason);

/**
 * Initialize srv_pipe handling for a worker
 * Called automatically by rspamd_srv_send_command if not initialized
 */
void rspamd_srv_pipe_init(struct rspamd_worker *worker, struct ev_loop *ev_base);

/**
 * Cleanup srv_pipe handling for a worker
 * Should be called during worker shutdown
 */
void rspamd_srv_pipe_cleanup(void);

G_END_DECLS

#endif
