/*
 * SPDX-License-Identifier: ISC
 * SPDX-URL: https://spdx.org/licenses/ISC.html
 *
 * Copyright (C) 2005-2007 Robin Burchell, et al.
 * Copyright (C) 2010 William Pitcock <nenolod@dereferenced.org>
 *
 * This file contains code for the NickServ LIST function.
 * Based on Alex Lambert's LISTEMAIL.
 */

#include <atheme.h>
#include "list_common.h"

// Imported by many other modules
extern void list_register(const char *, struct list_param *);
extern void list_unregister(const char *);

static mowgli_patricia_t *list_params;

static bool
email_match(const struct mynick *mn, const void *arg)
{
	struct myuser *mu = mn->owner;
	const char *cmpr = (const char*)arg;

	return !match(cmpr, mu->email);
}

static bool
lastlogin_match(const struct mynick *mn, const void *arg)
{
	struct myuser *mu = mn->owner;
	const time_t lastlogin = *((const time_t *) arg);

	return (CURRTIME - mu->lastlogin) > lastlogin;
}

static bool
pattern_match(const struct mynick *mn, const void *arg)
{
	const char *pattern = (const char*)arg;

	char pat[512], *nickpattern = NULL, *hostpattern = NULL, *p;
	struct metadata *md;

	bool hostmatch;

	struct myuser *mu = mn->owner;

	if (pattern != NULL)
	{
		mowgli_strlcpy(pat, pattern, sizeof pat);
		p = strrchr(pat, ' ');
		if (p == NULL)
			p = strrchr(pat, '!');
		if (p != NULL)
		{
			*p++ = '\0';
			nickpattern = pat;
			hostpattern = p;
		}
		else if (strchr(pat, '@'))
			hostpattern = pat;
		else
			nickpattern = pat;
		if (nickpattern && !strcmp(nickpattern, "*"))
			nickpattern = NULL;
	}

	if (nickpattern && match(nickpattern, mn->nick))
		return false;

	if (hostpattern)
	{
		hostmatch = false;
		md = metadata_find(mu, "private:host:actual");
		if (md != NULL && !match(hostpattern, md->value))
			hostmatch = true;
		md = metadata_find(mu, "private:host:vhost");
		if (md != NULL && !match(hostpattern, md->value))
			hostmatch = true;
		if (!hostmatch)
			return false;
	}

	return true;
}

static bool
registered_match(const struct mynick *mn, const void *arg)
{
	struct myuser *mu = mn->owner;
	const time_t age = *((const time_t *) arg);

	return (CURRTIME - mu->registered) > age;
}

static bool
primary_match(const struct mynick *mn, const void *arg)
{
	struct myuser *mu = mn->owner;
	(void) arg;

	return !irccasecmp(mn->nick, entity(mn->owner)->name);
}

static bool
has_waitauth(const struct mynick *mn, const void *arg)
{
	struct myuser *mu = mn->owner;

	return ( mu->flags & MU_WAITAUTH ) == MU_WAITAUTH;
}

void
list_register(const char *param_name, struct list_param *param)
{
	mowgli_patricia_add(list_params, param_name, param);
}

void
list_unregister(const char *param_name)
{
	mowgli_patricia_delete(list_params, param_name);
}

static time_t
parse_age(char *s)
{
	time_t duration;

	duration = (atol(s) * SECONDS_PER_MINUTE);
	while (isdigit((unsigned char)*s))
		s++;

	if (*s == 'h' || *s == 'H')
		duration *= MINUTES_PER_HOUR;
	else if (*s == 'd' || *s == 'D')
		duration *= MINUTES_PER_DAY;
	else if (*s == 'w' || *s == 'W')
		duration *= MINUTES_PER_WEEK;
	else if (*s == '\0')
		;
	else
		duration = 0;

	return duration;
}

static void
build_criteriastr(char *buf, int parc, char *parv[])
{
	int i;

	return_if_fail(buf != NULL);

	*buf = 0;
	for (i = 0; i < parc; i++)
	{
		mowgli_strlcat(buf, parv[i], BUFSIZE);
		mowgli_strlcat(buf, " ", BUFSIZE);
	}
}

static void
list_one(struct sourceinfo *si, struct myuser *mu, struct mynick *mn)
{
	char buf[BUFSIZE];

	if (mn != NULL)
		mu = mn->owner;

	*buf = '\0';
	if (metadata_find(mu, "private:freeze:freezer")) {
		if (*buf)
			mowgli_strlcat(buf, " ", BUFSIZE);

		mowgli_strlcat(buf, "\2[frozen]\2", BUFSIZE);
	}
	if (metadata_find(mu, "private:restrict:setter")) {
		if (*buf)
			mowgli_strlcat(buf, " ", BUFSIZE);

		mowgli_strlcat(buf, "\2[restricted]\2", BUFSIZE);
	}
	if (mu->flags & MU_HOLD) {
		if (*buf)
			mowgli_strlcat(buf, " ", BUFSIZE);

		mowgli_strlcat(buf, "\2[held]\2", BUFSIZE);
	}
	if (mu->flags & MU_WAITAUTH) {
		if (*buf)
			mowgli_strlcat(buf, " ", BUFSIZE);

		mowgli_strlcat(buf, "\2[unverified]\2", BUFSIZE);
	}

	if (mn == NULL || !irccasecmp(mn->nick, entity(mu)->name))
		command_success_nodata(si, "- %s (%s) %s", entity(mu)->name, mu->email, buf);
	else
		command_success_nodata(si, "- %s (%s) (%s) %s", mn->nick, mu->email, entity(mu)->name, buf);
}

static void
ns_cmd_list(struct sourceinfo *si, int parc, char *parv[])
{
	char criteriastr[BUFSIZE];

	mowgli_patricia_iteration_state_t state;
	struct myentity_iteration_state mestate;
	struct mynick *mn;

	unsigned int matches = 0;

	int i;
	bool error = false;
	bool found;

	MOWGLI_PATRICIA_FOREACH(mn, &state, nicklist)
	{
		found = true;

		for (i = 0; i < parc; i++)
		{
			struct list_param *param = mowgli_patricia_retrieve(list_params, parv[i]);

			if (param == NULL) {
				command_fail(si, fault_badparams, _("\2%s\2 is not a recognized LIST criterion"), parv[i]);
				error = true;
				break;
			}

			if (param->opttype == OPT_BOOL) {
				bool arg = true;

				if (!param->is_match(mn, &arg)) {
					found = false;
					break;
				}
			} else if (param->opttype == OPT_INT) {
				if (i + 1 < parc) {
					int arg = atoi(parv[++i]);

					if (!param->is_match (mn, &arg)) {
						found = false;
						break;
					}
				} else {
					command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, parv[i]);
					error = true;
					break;
				}
			} else if (param->opttype == OPT_STRING) {
				if (i + 1 < parc) {
					if (!param->is_match (mn, parv[++i])) {
						found = false;
						break;
					}
				} else {
					command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, parv[i]);
					error = true;
					break;
				}
			}
			else if (param->opttype == OPT_AGE) {
				if (i + 1 < parc)
				{
					time_t age = parse_age(parv[++i]);

					if (!param->is_match (mn, &age)) {
						found = false;
						break;
					}
				} else {
					command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, parv[i]);
					error = true;
					break;
				}
			}
		} //for (i = 0; i < parc; i++)

		if (error) {
			break;
		}

		if (found) {
			list_one(si, NULL, mn);
			matches++;
		}
	} //MOWGLI_PATRICIA_FOREACH(mn, &state, nicklist)

	build_criteriastr(criteriastr, parc, parv);

	logcommand(si, CMDLOG_ADMIN, "LIST: \2%s\2 (\2%u\2 matches)", criteriastr, matches);
	if (matches == 0)
		command_success_nodata(si, _("No nicknames matched criteria \2%s\2"), criteriastr);
	else
		command_success_nodata(si, ngettext(N_("\2%u\2 match for criteria \2%s\2."),
		                                    N_("\2%u\2 matches for criteria \2%s\2."), matches),
		                                    matches, criteriastr);
}

static struct command ns_list = {
	.name           = "LIST",
	.desc           = N_("Lists nicknames registered matching a given pattern."),
	.access         = PRIV_USER_AUSPEX,
	.maxparc        = 10,
	.cmd            = &ns_cmd_list,
	.help           = { .path = "nickserv/list" },
};

static void
mod_init(struct module *const restrict m)
{
	MODULE_TRY_REQUEST_DEPENDENCY(m, "nickserv/main")

	list_params = mowgli_patricia_create(strcasecanon);
	service_named_bind_command("nickserv", &ns_list);

	// list email
	static struct list_param email;
	email.opttype = OPT_STRING;
	email.is_match = email_match;

	static struct list_param lastlogin;
	lastlogin.opttype = OPT_AGE;
	lastlogin.is_match = lastlogin_match;

	static struct list_param pattern;
	pattern.opttype = OPT_STRING;
	pattern.is_match = pattern_match;

	static struct list_param registered;
	registered.opttype = OPT_AGE;
	registered.is_match = registered_match;

	static struct list_param primary;
	primary.opttype = OPT_BOOL;
	primary.is_match = primary_match;

	list_register("email", &email);
	list_register("lastlogin", &lastlogin);
	list_register("mail", &email);

	list_register("pattern", &pattern);
	list_register("registered", &registered);
	list_register("primary", &primary);

	static struct list_param waitauth;
	waitauth.opttype = OPT_BOOL;
	waitauth.is_match = has_waitauth;

	list_register("waitauth", &waitauth);
}

static void
mod_deinit(const enum module_unload_intent ATHEME_VATTR_UNUSED intent)
{
	service_named_unbind_command("nickserv", &ns_list);

	list_unregister("email");
	list_unregister("lastlogin");
	list_unregister("mail");

	list_unregister("pattern");
	list_unregister("registered");

	list_unregister("waitauth");
}

SIMPLE_DECLARE_MODULE_V1("nickserv/list", MODULE_UNLOAD_CAPABILITY_OK)
