/*
 * Copyright (C) 2021 Arsen Semenov arsperger@gmail.com
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include <stdio.h>
#include <stdlib.h>

#include "slack.h"

MODULE_VERSION

static char *_slmsg_buf = NULL;
static int mod_init(void);
static void mod_destroy(void);

static int buf_size = 4096;
static char *slack_url = NULL;
static char *slack_channel = SLACK_DEFAULT_CHANNEL;
static char *slack_username = SLACK_DEFAULT_USERNAME;
static char *slack_icon = SLACK_DEFAULT_ICON;

/* clang-format off */
/**
 * Exported functions
 */
static cmd_export_t cmds[] = {
	{"slack_send", (cmd_function)slack_send1, 1, slack_fixup, slack_fixup_free, ANY_ROUTE},
	{0, 0, 0, 0, 0, 0}
};

/**
 * Exported parameters
 */
static param_export_t mod_params[] = {
	{"slack_url", PARAM_STRING | PARAM_USE_FUNC, (void *)_slack_url_param},
	{"channel", PARAM_STRING, &slack_channel}, // channel starts with #
	{"username", PARAM_STRING, &slack_username},
	{"icon_emoji", PARAM_STRING, &slack_icon},
	{0, 0, 0}
};

/**
 * Module description
 */
struct module_exports exports = {
	"slack",         /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,            /* exported functions */
	mod_params,      /* exported parameters */
	0,               /* exported rpc functions */
	0,               /* exported pseudo-variables */
	0,               /* response handling function */
	mod_init,        /* module init function */
	0,               /* per-child init function */
	mod_destroy      /* module destroy function */
};
/* clang-format on */

/**
 * Module init
 */
static int mod_init(void)
{
	LM_INFO("slack module init\n");

	if(httpc_load_api(&httpapi) != 0) {
		LM_ERR("can not bind to http_client API \n");
		return -1;
	}

	_slmsg_buf = (char *)pkg_malloc((buf_size + 1) * sizeof(char));
	if(_slmsg_buf == NULL) {
		PKG_MEM_ERROR;
		return -1;
	}
	return (0);
}

/**
 * Module destroy
 */
static void mod_destroy()
{
	LM_INFO("slack module destroy\n");
	if(_slmsg_buf)
		pkg_free(_slmsg_buf);
	if(slack_url)
		pkg_free(slack_url);
	return;
}

/* free and reset str */
static void slack_free_str(str *string)
{
	str ptr = STR_NULL;
	if(string->s == NULL)
		return;

	ptr = *string;

	if(ptr.s != NULL && ptr.len > 0)
		pkg_free(ptr.s);

	string->s = NULL;
	string->len = 0;

	return;
}

/**
 * send slack message using http_client api
 * @return 0 on success, -1 on error
 */
static int slack_curl_send(struct sip_msg *msg, char *uri, str *post_data)
{
	int datasz;
	char *send_data;
	str ret = STR_NULL;
	int curl = 0;

	datasz = snprintf(NULL, 0, BODY_FMT, slack_channel, slack_username,
			post_data->s, slack_icon);
	if(datasz < 0) {
		LM_ERR("snprintf error in calculating buffer size\n");
		return -1;
	}
	send_data = (char *)pkg_mallocxz((datasz + 1) * sizeof(char));
	if(send_data == NULL) {
		LM_ERR("can not allocate pkg memory [%d] bytes\n", datasz);
		return -1;
	}
	snprintf(send_data, datasz + 1, BODY_FMT, slack_channel, slack_username,
			post_data->s, slack_icon);

	/* send request */
	curl = httpapi.http_client_query(msg, uri, &ret, send_data, NULL);
	pkg_free(send_data);

	if(curl >= 300 || curl < 100) {
		LM_ERR("request failed with error: %d\n", curl);
		slack_free_str(&ret);
		return -1;
	}

	LM_DBG("slack send response: [%.*s]\n", ret.len, ret.s);
	slack_free_str(&ret);

	return 0;
}

/**
 * parse slack_url param2
 */
static int _slack_parse_url_param(char *val)
{
	size_t len;
	len = strlen(val);
	if(len > SLACK_URL_MAX_SIZE) {
		LM_ERR("webhook url max size exceeded %d\n", SLACK_URL_MAX_SIZE);
		return -1;
	}

	if(strncmp(val, "https://hooks.slack.com", 23)) {
		LM_ERR("slack invalid webhook url [%s]\n", val);
		return -1;
	}

	// TODO: parse webhook to multiple channels? eg.: chan1=>https://AAA/BBB/CC, chan2=>...

	slack_url = (char *)pkg_malloc(len + 1);
	if(slack_url == NULL) {
		PKG_MEM_ERROR;
		return -1;
	}
	strncpy(slack_url, val, len);
	slack_url[len] = '\0';

	return 0;
}

/**
 * parse slack_url param
 */
int _slack_url_param(modparam_t type, void *val)
{
	if(val == NULL) {
		LM_ERR("webhook url not specified\n");
		return -1;
	}

	return _slack_parse_url_param((char *)val);
}

static int slack_fixup_helper(void **param, int param_no)
{
	sl_msg_t *sm;
	str s;

	sm = (sl_msg_t *)pkg_malloc(sizeof(sl_msg_t));
	if(sm == NULL) {
		PKG_MEM_ERROR;
		return -1;
	}
	memset(sm, 0, sizeof(sl_msg_t));
	s.s = (char *)(*param);
	s.len = strlen(s.s);

	if(pv_parse_format(&s, &sm->m) < 0) {
		LM_ERR("wrong format[%s]\n", (char *)(*param));
		pkg_free(sm);
		return E_UNSPEC;
	}
	*param = (void *)sm;
	return 0;
}


static int slack_fixup(void **param, int param_no)
{
	if(param_no != 1 || param == NULL || *param == NULL) {
		LM_ERR("invalid parameter number %d\n", param_no);
		return E_UNSPEC;
	}
	return slack_fixup_helper(param, param_no);
}

static int slack_fixup_free(void **param, int param_no)
{
	sl_msg_t *sm;
	if(param_no != 1 || param == NULL || *param == NULL) {
		LM_ERR("invalid parameter number %d\n", param_no);
		return E_UNSPEC;
	}

	sm = *param;
	pv_elem_free_all(sm->m);
	pkg_free(sm);
	return 0;
}

/**
 * send text message to slack
 */
static inline int slack_helper(struct sip_msg *msg, sl_msg_t *sm)
{
	str txt;
	txt.len = buf_size;

	if(_slack_print_log(msg, sm->m, _slmsg_buf, &txt.len) < 0)
		return -1;

	txt.s = _slmsg_buf;

	return slack_curl_send(msg, slack_url, &txt);
}

static int slack_send1(struct sip_msg *msg, char *frm, char *str2)
{
	return slack_helper(msg, (sl_msg_t *)frm);
}


/**
 * Kemi
 * send slack msg after evaluation of pvars
 * @return 0 on success, -1 on error
 */
static int ki_slack_send(sip_msg_t *msg, str *slmsg)
{
	pv_elem_t *xmodel = NULL;
	str txt = STR_NULL;
	int res;

	if(pv_parse_format(slmsg, &xmodel) < 0) {
		LM_ERR("wrong format[%s]\n", slmsg->s);
		return -1;
	}
	if(pv_printf_s(msg, xmodel, &txt) != 0) {
		LM_ERR("cannot eval reparsed value\n");
		pv_elem_free_all(xmodel);
		return -1;
	}

	res = slack_curl_send(msg, slack_url, &txt);
	pv_elem_free_all(xmodel);
	return res;
}


/* clang-format off */
static sr_kemi_t sr_kemi_slack_exports[] = {
	{ str_init("slack"), str_init("slack_send"),
		SR_KEMIP_INT, ki_slack_send,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},

	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
};
/* clang-format on */


int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
	sr_kemi_modules_add(sr_kemi_slack_exports);
	return 0;
}
