//go:build !dev
// +build !dev

package locals

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"syncscan-go/locals/gosodium/cryptobox"
	"time"

	"github.com/dabory/abango-rest"
	e "github.com/dabory/abango-rest/etc"
	"github.com/go-xorm/xorm"
	"github.com/microcosm-cc/bluemonday"
)

const (
	// Tpf string = "dbr_" // TablePrefix
	//QueryComment
	QcWhere        string = "-- @where"
	QcSubWhere     string = "-- @subwhere"
	QcHaving       string = "-- @having"
	QcOrder        string = "-- @order"
	QcLimitOffset  string = "-- @limitoffset"
	QcExtract      string = "-- @extract:"
	QcClosed       string = "-- @closed:"
	QcDelivery     string = "-- @delivery:"
	QcBetweenDates string = "-- @between_dates"
	QcEnd          string = "--" //QueryComment
	//QueryKeyword
	QkWhere string = "\nwhere true "
	// QkWhere  string = "\nwhere 1 "
	QkHaving string = "\nhaving true "
	QkOrder  string = "\norder by "
	QkLimit  string = "\nlimit  "
	QkOffset string = " offset "

	QkTmpOrder string = " order by is_sum desc, t_id asc "
)

type AppApi struct {
	ApiUrl    string
	GateToken string
}

// 0:Sso, 1:Dbu  매우 중요하다.
var gAppApis [2]AppApi

var (
	SQL_DEBUG    bool
	NORMAL_DEBUG bool
)

var BlockNoCnt int

// type GateTokenGetReq struct {
// 	ClientId     string
// 	BeforeBase64 string
// 	AppBase64    string
// }

func (y *SyncController) Init() error {

	var kPairStr string
	if abango.XConfig["IsLocalKeyPair"] != "Yes" {
		keypair, err := GuestKeyPairGet(abango.XConfig["SyncClientId"])
		if err != nil {
			return e.ErrLog(e.FuncRun("03joau0u3qd", e.CurrFuncName()), err)
		}
		kPairStr = keypair
		fmt.Println("RemoteKeypair:", kPairStr)

	} else {
		kPairStr = abango.XConfig["LocalKeyPair"]
		fmt.Println("LocalKeyPair:", kPairStr)
	}

	keyPair, err := base64.StdEncoding.DecodeString(kPairStr)
	if err != nil {
		return e.ErrLog(e.FuncRun("joiuejcsoe: Decryption Failure-1 ", e.CurrFuncName()), err)
	}

	sKey, pKey, err := cryptobox.CryptoBoxGetSecretPublicKeyFrom(keyPair)
	if err != nil {
		return e.ErrLog(e.FuncRun("joiuejcsoe: Decryption Failure-2 ", e.CurrFuncName()), err)
	}

	decodedBytes, err := base64.StdEncoding.DecodeString(abango.XConfig["SyncBB64"])
	if err != nil {
		return e.ErrLog(e.FuncRun("joiuejcsoe: Decryption Failure-3 ", e.CurrFuncName()), err)
	}

	//decrypt 문제는 여기서 memory error 가 난다.
	decryptedBytes, boxRet := cryptobox.CryptoBoxSealOpen(decodedBytes, pKey, sKey)
	if boxRet != 0 {
		return e.ErrLog(e.FuncRun("joiuejcsoe: Decryption Failure-4(Wrong BB64Key) ", e.CurrFuncName()), err)
	}

	vmc := &struct {
		Driver   string
		Host     string
		Port     int
		Username string
		Database string
		Password string
	}{}

	if err := json.Unmarshal(decryptedBytes, vmc); err != nil {
		return e.ErrLog(e.FuncRun("joiuejcsoe: Decryption Failure-5 ", e.CurrFuncName()), err)
	}

	connStr := vmc.Username + ":" + vmc.Password + "@tcp(" + vmc.Host + ":" + e.NumToStr(vmc.Port) + ")/" + vmc.Database

	fmt.Println("connStr:", connStr)
	y.Scb.ConnString = connStr

	if y.Db, err = xorm.NewEngine(abango.XConfig["DbType"], connStr); err != nil {
		return e.ErrLog(e.FuncRun("309upajs3w: DBEngine Open Error ", e.CurrFuncName()), err)
	}

	var connHint string
	strArr := strings.Split(connStr, "@tcp")
	if len(strArr) == 2 {
		connHint = strArr[1]
	} else {
		return e.ErrLog(e.FuncRun("309upajs3w: connString format mismatch: "+strArr[1], e.CurrFuncName()), err)
	}

	y.Db.ShowSQL(false)
	y.Db.SetMaxOpenConns(100)
	y.Db.SetMaxIdleConns(20)
	y.Db.SetConnMaxLifetime(60 * time.Second)
	if _, err := y.Db.IsTableExist("aaa"); err == nil {
		e.OkLog("SyncDB connection in " + connHint)
		return nil
	} else {
		return e.ErrLog(e.FuncRun("93haoy93d: SyncDB connection Fail in "+connHint+": ", e.CurrFuncName()), err)
	}

}

func (t *PageVars) ChkPageVars(chk string) {

	if t.Query == "" {
		e.ChkLog(chk, "Query is empty")
	}

	if t.Fields == "" {
		e.ChkLog(chk, "Fields is empty")
	}

	if t.Asc == "" {
		e.ChkLog(chk, "Asc is empty")
	}

	if t.Desc == "" {
		e.ChkLog(chk, "Desc is empty")
	}

	if t.Limit == 0 {
		e.ChkLog(chk, "Limit is zero")
	}

	if t.Offset == 0 {
		e.ChkLog(chk, "Offset is zero")
	}

	e.ChkLog(chk+", PageVars value is", *t)

	return
}

type (
	MemoryMap map[string]interface{}

	MapStore struct {
		store MemoryMap
	}
)

func (c *MapStore) Get(key string) interface{} {
	return c.store[key]
}

func (c *MapStore) Set(key string, val interface{}) {
	if c.store == nil {
		c.store = make(MemoryMap)
	}
	c.store[key] = val
}

func DbrPasswd(password string, salt string) string {
	salt16 := DbrSaltBase(salt, 16)
	var passwordBytes = []byte(password)
	var sha256Hasher = sha256.New()

	passwordBytes = append(passwordBytes, salt16...)
	sha256Hasher.Write(passwordBytes)

	var hashedPasswordBytes = sha256Hasher.Sum(nil)
	var base64EncodedPasswordHash = base64.URLEncoding.EncodeToString(hashedPasswordBytes)

	return base64EncodedPasswordHash
}

func DbrHashedIndex(target string) string {
	//!!중요: salt는 16char에서만 작동된다. hash 값은 44 char나오지만 32char로 잘라서 쓴다.
	fmt.Println("hash_full_length:", DbrPasswd(target, "$$hashed_index$$"))
	return DbrPasswd(target, "$$hashed_index$$")[0:32]
}

func DbrCompare(hashedPassword, currPassword string, salt string) bool {
	// fmt.Println("salt:", salt)
	// fmt.Println("currPassword:", currPassword)
	var currPasswordHash = DbrPasswd(currPassword, salt)
	// fmt.Println("currPasswordHash:", currPasswordHash)
	// fmt.Println("hashedPassword:", hashedPassword)
	return hashedPassword == currPasswordHash
}

func DbrSaltBase(salt string, saltSize int) []byte { //어떤 사이즈라도 16byte의 Base64로 변경
	tmp := []byte(salt)
	salt64 := base64.StdEncoding.EncodeToString(tmp)
	return []byte(salt64[4 : saltSize+4])
}

func HasPickActPage(uri string, table string) bool {
	if table == "member" {
		if uri == "/"+table+"-pick" || uri == "/"+table+"-act" || uri == "/"+table+"-page" || uri == "/"+table+"-secured-pick" || uri == "/"+table+"-secured-page" || uri == "/"+table+"-secured-act" {
			return true
		} else {
			return false
		}
	} else {
		if uri == "/"+table+"-pick" || uri == "/"+table+"-act" || uri == "/"+table+"-page" {
			return true
		} else {
			return false
		}
	}
}

// func ByteIndex(ba *[]byte, bt byte, opt int) int {
// 	if opt == 0 { //normal
// 		for i := 0; i < len(*ba); i++ {
// 			if (*ba)[i] == bt {
// 				return i
// 			}
// 		}
// 	} else if opt == 1 { //rerverse
// 		for i := len(*ba) - 1; i > 0; i-- {
// 			if (*ba)[i] == bt {
// 				return i
// 			}
// 		}
// 	}
// 	return -1
// }

func LastQry(qry xorm.Session) string {
	ret, _ := qry.LastSQL()
	fmt.Println("\n" + ret + "\n")
	return ret
}

func ShowQry(qry xorm.Session, qryName string) string {
	if SQL_DEBUG {
		ret, _ := qry.LastSQL()
		return e.LogStr("", "ShowQry===["+qryName+"]==="+"\n[ "+ret+" ]\n")
	}
	return ""
}

func ShowSql(sqlStr string, qryName string) string {
	if SQL_DEBUG {
		return e.LogStr("", "ShowSql===["+qryName+"]==="+"\n[ "+sqlStr+" ]\n")
	}
	return ""
}

// func ShowDebug(debugStr string, index string) string {
// 	if NORMAL_DEBUG {
// 		return e.LogStr("", "ShowDebug===["+index+"]==="+"\n[ "+debugStr+" ]\n")
// 	}
// 	return ""
// }

func QryDirName(qryName string) (string, string) {
	if !strings.Contains(qryName, "::") {
		return "queries/", qryName
	} else {
		q := strings.Split(qryName, "::")
		return "queries/themes/" + q[0] + "/", q[1]
	}
}

func StripHtml(cont string, max int) string {
	p := bluemonday.StripTagsPolicy()
	s := p.Sanitize(cont)
	if len(s) > max {
		return string([]rune(s)[:max])
	} else {
		return s
	}
}

func Sanitize(cont string) string {
	p := bluemonday.UGCPolicy()
	return p.Sanitize(cont)
}

func AddStrIfNotExist(s *string, target string) {
	if !strings.Contains(*s, target) {
		*s += target
	}
}

func HttpResponseSimplePost(method string, apiurl string, jsBytes []byte) (retbody []byte, retsta int, reterr error) {

	response, err := http.Post(apiurl, "application/json", bytes.NewBuffer(jsBytes))
	if err != nil {
		return nil, 0, errors.New(e.FuncRunErr("65rfg0csdew", "The HTTP request failed with error "+e.CurrFuncName()+err.Error()))
	} else {
		retbody, err = ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, 0, errors.New(e.FuncRunErr("kjda89382", "ReadAll error "+e.CurrFuncName()+err.Error()))
		}
	}
	return retbody, response.StatusCode, nil
}

func HttpResponseWithGt(method string, apiurl string, jsBytes []byte, gateToken string) (retbody []byte, retsta int, reterr error) {
	reader := bytes.NewBuffer(jsBytes)
	req, err := http.NewRequest(method, apiurl, reader)
	if err != nil {
		return nil, 909, e.ErrLog(e.FuncRun("xcawrq3276fa-http.NewRequest "+apiurl, e.CurrFuncName()), err)
	}

	req.Header.Add("RemoteIp", "localhost")
	req.Header.Add("Referer", "http://localhost")
	req.Header.Add("GateToken", gateToken)

	req.Body = ioutil.NopCloser(bytes.NewReader(jsBytes))

	// Client객체에서 Request 실행
	client := &http.Client{
		Timeout: time.Second * 20, //Otherwirse, it can cause crash without this line. Must Must.
	} // Normal is 10 but extend 20 on 1 Dec 2018

	// fmt.Println(reflect.TypeOf(respo))
	resp, err := client.Do(req)
	if err != nil {
		return nil, 909, e.ErrLog(e.FuncRun("wewer2354e-client.Do "+apiurl, e.CurrFuncName()), err)
	}
	defer resp.Body.Close()

	byteRtn, _ := ioutil.ReadAll(resp.Body)
	return byteRtn, resp.StatusCode, nil
}

func GuestGateTokenGet(appType int, pivotUrl string, ab64 string) (string, string, error) {

	if gAppApis[appType].GateToken == "" {
		req := &struct {
			AppType   string
			AppBase64 string
		}{
			AppType:   "Main",
			AppBase64: ab64,
		}

		bodyBytes, _ := json.Marshal(req)
		apiUrl := pivotUrl + "/gate-token-get"
		msgBytes, staInt, err := HttpResponseSimplePost("POST", apiUrl, bodyBytes)
		fmt.Println("apiUrl:", apiUrl)
		fmt.Println("bodyBytes:", string(bodyBytes))

		if err != nil {
			return "", "", e.ErrLog(e.FuncRun("45425fd34sd-The HTTP request "+apiUrl, e.CurrFuncName()), err)
		}

		if staInt != 200 {
			return "", "", errors.New(e.FuncRun("87ty344ra3-Request Fail "+string(msgBytes), e.CurrFuncName()))
		}

		ret := &struct {
			ApiUrl    string
			GateToken string
		}{}

		if err := json.Unmarshal(msgBytes, ret); err != nil {
			return "", "", e.ErrLog(e.FuncRun("45425fd34sd-Json Format "+apiUrl, e.CurrFuncName()), err)
		}
		gAppApis[appType].ApiUrl = ret.ApiUrl
		gAppApis[appType].GateToken = ret.GateToken

	} else {
		fmt.Println("GateToken already is in the ARRAY")
	}

	// fmt.Println("gSsoApiUrl:", gSsoApiUrl)
	// fmt.Println("gSsoGateToken:", gSsoGateToken)

	return gAppApis[appType].ApiUrl, gAppApis[appType].GateToken, nil
}

func GuestEncryptGet(code string) (string, string, error) {

	appType := 1 //Dbupdate
	req := &struct {
		EncryptCode string
	}{
		EncryptCode: code,
	}

	// 0:Sso, 1:Dbu
	pivotUrl, gateToken, err := GuestGateTokenGet(appType, abango.XConfig["DbuConnString"], abango.XConfig["DbuAppBase64"])
	if err != nil {
		return "", "", e.ErrLog(e.FuncRun("23rfsr3qrase", e.CurrFuncName()), err)
	}

	ret := &struct {
		EncrypteKey string
		SaltKey     string
	}{}

	bodyBytes, _ := json.Marshal(req)
	apiUrl := pivotUrl + "/encrypt-get"
	msgBytes, staInt, err := HttpResponseWithGt("POST", apiUrl, bodyBytes, gateToken)
	if err != nil {
		return "", "", e.ErrLog(e.FuncRun("1eadwrq34dxc-The HTTP request "+apiUrl, e.CurrFuncName()), err)
	}
	if staInt != 200 {
		gAppApis[appType].GateToken = "" // GateToke Expired 경우 Clear 한다.
		return "", "", errors.New(e.FuncRun("45faw3rfw-Request Fail "+string(msgBytes), e.CurrFuncName()))
	}

	if err := json.Unmarshal(msgBytes, ret); err != nil {
		return "", "", e.ErrLog(e.FuncRun("6756er345r3", e.CurrFuncName()), err)
	}

	return ret.EncrypteKey, ret.SaltKey, nil
}

func GuestAvailDbupdateGet(lastno string, isskipup string) ([]byte, error) {

	appType := 1 //Dbupdate
	req := &struct {
		DbupdateNo   string
		IsSkipUpdate string
	}{
		DbupdateNo:   lastno,
		IsSkipUpdate: isskipup,
	}

	// 0:Sso, 1:Dbu
	pivotUrl, gateToken, err := GuestGateTokenGet(appType, abango.XConfig["DbuConnString"], abango.XConfig["DbuAppBase64"])
	if err != nil {
		return nil, e.ErrLog(e.FuncRun("23rfsr3qrase", e.CurrFuncName()), err)
	}

	bodyBytes, _ := json.Marshal(req)
	apiUrl := pivotUrl + "/avail-dbupdate-get"
	msgBytes, staInt, err := HttpResponseWithGt("POST", apiUrl, bodyBytes, gateToken)
	if err != nil {
		return nil, e.ErrLog(e.FuncRun("1eadwrq34dxc-The HTTP request "+apiUrl, e.CurrFuncName()), err)
	}
	if staInt != 200 {
		gAppApis[appType].GateToken = "" // GateToke Expired 경우 Clear 한다.
		return nil, errors.New(e.FuncRun("0asfweijcvs-Request Fail "+string(msgBytes), e.CurrFuncName()))
	}
	return msgBytes, nil
}

func GuestKeyPairGet(clientId string) (string, error) {
	appType := 0 //Dbupdate
	req := &struct {
		ClientId string
	}{
		ClientId: clientId,
	}
	fmt.Println("clientId:", clientId)
	// 0:Sso, 1:Dbu
	pivotUrl, gateToken, err := GuestGateTokenGet(appType, abango.XConfig["SsoConnString"], abango.XConfig["SsoAppBase64"])
	if err != nil {
		return "", e.ErrLog(e.FuncRun("23rfsr3qrase", e.CurrFuncName()), err)
	}

	ret := &struct {
		KeyPair string
	}{}

	bodyBytes, _ := json.Marshal(req)
	apiUrl := pivotUrl + "/key-pair-get"
	msgBytes, staInt, err := HttpResponseWithGt("POST", apiUrl, bodyBytes, gateToken)
	if err != nil {
		return "", e.ErrLog(e.FuncRun("1eadwrq34dxc-The HTTP request "+apiUrl, e.CurrFuncName()), err)
	}
	if staInt != 200 {
		gAppApis[appType].GateToken = "" // GateToke Expired 경우 Clear 한다.
		return "", errors.New(e.FuncRun("09665gsre3-Request Fail "+string(msgBytes), e.CurrFuncName()))
	}

	if err := json.Unmarshal(msgBytes, ret); err != nil {
		return "", e.ErrLog(e.FuncRun("9074tf32de", e.CurrFuncName()), err)
	}

	return ret.KeyPair, nil
}

func OneRowQuery(y *abango.Controller, sql string) (c1 string, c2 string, c3 string, err error) {
	page, err := y.Db.Query(sql)
	if err != nil {
		return "", "", "", errors.New(e.FuncRunErr("0hjnboisqow", e.CurrFuncName()+err.Error()))
	}
	if len(page) > 1 {
		return "", "", "", errors.New(e.FuncRunErr("0k1dt6j3d", e.CurrFuncName()+"Row Count > 1 "))
	}

	for _, row := range page {
		c1 = string(row["c1"])
		c2 = string(row["c2"])
		c3 = string(row["c3"])
	}
	return
}

func IsFirstOrderGet(y *abango.Controller, buyerId int) string {
	qry := fmt.Sprintf("select count(*) as c1 from dbr_sorder where buyer_id = %d ", buyerId)
	ordCnt, _, _, _ := OneRowQuery(y, qry)
	if ordCnt == "1" {
		return "1"
	} else {
		return "0"
	}
}

func TimeFormatGet(format string) string {
	rtn := ""
	if format == "" {
		rtn = "060102"
	} else if format == "YYMMDD" {
		rtn = "060102"
	} else if format == "YYYYMMDD" {
		rtn = "20060102"
	} else if format == "YY-MM-DD" {
		rtn = "06-01-02"
	} else if format == "YY.MM.DD" {
		rtn = "06.01.02"
	} else if format == "YYMM" {
		rtn = "0601"
	} else if format == "YY" {
		rtn = "06"
	}
	return rtn
}
