#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "lib.h"
#include "http.h"
#include "lprintf.h"
#include "lib_spawn.h"
#include "ex_auth.h"
#include "sauth.h"
#include "cjson.h"
#define DMID 14
#include "debug.h"
#ifdef DO_THREADS
#include "thread.h"
#endif

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#include "defines.h"
#define iffree(x) if (x) free(x); x = NULL;
int sauth_reset(char *reason);
char *sauth_encode_spaces(char *pass, char *in);
void sauth_init_spawn_cmd(char *scmd) ;
void sauth_init_spawn_cmd(char *scmd) ;
int sauth_spawn(void);
int sauth_restart_one(char *reason);
static IHANDLE pin;
static IHANDLE pout;
static pid_t pid;

static int (*imsg)( const char *format ,...);
int sauth_startall(void);
static char spawn_cmd[BFSZ];
static int is_shutdown;
#define MAX_ENV 100
static char *sauth_env[MAX_ENV];
static int sauth_nenv;
static int mutsauth_i;
static int (*mutsauth_cb)(int vm, int isget, int seconds);
int sauth_set_mutex(int (*cb)(int vm, int isget, int seconds), int m)
{
		mutsauth_i = m;
		mutsauth_cb = cb;
		return TRUE;
}

int sauth_wait(void)
{
	return mutsauth_cb(mutsauth_i,TRUE,600);
}
void sauth_release(void)
{
	mutsauth_cb(mutsauth_i,FALSE,600); return; 
}
int sauth_init(int (*vimsg)( const char *format ,...), char *scmd)
{
	imsg = vimsg;
	if (strlen(scmd)==0) return TRUE;
	sauth_init_spawn_cmd(scmd);
	sauth_startall();
	return TRUE;
}
int sauth_startall(void)
{
	sauth_wait();
	sauth_spawn();
	sauth_release();
	return TRUE;
}
void sauth_init_spawn_cmd(char *scmd) 
{
	ncpy(spawn_cmd,scmd,200);
}
int sauth_restart(void)
{
	char reason[BFSZ];
	if (is_shutdown) return TRUE;
	if (imsg) imsg("sauth_auth: restart called, first shutting down old ones\n");
	sauth_restart_one(reason);
	if (imsg) imsg("sauth_auth: restart: now starting new ones\n");
	sauth_startall();
	if (imsg) imsg("sauth_auth: restart: finished\n");
	return TRUE;
}
int sauth_goodascii(char *cmdline)
{
	char *p;
	for(p = cmdline; *p; p++) {
		if (*p < 32) break;
		if (*p >= 127) break;
	}
	return *p == 0;
}
int sauth_restart_one(char *reason)
{
	int r;
	char cmdline[BFSZ];

	if (is_shutdown) return FALSE;
	
	strcpy(cmdline,"exit\n");
	if (pin > 0) {
		r = spawn_write_timed(pin,cmdline,lib_strlen(cmdline),100);
		lib_msleep(100);
	}
	sauth_reset(reason);
	return TRUE;
}
int sauth_spawn(void)
{
	int r;
	char etext[BFSZ];
	if (is_shutdown) return FALSE;
	if (strlen(spawn_cmd)==0) return FALSE;
	r = lib_spawn_noenv(spawn_cmd,&pin,&pout, etext);
	if (r<=0) {
		if (imsg) imsg("sauth_auth: Could not spawn %.500s\n",etext);
		return FALSE;
	}
	if (imsg) imsg("sauth_auth: Done spawn\n");
	pid = r;
	return TRUE;
}
int sauth_do_cmd(char *cmdline, char *result, int bfsz)
{
	int r;
	int n;
	time_t start = time(NULL);
//	printf("do_comd pin %d put %d (%s)\n",(int)pin,(int)pout,cmdline);
	sauth_wait();
	r = spawn_write(pin,cmdline,lib_strlen(cmdline));
	printf("do_cmd, wait for result\n");
	for (;time(NULL)-start<60;) {
		lib_msleep(10);
		r = spawn_nready(pout);
		printf("nready %d\n",r);
		if (r>0) {
			n = spawn_read_timed(pout,result,bfsz-1,5000); // Small timeout as it should already be there.
		printf("read %d\n",n);
			if (n>0) {
				result[n] = 0;
				sauth_release();
				return TRUE;
			}
		}
		lib_msleep(100);
	}
	sauth_restart();
	sauth_release();
	return TRUE;
}
int sauth_set_shutdown(void)
{
	is_shutdown = TRUE;
	return TRUE;
}
int sauth_reset(char *reason)
{
	if (pout>0) spawn_close_pipe(pout); pout = -1;
	if (pin>0) spawn_close_pipe(pin); pin = -1;
	if (pid!=0) spawn_kill(pid); pid = 0; /* Kill the sucker */
	return TRUE;
}

#define BIGBF 5000
char *sauth_rest(char *info)
{	
	char *s = strchr(info,' ');
	if (s!=NULL) s = strchr(s+1,' ');
	if (s!=NULL) s = strchr(s+1,' ');
	if (s!=NULL) s = strchr(s+1,' ');
	if (s!=NULL) s++;
	return s;
}
static int auth_pass_lookup;
int sauth_set_lookup(int x)
{
	auth_pass_lookup = x;
	return TRUE;
}
int sauth_ex_lookup(char *userdom, char *pass, char *result, int bfsz)
{
// use a setting:
	int r;
	int failed;
	char fake[BFSZ];
	char rbf[BFSZ];
	if (auth_pass_lookup) {
		r = ex_lookup(userdom,pass,result,bfsz);
		if (r) if (*result=='+') {
			return r;
		}
		r = sauth_check(userdom,pass,result,&failed);	
		if (r) {
			if (*result=='+') {
				srand((int)time(NULL)+(int)th_id()*1000);
				lprintf(fake,BFSZ-1,"%d%d",myrand(),myrand());
				ex_set(userdom,"*",fake,rbf,BFSZ-1); // CREATE a fake entry with random password.		
				return TRUE;
			}
		}
	}
	return ex_lookup(userdom,pass,result,bfsz);
}
static char oauth_url[BFSZ];
static char oauth_client_id[BFSZ];
static char oauth_client_secret[BFSZ];
static int oauth_trim;
int sauth_init_oauth(char *url, char *id, char *secret, int trim)
{
	ncpy(oauth_url,url,BFSZ-1);
	ncpy(oauth_client_id,id,BFSZ-1);
	ncpy(oauth_client_secret,secret,BFSZ-1);
	oauth_trim = trim;
	return TRUE;
}
#define HBFSZ 20000
int sauth_check_oauth(char *userdom, char *pass, char *result, int *failed) // failed means the mechanism failed.
{
	// Do the http request...
	char body[BFSZ];
	char big[HBFSZ];
	char user[BFSZ];
	int rlen;
	char *error=NULL;
	char *error_description=NULL;
	char *token_type=NULL;
	cJSON *json=NULL,*j=NULL;
	char *s;
	*failed = TRUE;
	ncpy(user,userdom,BFSZ-1);
	if (oauth_trim) {
		s = strchr(user,'@'); 
		if (s!=NULL) *s = 0;
	}
	lprintf(body,BFSZ-1,"grant_type=password&username=%s&password=%s&client_id=%s&client_secret=%s",user,pass,oauth_client_id,oauth_client_secret);
	printf("send oauth %s\n",body);
	if (!http_post_any("POST",oauth_url,"","",body,big,HBFSZ-1,"",&rlen,"","",NULL)) {
		ncpy(result,big,BFSZ-1);
		imsg("Failed: %s\n",result);
		printf("Failed: %s\n",result);
		goto done;
	}
	printf("Response %d (%s)\n",rlen,big);


/*{
  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
  "scope":"create"
}
*/


	json = cJSON_Parse(big);
	if (json==NULL) {
		printf("json FAILED %s\n",big);
		imsg("json FAILED %s\n",big);
		ncpy(result,"Parse xml failed",BFSZ-1);
		goto done;
	}
	*failed = FALSE;
	error = json_getstr(json,"error"); 
	error_description = json_getstr(json,"error_description"); 
	if (error!=NULL) if (strlen(error)>0) {
		lprintf(result,BFSZ-1,"json response %s %s",error,nullok(error_description));
		printf("Result %s\n",result);
		goto done;
	}
	token_type = json_getstr(json,"token_type"); 
	printf("Token type returned %s\n",token_type);
	printf("We think this is a login with a correct password\n");
	iffree(token_type); iffree(error); iffree(error_description);
	cJSON_Delete(json);
	return TRUE;

done:;
	iffree(token_type); iffree(error); iffree(error_description);
	cJSON_Delete(json);
	return FALSE;
}

int sauth_ex_check_ip(char *userdom, char *pass, char *result, int bfsz, char *ip)
{
	int failed=FALSE;
	char info[BIGBF];
	int ret=FALSE;
	// New generic code, if enabled, check with sauth, and set ex...	
	if ((strlen(spawn_cmd)!=0)|| (strlen(oauth_url)>0)) {
		if ((pass==NULL) || (strlen(pass)==0)) if (!auth_pass_lookup) goto normal;
		ret = ex_check_ip(userdom,pass,result,bfsz,ip);
		if (strncmpnc(result,"-ERR",4)!=0) {
			return ret; // all good.
		}
		imsg("sauth: cache failed so check account %s\n",userdom);
		if (strlen(oauth_url)>0) {
			ret = sauth_check_oauth(userdom,pass,result,&failed);	
		} else {
			ret = sauth_check(userdom,pass,result,&failed);	
		}
		if (failed) {
			imsg("sauth: failed %s\n",result);}
		else {
			if (strncmpnc(result,"+OK",3)==0) {
				char *rest="";
				imsg("sauth: found valid account in sauth module, so updating password for %s\n",userdom);
				ex_lookup(userdom,NULL,info,BIGBF-1);
				if (*info=='+') {  // BUG FIX DURING 64BIT PORTING...
					// append other info to the set command...
					rest = sauth_rest(info);
					if (rest==NULL) rest = "";
				}
				ex_set(userdom,pass,rest,result,bfsz);
				imsg("sauth: updated user %s %s\n",userdom,result);
			}
		}
	}
normal:
	return ex_check_ip(userdom,pass,result,bfsz,ip);
}


int sauth_check(char *user, char *pass, char *result, int *failed)
{
	int ret;
	char bf[BFSZ];
	int done=FALSE;
	char xpass[BFSZ];
	time_t start = time(NULL);
	pass = sauth_encode_spaces(xpass,pass);
	*failed = FALSE;
	if (lib_strlen(user)>98) {
		lprintf(result,100,"-ERR invalid long username");
		return FALSE;
	}
	if (pass && (!pass[0] || strspn(pass," \t")==lib_strlen(pass))) {
		lprintf(result,100,"-ERR no password entered");
		return FALSE;
	}
	if (strchr(user,' ')!=NULL) {
		lprintf(result,100,"-ERR %.100s username cannot contain spaces",user);
		return FALSE;
	}
	if ((pass==NULL) || (strlen(pass)==0)) {
		sprintf(bf,"lookup %.100s\n",user);
	}
	else {
		sprintf(bf,"check %.100s %.100s\n",user,pass);
	}

	ret = sauth_do_cmd(bf,result,BFSZ-1);
	if (!ret) *failed = TRUE;
	if (strstrnc(result,"-dead")!=NULL) *failed = TRUE;
	return ret;
}
char *sauth_encode_spaces(char *pass, char *in)
{
	char *s;
	char *out=pass;
	if (in==NULL) return NULL;
	ncpy(pass,in,BFSZ-1);
	for (s=in;*s!=0;s++) {
		if (out-pass>BFSZ-10) break;
		if (*s==' ') {*out++ = '\\'; *out++ = '_';}
		else if (*s=='\\') {*out++ = '\\'; *out++ = *s;}
		else *out++ = *s; 
	}
	*out++ = 0;
	return pass;
}
