#pragma once

/**
** \defgroup ssb_db SSB Database
** This is the main interface to SSB persistence.  Everything about getting and
** storing messages and blobs goes through here.
** @{
*/

#include "ssb.h"

#include "quickjs.h"

#include <stdbool.h>

/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;

/**
** Initialize the database writer for an SSB instance.
** @param ssb The SSB instance.
*/
void tf_ssb_db_init(tf_ssb_t* ssb);

/**
** Configure an opened SQLite database for reading.
*/
void tf_ssb_db_init_reader(sqlite3* db);

/**
** Get message content by ID.
** @param ssb The SSB instance.
** @param id The message identifier.
** @param[out] out_blob Populated with the message content.
** @param[out] out_size POpulated with the size of the message content.
** @return true If the message content was found and retrieved.
*/
bool tf_ssb_db_message_content_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size);

/**
** Determine whether a blob is in the database by ID.
** @param db The SQLite database instance to use.
** @param id The blob identifier.
** @return true If the blob is in the database.
*/
bool tf_ssb_db_blob_has(sqlite3* db, const char* id);

/**
** Retrieve a blob from the database.
** @param ssb The SSB instance.
** @param id The blob identifier.
** @param[out] out_blob Populated with the blob data.
** @param[out] out_size The size of the blob data.
** @return true If the blob was found and retrieved.
*/
bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size);

/**
** A function called when a blob is retrieved from the database.
** @param found Whether the blob was found.
** @param data The blob data if found.
** @param size The size of the blob data if found, in bytes.
** @param user_data The user data.
*/
typedef void(tf_ssb_db_blob_get_callback_t)(bool found, const uint8_t* data, size_t size, void* user_data);

/**
** Retrieve a blob from the database asynchronously.
** @param ssb The SSB instance.
** @param id The blob identifier.
** @param callback Callback called with the result.
** @param user_data The user data.
*/
void tf_ssb_db_blob_get_async(tf_ssb_t* ssb, const char* id, tf_ssb_db_blob_get_callback_t* callback, void* user_data);

/**
** A function called when a message is stored in the database.
** @param id The message identifier.
** @param stored True if the message wasn't already in the database.
** @param user_data The user data.
*/
typedef void(tf_ssb_db_store_message_callback_t)(const char* id, bool stored, void* user_data);

/**
** Store a message in the database.
** @param ssb The SSB instance.
** @param context The JS context.
** @param id The message identifier.
** @param val The message object.
** @param signature The signature of the message.
** @param flags tf_ssb_message_flags_t describing the message.
** @param callback A callback to call upon completion.
** @param user_data User data for the callback.
*/
void tf_ssb_db_store_message(
	tf_ssb_t* ssb, JSContext* context, const char* id, JSValue val, const char* signature, int flags, tf_ssb_db_store_message_callback_t* callback, void* user_data);

/**
** A function called when a block is stored in the database.
** @param id The blob identifier.
** @param is_new True if the blob wasn't already in the database.
** @param user_data The user data.
*/
typedef void(tf_ssb_db_blob_store_callback_t)(const char* id, bool is_new, void* user_data);

/**
** Store a blob in the database asynchronously.
** @param ssb The SSB instance.
** @param blob The blob data.
** @param size The size of the blob data.
** @param callback A callback to call upon completion.
** @param user_data User data for the callback.
*/
void tf_ssb_db_blob_store_async(tf_ssb_t* ssb, const uint8_t* blob, size_t size, tf_ssb_db_blob_store_callback_t* callback, void* user_data);

/**
** Store a blob in the database and wait for the operation to complete.
** @param ssb The SSB instance.
** @param blob The blob data.
** @param size The size of the blob.
** @param[out] out_id Populated with the blob identifier.
** @param out_id_size The size of the out_id buffer.
** @param[out] out_new True if the blob wasn't already in the datbase.
*/
bool tf_ssb_db_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char* out_id, size_t out_id_size, bool* out_new);

/**
** Get a message by its identifier.
** @param ssb The SSB instance.
** @param id The message identifier.
** @param is_keys Whether to produce {"key": id, "value": message, "timestamp": ts} or just the message.
** @return The message.
*/
JSValue tf_ssb_db_get_message_by_id(tf_ssb_t* ssb, const char* id, bool is_keys);

/**
** Get a message by its author and sequence number.
** @param ssb The SSB instance.
** @param author The author's identity.
** @param sequence The message sequence number.
** @param[out] out_message_id Populated with the message identifier.
** @param out_message_id_size The size of the out_message_id buffer.
** @param[out] out_previous Populated with the previous message identifier.
** @param out_previous_size The size of the out_previous buffer.
** @param[out] out_timestamp Populated with the timestamp.
** @param[out] out_content Populated with the message content.  Free with tf_free().
** @param[out] out_hash Populated with the message hash format.
** @param out_hash_size The size of the out_hash buffer.
** @param[out] out_signature Populated with the message signature.
** @param out_signature_size The size of the out_signature buffer.
** @param[out] out_flags Populated with flags describing the format of the message.
** @return True if the message was found and retrieved.
*/
bool tf_ssb_db_get_message_by_author_and_sequence(tf_ssb_t* ssb, const char* author, int32_t sequence, char* out_message_id, size_t out_message_id_size, char* out_previous,
	size_t out_previous_size, double* out_timestamp, char** out_content, char* out_hash, size_t out_hash_size, char* out_signature, size_t out_signature_size, int* out_flags);

/**
** Get information about the last message from an author.
** @param ssb The SSB instance.
** @param author The author's identity.
** @param[out] out_sequence Populated with the message sequence number.
** @param[out] out_message_id Populated with the message identifier.
** @param out_message_id_size The size of the out_message_id buffer.
** @return True if the message was found and information was retrieved.
*/
bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, int32_t* out_sequence, char* out_message_id, size_t out_message_id_size);

/**
** Call a function for each result row of an SQL query.
** @param ssb The SSB instance.
** @param query The SQL query.
** @param binds An array of values to bind to SQL parameters.
** @param callback A callback to call for each result row.
** @param user_data User data to pass to the callback.
** @return A promise resolved when the query completes or rejected if it fails.
*/
JSValue tf_ssb_db_visit_query(tf_ssb_t* ssb, const char* query, const JSValue binds, void (*callback)(JSValue row, void* user_data), void* user_data);

/**
** Sanity check the feed for the given author.
** @param db The SQLite database instance to use.
** @param author The identity of the author to check.
** @return True if the author's feed is fully valid.
*/
bool tf_ssb_db_check(sqlite3* db, const char* author);

/**
** Get the number of SSB identities a Tilde Friends user has.
** @param ssb The SSB instance.
** @param user The user's username.
** @return The number of identities found.
*/
int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user);

/**
** Create a new identity for a user.
** @param ssb The SSB instance.
** @param user The user's username.
** @param[out] out_public_key A buffer populated with the new public key.
** @param[out] out_private_key A buffer populated with the new private key.
** @return True if the identity was created.
*/
bool tf_ssb_db_identity_create(tf_ssb_t* ssb, const char* user, uint8_t* out_public_key, uint8_t* out_private_key);

/**
** Delete an identity for a user from the database.  This is an unrecoverable operation.
** @param ssb The SSB instance.
** @param user The user's username.
** @param public_key The identity to delete.
** @return True if the identity was deleted.
*/
bool tf_ssb_db_identity_delete(tf_ssb_t* ssb, const char* user, const char* public_key);

/**
** Add an identity for a user to the database.
** @param ssb The SSB instance.
** @param user The user's username.
** @param public_key The public key of the identity.
** @param private_key The private key of the identity.
*/
bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key);

/**
** Get the active identity for a user for a given package.
** @param db An sqlite3 database.
** @param user The username.
** @param package_owner The username of the package owner.
** @param package_name The name of the package.
** @param[out] out_identity Populated with the identity.
** @param out_identity_size The size of the out_identity buffer.
** @return true If the identity was retrieved.
*/
bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* package_owner, const char* package_name, char* out_identity, size_t out_identity_size);

/**
** Call a function for each identity owned by a user.
** @param ssb The SSB instance.
** @param user The user's username.
** @param callback The function to call for each identity.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(const char* identity, void* user_data), void* user_data);

/**
** Call a function for all identities in the database.
** @param ssb The SSB instance.
** @param callback The callback to call for each identity.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data);

/**
** Get the user owning an identity.
** @param ssb The SSB instance.
** @param public_key the identity.
** @return The username of the owner of the identity or NULL.
*/
const char* tf_ssb_db_get_user_for_identity(tf_ssb_t* ssb, const char* public_key);

/**
** Get the private key for an identity in the database.
** @param ssb The SSB instance.
** @param user The owning user's username.
** @param public_key The public key of the identity.
** @param[out] out_private_key A buffer to receive the private key of the identity.
** @param private_key_size The size of the out_private_key buffer.
** @return True if the private key was found and retrieved.
*/
bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size);

/**
** Format a message in the standard format for SSB signing.
** @param context A JS context.
** @param previous The previous message identifier.
** @param author The author's public key.
** @param sequence The message sequence number.
** @param timestamp The message timestamp.
** @param hash The hash type (probably "sha256").
** @param content The message content.
** @param signature The signature of the message.
** @param flags tf_ssb_message_flags_t describing the message.
*/
JSValue tf_ssb_format_message(
	JSContext* context, const char* previous, const char* author, int32_t sequence, double timestamp, const char* hash, const char* content, const char* signature, int flags);

/** Information about a single followed account. */
typedef struct _tf_ssb_following_t
{
	/** The number of known users the account is following. */
	int following_count;
	/** The number of known users the account is blocking. */
	int blocking_count;
	/** The number of known users following the account. */
	int followed_by_count;
	/** The number of known users blocking the account. */
	int blocked_by_count;
	/** Degree of separation between initial accounts and this account. */
	int depth;
	/** The account's identity. */
	char id[k_id_base64_len];
} tf_ssb_following_t;

/**
** Get all the identities visible from a set of identities given known follows and blocks.
** @param ssb The SSB instance.
** @param ids An array of identities.
** @param count The number of identities.
** @param depth The following depth to use (prefer 2).
** @return An array of identities.  Free with tf_free().
*/
const char** tf_ssb_db_following_deep_ids(tf_ssb_t* ssb, const char** ids, int count, int depth);

/**
** Return information about identities visible from a set of identities given known follows and blocks.
** @param ssb The SSB instance.
** @param ids An array of identities.
** @param count The number of identities.
** @param depth The following depth to use (prefer 2).
** @param include_blocks Whether to include blocked identities in results.
** @return An array of information about visible accounts.  Free with tf_free().
*/
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks);

/**
** Get all visible identities from all local accounts.
** @param ssb The SSB instance.
** @param depth The following depth to consider (prefer 2).
** @return The visible identities.  Free with tf_free().
*/
const char** tf_ssb_db_get_all_visible_identities(tf_ssb_t* ssb, int depth);

/**
** Information about a stored SHS connection.
*/
typedef struct _tf_ssb_db_stored_connection_t
{
	/** The connection's address. */
	char address[256];
	/** The network port number of the connection. */
	int port;
	/** The identity. */
	char pubkey[k_id_base64_len];
	/** Time of last attempted connection. */
	int64_t last_attempt;
	/** Time of last successful connection. */
	int64_t last_success;
} tf_ssb_db_stored_connection_t;

/**
** Get the list of stored connections from the SSB connection tracker.
** @param ssb The SSB instance.
** @param[out] out_count Populated with the number of returned connections.
** @return Information about all the stored connections.
*/
tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, int* out_count);

/**
** Remove a stored connection.
** @param ssb The SSB instance.
** @param address The connection address.
** @param port The connection network port number.
** @param pubkey The identity of the connection.
*/
void tf_ssb_db_forget_stored_connection(tf_ssb_t* ssb, const char* address, int port, const char* pubkey);

/**
** Retrieve a user's hashed password from the database.
** @param ssb The SSB instance.
** @param name The username.
** @param[out] out_password Populated with the password.
** @param password_size The size of the out_password buffer.
** @return true if the password hash was successfully retrieved.
*/
bool tf_ssb_db_get_account_password_hash(tf_ssb_t* ssb, const char* name, char* out_password, size_t password_size);

/**
** Insert or update a user's hashed password in the database.
** @param loop The event loop.
** @param db A DB writer.
** @param context A JS context.
** @param name The username.
** @param password The raw password.
** @return true if the hash of the password was successfully stored.
*/
bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password);

/**
** Add a user account to the database.
** @param loop The event loop.
** @param db A DB writer.
** @param context A JS context.
** @param name The username to add.
** @param password The user's raw password.
** @return true If the user was added successfully.
*/
bool tf_ssb_db_register_account(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password);

/**
** Get an entry from the properties table.
** @param ssb The SSB instance.
** @param id The user.
** @param key The property key.
** @return The property value or null.  Free with tf_free().
*/
const char* tf_ssb_db_get_property(tf_ssb_t* ssb, const char* id, const char* key);

/**
** Store an entry in the properties table.
** @param ssb The SSB instance.
** @param id The user.
** @param key The property key.
** @param value The property value.
** @return true if the property was stored successfully.
*/
bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value);

/**
** Remove an entry in the properties table.
** @param ssb The SSB instance.
** @param id The user.
** @param key The property key.
** @return true if the property was removed successfully.
*/
bool tf_ssb_db_remove_property(tf_ssb_t* ssb, const char* id, const char* key);

/**
** Remove a value from an entry in the properties table that is a JSON array.
** @param ssb The SSB instance.
** @param id The user.
** @param key The property key.
** @param value The value to remove from the JSON array.
** @return true if the property was updated.
*/
bool tf_ssb_db_remove_value_from_array_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value);

/**
** Ensure a value is in an entry in the properties table that is a JSON array.
** @param ssb The SSB instance.
** @param id The user.
** @param key The property key.
** @param value The value to add to the JSON array.
** @return true if the property was updated.
*/
bool tf_ssb_db_add_value_to_array_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value);

/**
** Resolve a hostname to its index path by global settings.
** @param ssb The SSB instance.
** @param host The hostname.
** @param callback The callback.
** @param user_data The callback user data.
*/
void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data);

/**
** Verify an author's feed.
** @param ssb The SSB instance.
** @param id The author'd identity.
** @param debug_sequence Message sequence number to debug if non-zero.
** @param fix Fix invalid messages when possible.
** @return true If the feed verified successfully.
*/
bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id, int32_t debug_sequence, bool fix);

/**
** Check if a user has a specific permission.
** @param ssb The SSB instance.
** @param db Optional database instance.  If NULL, one will be acquired from ssb.
** @param id The user ID.
** @param permission The name of the permission.
** @return true If the user has the requested permission.
*/
bool tf_ssb_db_user_has_permission(tf_ssb_t* ssb, sqlite3* db, const char* id, const char* permission);

/**
** Get a boolean global setting value.
** @param db The database.
** @param name The setting name.
** @param out_value Populated with the value.
** @return true if the setting was found.
*/
bool tf_ssb_db_get_global_setting_bool(sqlite3* db, const char* name, bool* out_value);

/**
** Get an int64_t global setting value.
** @param db The database.
** @param name The setting name.
** @param out_value Populated with the value.
** @return true if the setting was found.
*/
bool tf_ssb_db_get_global_setting_int64(sqlite3* db, const char* name, int64_t* out_value);

/**
** Get a string global setting value.
** @param db The database.
** @param name The setting name.
** @param out_value Populated with the value.
** @param size The size of the out_value buffer.
** @return true if the setting was found.
*/
bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* out_value, size_t size);

/**
** Set a global setting from a string representation of its value.
** @param db The database.
** @param name The setting name.
** @param value The settinv value.
** @return true if the setting was set.
*/
bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, char* value);

/**
** Get the latest profile information for the given identity.
** @param db The database.
** @param id The identity.
** @return A JSON representation of the latest profile information set for the account.  Free with tf_free().
*/
const char* tf_ssb_db_get_profile(sqlite3* db, const char* id);

/**
** Get the latest profile name for the given identity.
** @param db The database.
** @param id The identity.
** @return The name.  Free with tf_free().
*/
const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id);

/**
** Generate an invite code and store information for it to be usable.
** @param db The database.
** @param id The identity.
** @param host Hostname to which recipient should connect.
** @param port The port to which the recipient should connect.
** @param use_count Number of times the invite code is allowed to be used, or -1 for indefinitely.
** @param expires_seconds How long the invite lasts.
** @param out_invite Populated with the invite code on success.
** @param size The size of the out_invite buffer.
** @return true If an invite was generated.
*/
bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, int port, int use_count, int expires_seconds, char* out_invite, size_t size);

/**
** Consume and validate an invite.
** @param db The database.
** @param id The invite public key.
** @return true If the invite was valid and successfully consumed.
*/
bool tf_ssb_db_use_invite(sqlite3* db, const char* id);

/**
** Determine if an account is familiar, meaning it is local or within the given
** follow depth of the local accounts or we have already replicated data for
** it.
** @param db The database.
** @param id The identity.
** @param depth The follow depth.
** @return true if the account is familiar.
*/
bool tf_ssb_db_is_account_familiar(sqlite3* db, const char* id, int depth);

/**
** An SQLite authorizer callback.  See https://www.sqlite.org/c3ref/set_authorizer.html for use.
** @param user_data User data registered with the authorizer.
** @param action_code The type of action.
** @param arg0 Depends on the action.
** @param arg1 Depends on the action.
** @param arg2 Depends on the action.
** @param arg3 Depends on the action.
** @return A value indicating whether the operation is allowed.
*/
int tf_ssb_sqlite_authorizer(void* user_data, int action_code, const char* arg0, const char* arg1, const char* arg2, const char* arg3);

/**
** Check if we have an invite for the given account.
** @param db The database.
** @param id The invite public key to check.
** @return true If we have a valid invite for the address.
*/
bool tf_ssb_db_has_invite(sqlite3* db, const char* id);

/**
** Identity information
*/
typedef struct _tf_ssb_identity_info_t
{
	/** An array of identities. */
	const char** identity;
	/** A array of identities, one for each identity. */
	const char** name;
	/** The number of elements in both the identity and name arrays. */
	int count;
	/** The active identity. */
	char active_identity[k_id_base64_len];
} tf_ssb_identity_info_t;

/**
** Get available identities, names, and the active identity for a user.
** @param ssb The SSB instance.
** @param user The user name.
** @param package_owner The owner of the package for which identity info is being fetched.
** @param package_name The name of the package for which identity info is being fetched.
** @return A struct of identities, names, and the active identity.  Free with tf_free().
*/
tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* user, const char* package_owner, const char* package_name);

/**
** Add or update a blob wants cache entry.
** @param db The database.
** @param id The wanted blob ID.
*/
void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id);

/** @} */
