/*
 * SPDX-License-Identifier: ISC
 * SPDX-URL: https://spdx.org/licenses/ISC.html
 *
 * Copyright (C) 2005 William Pitcock, et al.
 *
 * This file contains code for the CService RECOVER functions.
 */

#include <atheme.h>

static void
cs_cmd_recover(struct sourceinfo *si, int parc, char *parv[])
{
	struct chanuser *cu, *origin_cu = NULL;
	struct mychan *mc;
	mowgli_node_t *n, *tn;
	struct chanban *cb;
	char *name = parv[0];
	char hostbuf2[BUFSIZE];
	char e;
	bool added_exempt = false;
	int i;
	char str[3];

	if (!name)
	{
		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "RECOVER");
		command_fail(si, fault_needmoreparams, _("Syntax: RECOVER <#channel>"));
		return;
	}

	if (!(mc = mychan_find(name)))
	{
		command_fail(si, fault_nosuch_target, STR_IS_NOT_REGISTERED, name);
		return;
	}

	if (!chanacs_source_has_flag(mc, si, CA_RECOVER))
	{
		command_fail(si, fault_noprivs, STR_NOT_AUTHORIZED);
		return;
	}

	if (metadata_find(mc, "private:close:closer"))
	{
		command_fail(si, fault_noprivs, STR_CHANNEL_IS_CLOSED, name);
		return;
	}

	if (!mc->chan)
	{
		if (chansvs.changets)
		{
			// Otherwise, our modes are likely ignored.
			join(mc->name, chansvs.nick);
			mc->flags |= MC_INHABIT;
		}
		if (!mc->chan)
		{
			command_fail(si, fault_nosuch_target, STR_CHANNEL_IS_EMPTY, name);
			return;
		}
	}

	command_add_flood(si, FLOOD_HEAVY);
	verbose(mc, "\2%s\2 used RECOVER.", get_source_name(si));
	logcommand(si, CMDLOG_DO, "RECOVER: \2%s\2", mc->name);

	// deop everyone
	MOWGLI_ITER_FOREACH(n, mc->chan->members.head)
	{
		cu = (struct chanuser *)n->data;

		if (cu->user == si->su)
			origin_cu = cu;
		else if (is_internal_client(cu->user))
			;
		else
		{
			if (ircd->uses_owner && (ircd->owner_mode & cu->modes))
			{
				modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, ircd->owner_mchar[1], CLIENT_NAME(cu->user));
				cu->modes &= ~ircd->owner_mode;
			}
			if (ircd->uses_protect && (ircd->protect_mode & cu->modes))
			{
				modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, ircd->protect_mchar[1], CLIENT_NAME(cu->user));
				cu->modes &= ~ircd->protect_mode;
			}
			if ((CSTATUS_OP & cu->modes))
			{
				modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, 'o', CLIENT_NAME(cu->user));
				cu->modes &= ~CSTATUS_OP;
			}
			if (ircd->uses_halfops && (ircd->halfops_mode & cu->modes))
			{
				modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, ircd->halfops_mchar[1], CLIENT_NAME(cu->user));
				cu->modes &= ~ircd->halfops_mode;
			}
		}
	}

	if (origin_cu == NULL)
	{
		/* if requester is not on channel,
		 * remove modes that keep people out */
		if (CMODE_LIMIT & mc->chan->modes)
			channel_mode_va(si->service->me, mc->chan, 1, "-l");
		if (CMODE_KEY & mc->chan->modes)
			channel_mode_va(si->service->me, mc->chan, 2, "-k", "*");

		/* stuff like join throttling
		 * XXX only remove modes that could keep people out
		 * -- jilles */
		str[0] = '-';
		str[2] = '\0';
		for (i = 0; ignore_mode_list[i].mode != '\0'; i++)
		{
			str[1] = ignore_mode_list[i].mode;
			if (mc->chan->extmodes[i] != NULL)
				channel_mode_va(si->service->me, mc->chan, 1, str);
		}
	}
	else
	{
		if (!(CSTATUS_OP & origin_cu->modes))
			modestack_mode_param(chansvs.nick, mc->chan, MTYPE_ADD, 'o', CLIENT_NAME(si->su));
		origin_cu->modes |= CSTATUS_OP;
	}

	if (origin_cu != NULL || (si->su != NULL && chanacs_source_flags(mc, si) & (CA_OP | CA_AUTOOP)))
	{

		channel_mode_va(si->service->me, mc->chan, 1, "+im");
	}
	else if (CMODE_INVITE & mc->chan->modes)
	{
		channel_mode_va(si->service->me, mc->chan, 1, "-i");
	}

	if (si->su != NULL)
	{
		// unban the user
		snprintf(hostbuf2, BUFSIZE, "%s!%s@%s", si->su->nick, si->su->user, si->su->vhost);

		for (n = next_matching_ban(mc->chan, si->su, 'b', mc->chan->bans.head); n != NULL; n = next_matching_ban(mc->chan, si->su, 'b', tn))
		{
			tn = n->next;
			cb = n->data;

			modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, cb->type, cb->mask);
			chanban_delete(cb);
		}

		if (origin_cu == NULL)
		{
			// set an exempt on the user calling this
			e = ircd->except_mchar;
			if (e != '\0')
			{
				if (!chanban_find(mc->chan, hostbuf2, e))
				{
					chanban_add(mc->chan, hostbuf2, e);
					modestack_mode_param(chansvs.nick, mc->chan, MTYPE_ADD, e, hostbuf2);
					added_exempt = true;
				}
			}
		}
	}

	modestack_flush_channel(mc->chan);

	// invite them back. must have sent +i before this
	if (origin_cu == NULL && si->su != NULL)
		invite_sts(si->service->me, si->su, mc->chan);

	if (added_exempt)
		command_success_nodata(si, _("Recover complete for \2%s\2, ban exception \2%s\2 added."), mc->chan->name, hostbuf2);
	else
		command_success_nodata(si, _("Recover complete for \2%s\2."), mc->chan->name);
}

static struct command cs_recover = {
	.name           = "RECOVER",
	.desc           = N_("Regain control of your channel."),
	.access         = AC_NONE,
	.maxparc        = 1,
	.cmd            = &cs_cmd_recover,
	.help           = { .path = "cservice/recover" },
};

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

        service_named_bind_command("chanserv", &cs_recover);
}

static void
mod_deinit(const enum module_unload_intent ATHEME_VATTR_UNUSED intent)
{
	service_named_unbind_command("chanserv", &cs_recover);
}

SIMPLE_DECLARE_MODULE_V1("chanserv/recover", MODULE_UNLOAD_CAPABILITY_OK)
