/*
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 *
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fraunhofer FOKUS Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 *
 * NB: A lot of this code was originally part of OpenIMSCore,
 * FhG Fokus.
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * 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 "stats.h"
#include "../cdp/cdp_load.h"
#include "../../modules/tm/tm_load.h"
#include "../../modules/ims_dialog/dlg_load.h"
#include "../ims_usrloc_scscf/usrloc.h"
#include "api.h"
#include "cxdx_avp.h"

#include "reply.h"
#include "rerrno.h"
#include "ims_registrar_scscf_mod.h"
#include "cxdx_sar.h"
#include "save.h"
#include "userdata_parser.h"
#include "../../core/counters.h"
#include "../../core/data_lump_rpl.h"
#include "sip_msg.h"
#include "regtime.h"
#include "../../core/parser/hf.h"
#include "../../lib/ims/ims_getters.h"
#include "registrar_notify.h"
#include "pvt_message.h"

extern struct cdp_binds cdpb;
extern unsigned int send_vs_callid_avp;

int create_return_code(int result)
{
	int rc;
	int_str avp_val, avp_name;
	avp_name.s.s = "saa_return_code";
	avp_name.s.len = 15;

	//build avp spec for saa_return_code
	avp_val.n = result;

	rc = add_avp(AVP_NAME_STR, avp_name, avp_val);

	if(rc < 0)
		LM_ERR("couldnt create AVP\n");
	else
		LM_INFO("created AVP successfully : [%.*s] - [%d]\n", avp_name.s.len,
				avp_name.s.s, result);

	return 1;
}

void free_saved_transaction_data(saved_transaction_t *data)
{
	if(!data)
		return;

	if(data->public_identity.s && data->public_identity.len) {
		shm_free(data->public_identity.s);
		data->public_identity.len = 0;
	}
	free_contact_buf(data->contact_header);
	shm_free(data);
}

void async_cdp_callback(
		int is_timeout, void *param, AAAMessage *saa, long elapsed_msecs)
{
	struct cell *t = 0;
	int rc = -1, experimental_rc = -1;
	int result = CSCF_RETURN_TRUE;
	saved_transaction_t *data = 0;
	struct sip_msg *req;

	str xml_data = {0, 0}, ccf1 = {0, 0}, ccf2 = {0, 0}, ecf1 = {0, 0},
		ecf2 = {0, 0};
	ims_subscription *s = 0;
	rerrno = R_FINE;

	if(!param) {
		LM_DBG("No transaction data this must have been called from usrloc cb "
			   "impu deleted - just log result code and then exit");
		cxdx_get_result_code(saa, &rc);
		cxdx_get_experimental_result_code(saa, &experimental_rc);

		if(saa)
			cdpb.AAAFreeMessage(&saa);

		if(!rc && !experimental_rc) {
			LM_ERR("bad SAA result code\n");
			return;
		}
		switch(rc) {
			case -1:
				LM_DBG("Received Diameter error\n");
				return;

			case AAA_UNABLE_TO_COMPLY:
				LM_DBG("Unable to comply\n");
				return;

			case AAA_SUCCESS:
				LM_DBG("received AAA success\n");
				return;

			default:
				LM_ERR("Unknown error\n");
				return;
		}

	} else {
		LM_DBG("There is transaction data this must have been called from save "
			   "or assign server unreg");
		data = (saved_transaction_t *)param;
		if(tmb.t_lookup_ident(&t, data->tindex, data->tlabel) < 0) {
			LM_ERR("t_continue: transaction not found and t is now pointing to "
				   "%p and will be set to NULL\n",
					t);
			t = NULL;
			rerrno = R_SAR_FAILED;
			goto error_no_send;
		}

		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_URI, &t->uri_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_URI, &t->uri_avps_to);
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_USER, &t->user_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_USER, &t->user_avps_to);
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_DOMAIN, &t->domain_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_DOMAIN, &t->domain_avps_to);

		get_act_time();

		req = get_request_from_tx(t);
		if(!req) {
			LM_ERR("Failed to get SIP Request from Transaction\n");
			goto error_no_send;
		}

		if(is_timeout) {
			update_stat(stat_sar_timeouts, 1);
			LM_ERR("Transaction timeout - did not get SAA\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}
		if(!saa) {
			LM_ERR("Error sending message via CDP\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}

		update_stat(sar_replies_received, 1);
		update_stat(sar_replies_response_time, elapsed_msecs);


		/* check and see that all the required headers are available and can be parsed */
		if(parse_message_for_register(req) < 0) {
			LM_ERR("Unable to parse register message correctly\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}

		LM_DBG("callid for found transaction is [%.*s]\n",
				req->callid->body.len, req->callid->body.s);


		cxdx_get_result_code(saa, &rc);
		cxdx_get_experimental_result_code(saa, &experimental_rc);
		cxdx_get_charging_info(saa, &ccf1, &ccf2, &ecf1, &ecf2);

		if(!rc && !experimental_rc) {
			LM_ERR("bad SAA result code\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}

		switch(rc) {
			case -1:
				LM_DBG("Received Diameter error\n");
				rerrno = R_SAR_FAILED;
				goto error;

			case AAA_UNABLE_TO_COMPLY:
				LM_DBG("Unable to comply\n");
				rerrno = R_SAR_FAILED;
				goto error;

			case AAA_SUCCESS:
				LM_DBG("received AAA success for SAR - SAA\n");
				break;

			default:
				LM_ERR("Unknown error\n");
				rerrno = R_SAR_FAILED;
				goto error;
		}
		//success
		//if this is from a save (not a server assign unreg) and expires is zero we don't update usrloc as this is a dereg and usrloc was updated previously
		if(data->sar_assignment_type != AVP_IMS_SAR_UNREGISTERED_USER
				&& data->expires == 0) {
			LM_DBG("no need to update usrloc - already done for de-reg\n");
			result = CSCF_RETURN_TRUE;
			goto success;
		}

		xml_data = cxdx_get_user_data(saa);
		/*If there is XML user data we must be able to parse it*/
		if(xml_data.s && xml_data.len > 0) {
			LM_DBG("Parsing user data string from SAA\n");
			s = parse_user_data(xml_data);
			if(!s) {
				LM_ERR("Unable to parse user data XML string\n");
				rerrno = R_SAR_FAILED;
				goto error;
			}
			LM_DBG("Successfully parse user data XML setting ref to 1 (we are "
				   "referencing it)\n");
			s->ref_count =
					1; //no need to lock as nobody else will be referencing this piece of memory just yet
		} else {
			if(data->require_user_data) {
				LM_ERR("We require User data for this assignment/register and "
					   "none was supplied\n");
				rerrno = R_SAR_FAILED;
				result = CSCF_RETURN_FALSE;
				goto done;
			}
		}

		//here we update the contacts and also build the new contact header for the 200 OK reply
		if(update_contacts(req, data->domain, &data->public_identity,
				   data->sar_assignment_type, &s, &ccf1, &ccf2, &ecf1, &ecf2,
				   &data->contact_header)
				<= 0) {
			LM_ERR("Error processing REGISTER\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}

		if(data->contact_header) {
			LM_DBG("Updated contacts: %.*s\n", data->contact_header->data_len,
					data->contact_header->buf);
		} else {
			LM_DBG("Updated unreg contact\n");
		}
	}

success:
	update_stat(accepted_registrations, 1);

done:
	if(data->sar_assignment_type != AVP_IMS_SAR_UNREGISTERED_USER)
		reg_send_reply_transactional(req, data->contact_header, t);
	LM_DBG("DBG:SAR Async CDP callback: ... Done resuming transaction\n");

	create_return_code(result);

	//release our reference on subscription (s)
	if(s)
		ul.unref_subscription(s);

	//free memory
	if(saa)
		cdpb.AAAFreeMessage(&saa);
	if(t) {
		//        del_nonshm_lump_rpl(&req->reply_lump);
		tmb.unref_cell(t);
	}
	//free path vector pkg memory
	//    reset_path_vector(req);

	tmb.t_continue_skip_timer(data->tindex, data->tlabel, data->act);
	free_saved_transaction_data(data);
	return;

error:
	create_return_code(-2);
	if(data->sar_assignment_type != AVP_IMS_SAR_UNREGISTERED_USER)
		reg_send_reply_transactional(req, data->contact_header, t);

error_no_send: //if we don't have the transaction then we can't send a transaction response
	update_stat(rejected_registrations, 1);
	//free memory
	if(saa)
		cdpb.AAAFreeMessage(&saa);
	if(t) {
		//        del_nonshm_lump_rpl(&req->reply_lump);
		tmb.unref_cell(t);
	}
	tmb.t_continue(data->tindex, data->tlabel, data->act);
	free_saved_transaction_data(data);
	return;
}

/**
 * Create and send a Server-Assignment-Request and returns the Answer received for it.
 * This function performs the Server Assignment operation.
 * @param msg - the SIP message to send for
 * @param public_identity - the public identity of the user
 * @param server_name - local name of the S-CSCF to save on the HSS
 * @param assignment_type - type of the assignment
 * @param data_available - if the data is already available
 * @returns the SAA
 */
int cxdx_send_sar(struct sip_msg *msg, str public_identity,
		str private_identity, str server_name, int assignment_type,
		int data_available, saved_transaction_t *transaction_data)
{
	AAAMessage *sar = 0;
	AAASession *session = 0;
	unsigned int hash = 0, label = 0;
	struct hdr_field *hdr;
	str call_id;

	session = cdpb.AAACreateSession(0);

	sar = cdpb.AAACreateRequest(IMS_Cx, IMS_SAR, Flag_Proxyable, session);
	if(session) {
		cdpb.AAADropSession(session);
		session = 0;
	}
	if(!sar)
		goto error1;

	if(msg && send_vs_callid_avp) {
		call_id = cscf_get_call_id(msg, &hdr);
		if(call_id.len > 0 && call_id.s) {
			if(!cxdx_add_call_id(sar, call_id))
				LM_WARN("Failed to add call-id to SAR.... continuing... "
						"assuming non-critical\n");
		}
	}

	if(!cxdx_add_destination_realm(sar, cxdx_dest_realm))
		goto error1;

	if(!cxdx_add_vendor_specific_appid(
			   sar, IMS_vendor_id_3GPP, IMS_Cx, 0 /*IMS_Cx*/))
		goto error1;
	if(!cxdx_add_auth_session_state(sar, 1))
		goto error1;

	if(!cxdx_add_public_identity(sar, public_identity))
		goto error1;
	if(!cxdx_add_server_name(sar, server_name))
		goto error1;
	if(private_identity.len)
		if(!cxdx_add_user_name(sar, private_identity))
			goto error1;
	if(!cxdx_add_server_assignment_type(sar, assignment_type))
		goto error1;
	if(!cxdx_add_userdata_available(sar, data_available))
		goto error1;

	if(msg && tmb.t_get_trans_ident(msg, &hash, &label) < 0) {
		// it's ok cause we can call this async with a message for ul callbacks!
		LM_DBG("SIP message without transaction... must be a ul callback\n");
		//return 0;
	}

	if(cxdx_forced_peer.len)
		cdpb.AAASendMessageToPeer(sar, &cxdx_forced_peer,
				(void *)async_cdp_callback, (void *)transaction_data);
	else
		cdpb.AAASendMessage(
				sar, (void *)async_cdp_callback, (void *)transaction_data);

	return 0;

error1: //Only free SAR IFF it has not been passed to CDP
	if(sar)
		cdpb.AAAFreeMessage(&sar);

	return -1;
}
