// Topup management toosl
#include "clog.h"
#include "cgi.h"
#include "cfg.h"
#include "input.h"
#include "wrenchboard_api.h"
#include "bko.h"
#include "account.h"
#include "email.h"
#include "safestring.h"
#include "hmac_sha2.h"
#include <string>
#include "pgsql.h"
#include "pgsql_wrapper.h"
#include "payments.h"

#include "php.h"
#include <curl/curl.h>
#include "account.h"

long BkoCommonSessionCheck(long backoffice_id,long shop,long acc, const char *sessionid, int create );
long BkoResendOffer(CVars in, CVars &out);	
long BkoRefreshWallet(CVars in, CVars &out);
long BkoResendMarketing(CVars in, CVars &out);

struct MemoryStruct {
    char *memory;
    size_t size;
};

static size_t
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *) userp;

    mem->memory = (char*) realloc(mem->memory, mem->size + realsize + 1);
    if (mem->memory == NULL) {
        /* out of memory! */
        logfmt(logINFO, "not enough memory (realloc returned NULL)");
        return 0;
    }

    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;

    return realsize;
}

long bko_calls(CVars in, CVars &out)
{
	logfmt( logINFO, "bko_calls()" );
	out["result"] = "YES I GET TO BACK END";
    long action = REQ_LONG( in, "action", 0, -1);
	switch( action )
    { 		
		case WRENCHBOARD_BKO_LOGIN:
			return LoginBkoAdmin( in, out);		
		break;
			
		case WRENCHBOARD_BKO_RESEND_OFFER:
           return BkoResendOffer( in, out);
		break;	

        case WRENCHBOARD_BKO_MARKETING_MSG:
            return  BkoResendMarketing( in, out );
        break;

		case WRENCHBOARD_BKO_SEND_PUSH:
           return BkoSendPush( in, out);
		break;

		case WRENCHBOARD_BKO_REFRESH_WALLET:
            return  BkoRefreshWallet(in, out);
		break;
   }
	logfmt( logINFO, "/bko_calls()" );
	return 0;
}

long BkoRefreshWallet(CVars in, CVars &out)
{
	logfmt( logINFO, "BkoRefreshWallet()" );
	REQ_STRING (in, "uid",   5, 149, "(.*)");
	CVars x;

    long member_id = REQ_LONG( in, "member_id", 0, -1);
    REQ_STRING (in, "member_uid",   9, 49, "(.*)");

        if ( load_db_record( x, "SELECT * FROM members WHERE uid='%s' AND id =%lu", in["member_uid"].c_str(),in["member_id"].Long()  )){
                    CVars vw;
                    load_db_record(vw, "SELECT code AS currency FROM currency WHERE country='%s'",x["country"].c_str());
                     if ( vw["currency"]  !=""){
                         vw["currency"].set_valid( true );
                         out["wallet_id"] = CheckWallet(member_id,vw);
                     }
        }
	logfmt( logINFO, "/BkoRefreshWallet()" );
	return 0;
}


long BkoResendMarketing(CVars in, CVars &out)
{
	logfmt( logINFO, "BkoResendMarketing()" );
	REQ_STRING (in, "uid",   5, 149, "(.*)");
	CVars x;

	if ( load_db_record( x, "SELECT * FROM marketing_list WHERE uid='%s'", in["uid"].c_str() )){
		market_email(WRENCHBOARD_BKO_MARKETING_MSG, x, out);
	}


	logfmt( logINFO, "/BkoResendMarketing()" );
	return 0;
}

long BkoResendOffer(CVars in, CVars &out)
{
	logfmt( logINFO, "BkoResendOffer()" );
	REQ_STRING (in, "offer_code",   5, 49, "(.*)");
	CVars x;
	
	if ( load_db_record( x, "SELECT id AS offer_id FROM members_jobs_offer WHERE offer_code='%s'", in["offer_code"].c_str() )){
		//ret = PHP_CREATED_OK;
		//x["offer_id"] = offer_id;
		//x["offer_id"].set_valid(true);
		job_email(JOBS_INDIVIDUAL_OFFER_MAIL, x, out);
	}

				
	logfmt( logINFO, "/BkoResendOffer()" );
	return 0;
}

long BkoSendPush(CVars in, CVars &out)
{
  logfmt( logINFO, "BkoSendPush()" );
  long ret = PHP_API_BAD_PARAM;
  REQ_STRING (in, "member_uid",   9, 49, "(.*)");
  REQ_STRING (in, "push_text",   5, 249, "(.*)");

    const char *onesignal_app_id = CfgReadChar("onesignal.app_id");
    const char *onesignal_api_key = CfgReadChar("onesignal.api_key");

    CURL *curl;
    CURLcode res;

    struct MemoryStruct chunk;

    chunk.memory = (char*) malloc(1); /* will be grown as needed by the realloc above */
    chunk.size = 0; /* no data at this point */

    curl_global_init(CURL_GLOBAL_ALL);

    /* get a curl handle */
    curl = curl_easy_init();

    if (curl) {
        // Prepare data
        string data = "";
       // https://www.php.net/hash_hmac
/*
        zval algo, value, key, function_name;
        ZVAL_STRING(&algo, "sha256");
        ZVAL_STRING(&value, in["member_uid"].c_str());
        ZVAL_STRING(&key, "YzczOTA0YmYtMjZkOC00MWFlLTgxMTYtNzNhMzg2NGY1Mjli");
        ZVAL_STRING(&function_name, "hash_hmac");

        // zval params = { algo, value, key };
        zval params;
        ZVAL_ARR(&params, zend_new_array(3));

        int param_count = 3;
        zval *retval_ptr;

        // ZVAL_COPY(&params[0], &algo);
        Z_TRY_ADDREF(algo);
        add_index_zval(&params, 0, &algo);
        Z_TRY_ADDREF(value);
        add_index_zval(&params, 1, &value);
        Z_TRY_ADDREF(key);
        add_index_zval(&params, 2, &key);
*/
/*
        if (call_user_function(CG(function_table), NULL, &function_name, retval_ptr, param_count, &params TSRMLS_CC) == SUCCESS) {
          zend_string *member_uid_hash = Z_STR(*retval_ptr);
          out["member_uid_hash"] = ZSTR_VAL(member_uid_hash);
        }

        zval_ptr_dtor(&value);
        ZVAL_STRING(&value, in["push_text"].c_str());
        //ZVAL_COPY(&params[1], &value);
        Z_TRY_ADDREF(value);
        add_index_zval(&params, 1, &value);

        if (call_user_function(CG(function_table), NULL, &function_name, retval_ptr, param_count, &params TSRMLS_CC) == SUCCESS) {
          zend_string *push_text_hash = Z_STR(*retval_ptr);
          out["push_text_hash"] = ZSTR_VAL(push_text_hash);
        }
*/
        /*
        zval_ptr_dtor(&algo);
        zval_ptr_dtor(&value);
        zval_ptr_dtor(&key);
        zval_ptr_dtor(retval_ptr);
        zval_ptr_dtor(&function_name);

        //zval_ptr_dtor(params[0]);
        //zval_ptr_dtor(params[1]);
        //zval_ptr_dtor(params[2]);
        //efree(params);      // free container
        zval_ptr_dtor(&params); 
*/
//in["member_uid"] = "e30c6484-a70e-4d2b-9932-347999e81d7a";
//out["member_uid_hash"] = "71298987653243bc7b27f1e06b221a872203e7b78093e85c2a996f3b7885fde0";
//in["push_text"] = "English or Any Language Message";
//out["push_text_hash"] = "9ea7a84fdcc3682b60f2072536a24efedaa25fb5b4cad3a9c6c684533ce23967";
/*      
        hmac_sha256(unsigned char *key, unsigned int key_size,
                 unsigned char *message, unsigned int message_len,
                 unsigned char *mac, unsigned mac_size);
*/
        unsigned mac_size = SHA256_DIGEST_SIZE; // 256 / 8 = 32 bytes
        unsigned char buffer[mac_size];
        bzero(buffer, mac_size);
        hmac_sha256((unsigned char*)onesignal_api_key, strlen(onesignal_api_key), (unsigned char*)in["member_uid"].c_str(), strlen(in["member_uid"].c_str()), buffer, mac_size);

        std::size_t buffer_size = mac_size; // strlen((const char *)buffer);
        char member_uid[buffer_size * 2 + 1];
        bzero(member_uid, sizeof(member_uid));
        for(int i = 0; i < buffer_size; i++)
        {
            sprintf(&member_uid[2*i], "%02X", buffer[i]);
        }

        out["member_uid_hash"] = member_uid;
        logfmt(FLOG_MAX, "\nraw=%s\nencoder = %s\nout = %s\n", buffer, member_uid, out["member_uid_hash"].c_str());

        bzero(buffer, mac_size);
        hmac_sha256((unsigned char*)onesignal_api_key, strlen(onesignal_api_key), (unsigned char*)in["push_text"].c_str(), strlen(in["push_text"].c_str()), buffer, mac_size);

        buffer_size = mac_size; // strlen((const char *)buffer);
        char push_text[buffer_size * 2 + 1];
        bzero(push_text, sizeof(push_text));
        for(int i = 0; i < buffer_size; i++)
        {
            sprintf(&push_text[2*i], "%02X", buffer[i]);
        }

        out["push_text_hash"] = push_text;
        logfmt(FLOG_MAX, "\nraw=%s\nencoded = %s\nout = %s\n", buffer, push_text, out["push_text_hash"].c_str());


        data += "{\"include_aliases\": {\"external_id\": [\"";
        data += in["member_uid"].c_str();
        data += "\"],\"external_id_auth_hash\": [\"";
        data += out["member_uid_hash"].c_str();
        data += "\"]},\"target_channel\": \"push\",\"contents\": {\"en\": \"";
        data += in["push_text"].c_str();
        data += "\",\"en_auth_hash\": \"";
        data += out["push_text_hash"].c_str();
        data += "\"},\"app_id\": \"";
        data += onesignal_app_id;
        data += "\"}";

        logfmt(logINFO, "DATA: %s", data.c_str());

        string auth = "Authorization: Basic ";
        auth += onesignal_api_key;

        struct curl_slist *headerlist=NULL;
        headerlist = curl_slist_append(headerlist, auth.c_str());
        headerlist = curl_slist_append(headerlist, "accept: application/json");
        headerlist = curl_slist_append(headerlist, "content-type: application/json");

        curl_easy_setopt(curl, CURLOPT_URL, "https://onesignal.com/api/v1/notifications");
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &chunk);
        res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            // Error
            out["status"] = curl_easy_strerror(res);
            logfmt(logINFO, "ERROR: %s", out["status"].c_str());
            ret = PHP_API_BAD_PARAM;
        } else {
            logfmt(logINFO, "%lu bytes retrieved\n", (long) chunk.size);
            data = (const char *) chunk.memory;
            istringstream buffer(data);
            string temp, val;
            size_t found, found2;
            while (std::getline(buffer, temp)) {
                logfmt(logINFO, "line: %s", temp.c_str());
                found = temp.find_first_of("\"id\":\"");
                found2 = temp.find_first_of("\"", found + 7);
                if (found > 0 && (found2 - found - 5) > 0) {
                    val = temp.substr(found + 8, found2 - found - 8);
                    out["onesignal_message_id"] = val;
                    logfmt(logINFO, "id => '%s'", out["onesignal_message_id"].c_str());
                }
            }
            ret = PHP_API_OK;
        }

        curl_slist_free_all(headerlist);
        curl_easy_cleanup(curl);

        if (chunk.memory) {
            free(chunk.memory);
        }
    }
    curl_global_cleanup();

    logfmt( logINFO, "/BkoSendPush()" );
    return ret;
}

long LoginBkoAdmin(CVars in, CVars &out)
{
	long ret = PHP_API_BAD_PARAM;
	logfmt( logINFO, "LoginBkoAdmin()" );
	REQ_STRING (in, "username",   5, 49, "(.*)");
	REQ_STRING (in, "password",   5, 49, "(.*)");
	//REQ_STRING (in, "sessionid",  4, 40, "(.*)");
	const char * loc = getenv("REMOTE_ADDR");
	
	load_db_record( out, "SELECT md5( md5('now()')||'%d' ) AS sessionid",rand()*10000);
	in["sessionid"] = out["sessionid"];  in["sessionid"].set_valid( true );
	 
	
		ret = load_db_record( out, "SELECT *,id AS backoffice_id "
		                           " FROM backoffice WHERE status=1 "
		                           " AND LOWER(username)=LOWER('%s') AND pass= md5('%s')", in["username"].c_str(), in["password"].c_str() );
		if (ret>0) {
			if (BkoCommonSessionCheck(out["id"].Long(),0,0, in["sessionid"].c_str(), 1)>0) {
			    pgsql_exec("UPDATE backoffice SET last_login=NOW() WHERE id=%ld AND uid='%s'", out["backoffice_id"].Long(), out["uid"].c_str());
				out["stauts"] = "OK";
				ret = PHP_API_OK;
			} else {
				out["status"] = "Session check failed";
			}
		} else {
			out["status"] = "Invalid username and/or password";
		}		
	
	logfmt( logINFO, "/LoginBkoAdmin()" );	
	return ret;
}


long BkoCommonSessionCheck(long backoffice_id,long shop,long acc, const char *sessionid, int create )
{
	logfmt( logINFO, "long BkoCommonSessionCheck(long backoffice_id,long shop, const char *sessionid, int create )" );
	// Sanity check
	if (backoffice_id<1 || sessionid==NULL || strlen(sessionid)<4) {
		return -1L; // Invalif parameters
	}
	
	char ptid[30];
	if ( shop > 0 )
	{
		  sprintf( ptid, " AND shop=%lu ", shop );
	}
	else
	{
		 sprintf( ptid, " " );  // just empty space
	}
	// Clean old sessions
	   if (create>0) 
	   {   pgsql_exec("DELETE FROM backoffice_session WHERE backoffice_id=%ld %s ", backoffice_id,ptid);   }
	   else
	   { 	pgsql_exec("DELETE FROM backoffice_session WHERE backoffice_id=%ld %s AND  updated < (now() - interval '15 minutes')", backoffice_id,ptid);    }

	   if (create==0 && pgsql_exec("UPDATE backoffice_session SET updated=NOW() WHERE backoffice_id=%ld %s AND sessionid='%s'", backoffice_id,ptid, sessionid)>0) {
		return 1L; // Session updated
        }
        if (create>0) {
		// Check session i?
		/*
		const PGresult *res = pgsql_query("SELECT * FROM backoffice_session WHERE backoffice_id=%ld %s AND sessionid<>'%s'", backoffice_id,ptid, sessionid);
		if (res!=NULL && pgsql_num_rows(res)>0) {
			return -2L; // Active sessions found
		}
		*/
		CVars sess; // Do we have the same session already?
		if (load_db_record( sess, "SELECT * FROM backoffice_session WHERE backoffice_id=%lu %s AND sessionid='%s'", backoffice_id,ptid, sessionid)>0) {
			pgsql_exec("UPDATE backoffice_session SET updated=NOW() WHERE backoffice_id=%ld %s AND sessionid='%s'", backoffice_id,ptid, sessionid);
			return sess["id"].Long();
		}
		// Create a new session
		sess["backoffice_id"] = backoffice_id; sess["backoffice_id"].set_valid(true);
		sess["sessionid"] = sessionid; sess["sessionid"].set_valid(true);
		const char * loc = getenv("REMOTE_ADDR");
		sess["loc"] = loc; sess["loc"].set_valid(true);
		
		if ( shop > 0 )
		{
			sess["shop"] = shop; sess["shop"].set_valid(true);
		}
				if ( acc > 0 )
		{
			sess["account"] = acc; sess["account"].set_valid(true);
		}
		long sid = insert_db_record( DBS_VALID, "backoffice_session", "backoffice_session_id_seq", sess );
		if (sid>0) {
			return sid; // New session created
		}
		return -3L; // Failed to create new session
	}
	logfmt( logINFO, "/long BkoCommonSessionCheck(long backoffice_id,long shop, const char *sessionid, int create )" );
	return 0L; // No route
}

/*
 
 CREATE TABLE backoffice_session (
    id SERIAL,
	backoffice_id INT REFERENCES managers(id),
	shop INT DEFAULT 0,
	sessionid varchar(100) NOT NULL,
	added timestamp without time zone DEFAULT now(),
	updated timestamp without time zone DEFAULT now(),
	status   integer DEFAULT 1, 
    loc INET	
	);
 

*/
