/*
 * Copyright 2025 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_HS_CACHE_BACKEND_H
#define RSPAMD_HS_CACHE_BACKEND_H

#include "config.h"

#ifdef __cplusplus
extern "C" {
#endif

struct rspamd_config;
struct ev_loop;

/**
 * Callback for async cache operations
 * @param err error message or NULL on success
 * @param data loaded data (for load operations) or NULL
 * @param len data length
 * @param ud userdata
 */
typedef void (*rspamd_hs_cache_cb_t)(const char *err,
									 const unsigned char *data,
									 gsize len,
									 void *ud);

/**
 * Cache backend operations structure
 */
struct rspamd_hs_cache_backend {
	/**
	 * Save data to cache
	 * @param cache_key unique key for this cache entry
	 * @param platform_id platform identifier
	 * @param data serialized hyperscan database
	 * @param len data length
	 * @param callback completion callback
	 * @param ud userdata for callback
	 */
	void (*save)(const char *cache_key,
				 const char *platform_id,
				 const unsigned char *data,
				 gsize len,
				 rspamd_hs_cache_cb_t callback,
				 void *ud);

	/**
	 * Load data from cache
	 * @param cache_key unique key for this cache entry
	 * @param platform_id platform identifier
	 * @param callback completion callback with data
	 * @param ud userdata for callback
	 */
	void (*load)(const char *cache_key,
				 const char *platform_id,
				 rspamd_hs_cache_cb_t callback,
				 void *ud);

	/**
	 * Check if cache entry exists
	 * @param cache_key unique key
	 * @param platform_id platform identifier
	 * @param callback completion callback (data will be NULL, check err)
	 * @param ud userdata
	 */
	void (*exists)(const char *cache_key,
				   const char *platform_id,
				   rspamd_hs_cache_cb_t callback,
				   void *ud);

	/* Opaque backend context */
	void *ctx;
};

/**
 * Set the global hyperscan cache backend.
 * Called by hs_helper after initializing the Lua backend.
 * @param backend backend operations structure (takes ownership)
 */
void rspamd_hs_cache_set_backend(struct rspamd_hs_cache_backend *backend);

/**
 * Get the current hyperscan cache backend.
 * @return backend or NULL if using default file backend
 */
struct rspamd_hs_cache_backend *rspamd_hs_cache_get_backend(void);

/**
 * Check if a custom (non-file) backend is configured.
 * @return TRUE if custom backend is set
 */
gboolean rspamd_hs_cache_has_custom_backend(void);

/**
 * Free the cache backend
 */
void rspamd_hs_cache_free_backend(void);

typedef struct lua_State lua_State;
/**
 * Set the Lua backend state (called by hs_helper)
 * @param L Lua state
 * @param ref registry reference to the backend object
 * @param platform_id hyperscan platform identifier
 */
void rspamd_hs_cache_set_lua_backend(lua_State *L, int ref, const char *platform_id);

/**
 * Check if Lua backend is available
 * @return TRUE if Lua backend is set
 */
gboolean rspamd_hs_cache_has_lua_backend(void);

/**
 * Get the name of the configured cache backend: `file`, `redis` or `http`
 * @return backend name (never NULL, defaults to `file`)
 */
const char *rspamd_hs_cache_backend_name(void);

/**
 * Check if the configured backend stores hyperscan databases as local files,
 * i.e. if reading them directly from `hs_cache_dir` makes any sense
 * @return TRUE for the file backend
 */
gboolean rspamd_hs_cache_backend_is_file(void);

/**
 * Check if a load error merely means that nothing is cached yet, which is a
 * normal condition: the database might not have been compiled so far, or a read
 * replica might not have caught up with the master. Backends indicate this by
 * returning `not found` (or `file not found` for the file one).
 * @param error error message from a load callback (NULL means no data at all)
 * @return TRUE if this is a cache miss rather than a backend failure
 */
gboolean rspamd_hs_cache_error_is_miss(const char *error);

/**
 * Initialize Lua HS cache backend in the current process using hs_helper worker
 * configuration (if configured and Lua is available).
 *
 * This is meant to be called from worker initialization after ev_base is ready.
 */
gboolean rspamd_hs_cache_try_init_lua_backend(struct rspamd_config *cfg,
											  struct ev_loop *ev_base);

/**
 * Async callback type
 * @param success TRUE if operation succeeded
 * @param data loaded data (for load) or NULL
 * @param len data length
 * @param error error message or NULL
 * @param ud userdata
 */
typedef void (*rspamd_hs_cache_async_cb)(gboolean success,
										 const unsigned char *data,
										 gsize len,
										 const char *error,
										 void *ud);

/**
 * Save data to cache via Lua backend (asynchronous)
 * @param cache_key unique cache key (hash)
 * @param entity_name human-readable name of the entity (e.g., multimap name, re class)
 * @param data serialized hyperscan data
 * @param len data length
 * @param cb completion callback
 * @param ud userdata
 */
void rspamd_hs_cache_lua_save_async(const char *cache_key,
									const char *entity_name,
									const unsigned char *data,
									gsize len,
									rspamd_hs_cache_async_cb cb,
									void *ud);

/**
 * Load data from cache via Lua backend (asynchronous)
 * @param cache_key unique cache key (hash)
 * @param entity_name human-readable name of the entity (e.g., multimap name, re class)
 * @param cb completion callback
 * @param ud userdata
 */
void rspamd_hs_cache_lua_load_async(const char *cache_key,
									const char *entity_name,
									rspamd_hs_cache_async_cb cb,
									void *ud);

/**
 * Check if cache entry exists via Lua backend (asynchronous)
 * @param cache_key unique cache key (hash)
 * @param entity_name human-readable name of the entity (e.g., multimap name, re class)
 * @param cb completion callback (len will be 1 if exists, 0 otherwise)
 * @param ud userdata
 */
void rspamd_hs_cache_lua_exists_async(const char *cache_key,
									  const char *entity_name,
									  rspamd_hs_cache_async_cb cb,
									  void *ud);

/**
 * Delete cache entry via Lua backend (asynchronous)
 * @param cache_key unique cache key (hash)
 * @param entity_name human-readable name of the entity (e.g., multimap name, re class)
 * @param cb completion callback (may be NULL for fire-and-forget)
 * @param ud userdata
 */
void rspamd_hs_cache_lua_delete_async(const char *cache_key,
									  const char *entity_name,
									  rspamd_hs_cache_async_cb cb,
									  void *ud);

/**
 * Load data from cache via Lua backend (synchronous)
 * Only works for backends that support synchronous operations (e.g., file backend).
 * For async-only backends (redis, http), returns FALSE immediately.
 *
 * @param cache_key unique cache key (hash)
 * @param entity_name human-readable name of the entity
 * @param[out] data pointer to receive loaded data (caller must g_free)
 * @param[out] len pointer to receive data length
 * @param[out] error pointer to receive error message (caller must g_free)
 * @return TRUE on success, FALSE on error or if sync not supported
 */
gboolean rspamd_hs_cache_lua_load_sync(const char *cache_key,
									   const char *entity_name,
									   unsigned char **data,
									   gsize *len,
									   char **error);

/**
 * Check if cache entry exists via Lua backend (synchronous)
 * Only works for backends that support synchronous operations (e.g., file backend).
 * For async-only backends (redis, http), returns FALSE immediately.
 *
 * @param cache_key unique cache key (hash)
 * @param entity_name human-readable name of the entity
 * @param[out] exists pointer to receive result
 * @return TRUE if check succeeded (check *exists for result), FALSE if sync not supported
 */
gboolean rspamd_hs_cache_lua_exists_sync(const char *cache_key,
										 const char *entity_name,
										 gboolean *exists);

#ifdef __cplusplus
}
#endif

#endif /* RSPAMD_HS_CACHE_BACKEND_H */
