text:
/* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2019 Intel Corporation
 */

package ngcnef

import (
	"context"
	"errors"
	"strconv"
	"strings"
)

const correlationIDOffset = 20
const subNotFound string = "Subscription Not Found"

//NEF context data
type nefData struct {
	ctx                context.Context
	afCount            int
	locationURLPrefix  string
	pcfClient          PcfPolicyAuthorization
	udrClient          UdrInfluenceData
	corrID             uint
	afs                map[string]*afData
	upfNotificationURL URI
}

//NEFSBGetFn is the callback for SB API
type NEFSBGetFn func(subData *afSubscription, nefCtx *nefContext) (
	sub TrafficInfluSub, rsp nefSBRspData, err error)

//NEFSBPutFn is the callback for SB API
type NEFSBPutFn func(subData *afSubscription, nefCtx *nefContext,
	ti TrafficInfluSub) (rsp nefSBRspData, err error)

//NEFSBPatchFn is the callback for SB API
type NEFSBPatchFn func(subData *afSubscription, nefCtx *nefContext,
	tisp TrafficInfluSubPatch) (rsp nefSBRspData, err error)

//NEFSBDeleteFn is the callback for SB API
type NEFSBDeleteFn func(subData *afSubscription, nefCtx *nefContext) (
	rsp nefSBRspData, err error)

//PCF Subscription data
type afSubscription struct {
	subid string
	ti    TrafficInfluSub

	//Applicable in case of single UE case only
	appSessionID AppSessionID

	//Applicable in case of Multiple UE only
	iid                       InfluenceID
	NotifCorreID              string
	afNotificationDestination Link
	NEFSBGet                  NEFSBGetFn
	NEFSBPut                  NEFSBPutFn
	NEFSBPatch                NEFSBPatchFn
	NEFSBDelete               NEFSBDeleteFn
}

//AF data
type afData struct {
	afID       string
	subIDnum   int
	maxSubSupp int
	subs       map[string]*afSubscription
}

type nefSBRspData struct {
	errorCode int
	pd        ProblemDetails
}

//Creates a AF instance
func (af *afData) afCreate(nefCtx *nefContext, afID string) error {

	//Validate afid ??

	af.afID = afID
	af.subIDnum = nefCtx.cfg.SubStartID //Start Number
	af.maxSubSupp = nefCtx.cfg.MaxSubSupport
	af.subs = make(map[string]*afSubscription)
	return nil
}

//Creates a new subscription
func (af *afData) afAddSubscription(nefCtx *nefContext,
	ti TrafficInfluSub) (loc string, rsp nefSBRspData, err error) {

	/*Check if max subscription reached */
	if len(af.subs) >= nefCtx.cfg.MaxSubSupport {

		rsp.errorCode = 400
		rsp.pd.Title = "MAX Subscription Reached"
		return "", rsp, errors.New("MAX SUBS Created")
	}

	//Generate a unique subscription ID string
	subIDStr := strconv.Itoa(af.subIDnum)
	af.subIDnum++

	//Create Subscription data
	afsub := afSubscription{subid: subIDStr, ti: ti, appSessionID: "",
		NotifCorreID: "", iid: ""}

	if len(ti.Gpsi) > 0 || len(ti.Ipv4Addr) > 0 || len(ti.Ipv6Addr) > 0 {

		//Applicable to single UE, PCF case

		rsp, err = nefSBPCFPost(&afsub, nefCtx, ti)

		if err != nil {

			//Return error failed to create subscription
			return "", rsp, err
		}
		//Store Notification Destination URI
		afsub.afNotificationDestination = ti.NotificationDestination
		afsub.NEFSBGet = nefSBPCFGet
		afsub.NEFSBPut = nefSBPCFPut
		afsub.NEFSBPatch = nefSBPCFPatch
		afsub.NEFSBDelete = nefSBPCFDelete

	} else if len(ti.ExternalGroupID) > 0 || ti.AnyUeInd {

		//Applicable to Any UE, UDR case

		rsp, err = nefSBUDRPost(&afsub, nefCtx, ti)

		if err != nil {

			//Return error
			return "", rsp, err
		}
		//Store Notification Destination URI
		afsub.afNotificationDestination = ti.NotificationDestination

		afsub.NEFSBGet = nefSBUDRGet
		afsub.NEFSBPut = nefSBUDRPut
		afsub.NEFSBPatch = nefSBUDRPatch
		afsub.NEFSBDelete = nefSBUDRDelete

	} else {
		//Invalid case. Return Error
		rsp.errorCode = 400
		rsp.pd.Title = "Invalid Request"
		return "", rsp, errors.New("Invalid AF Request")
	}

	//Link the subscription with the AF
	af.subs[subIDStr] = &afsub

	//Create Location URI
	loc = nefCtx.nef.locationURLPrefix + af.afID + "/subscriptions/" +
		subIDStr

	afsub.ti.Self = Link(loc)

	log.Infoln(" NEW AF Subscription added " + subIDStr)

	return loc, rsp, nil
}

func (af *afData) afUpdateSubscription(nefCtx *nefContext, subID string,
	ti TrafficInfluSub) (rsp nefSBRspData, updtTI TrafficInfluSub, err error) {

	sub, ok := af.subs[subID]

	if !ok {
		rsp.errorCode = 400
		rsp.pd.Title = subNotFound

		return rsp, updtTI, errors.New(subNotFound)
	}

	rsp, err = sub.NEFSBPut(sub, nefCtx, ti)

	if err != nil {
		log.Err("Failed to Update Subscription")
		return rsp, updtTI, err
	}

	updtTI = ti
	updtTI.Self = sub.ti.Self
	sub.ti = updtTI

	log.Infoln("Update Subscription Successful")
	return rsp, updtTI, err
}

func updateTiFromTisp(ti *TrafficInfluSub, tisp TrafficInfluSubPatch) {

	if tisp.AppReloInd != ti.AppReloInd {
		log.Infoln("Updating AppReloInd...")
		ti.AppReloInd = tisp.AppReloInd
	}

	if tisp.TrafficFilters != nil {
		log.Infoln("Updating TrafficFilters...")
		ti.TrafficFilters = tisp.TrafficFilters
	}

	if tisp.EthTrafficFilters != nil {
		log.Infoln("Updating EthTrafficFilters")
		ti.EthTrafficFilters = tisp.EthTrafficFilters
	}

	if tisp.TrafficRoutes != nil {
		log.Infoln("Updating TrafficRoutes")
		ti.TrafficRoutes = tisp.TrafficRoutes
	}
	if tisp.TempValidities != nil {
		log.Infoln("Updating TempValidities")
		ti.TempValidities = tisp.TempValidities
	}
	if tisp.ValidGeoZoneIDs != nil {
		log.Infoln("Updating ValidGeoZoneIDs")
		ti.ValidGeoZoneIDs = tisp.ValidGeoZoneIDs
	}

}

func (af *afData) afPartialUpdateSubscription(nefCtx *nefContext, subID string,
	tisp TrafficInfluSubPatch) (rsp nefSBRspData, ti TrafficInfluSub,
	err error) {

	sub, ok := af.subs[subID]

	if !ok {
		rsp.errorCode = 400
		rsp.pd.Title = subNotFound
		return rsp, ti, errors.New(subNotFound)
	}

	rsp, err = sub.NEFSBPatch(sub, nefCtx, tisp)

	if err != nil {
		log.Err("Failed to Patch Subscription")
		return rsp, ti, err
	}
	updateTiFromTisp(&sub.ti, tisp)

	return rsp, sub.ti, err

}

func (af *afData) afGetSubscription(nefCtx *nefContext,
	subID string) (rsp nefSBRspData, ti TrafficInfluSub, err error) {

	sub, ok := af.subs[subID]

	if !ok {
		rsp.errorCode = 404
		rsp.pd.Title = subNotFound
		return rsp, ti, errors.New(subNotFound)
	}

	//ti, rsp, err = sub.NEFSBGet(sub, nefCtx)

	/*
		if err != nil {
			log.Infoln("Failed to Get Subscription")
			return rsp, ti, err
		}

		return rsp, ti, err
	*/

	//Return locally
	return rsp, sub.ti, err
}

func (af *afData) afGetSubscriptionList(nefCtx *nefContext) (rsp nefSBRspData,
	subsList []TrafficInfluSub, err error) {

	var ti TrafficInfluSub

	if len(af.subs) > 0 {

		for key := range af.subs {

			rsp, ti, err = af.afGetSubscription(nefCtx, key)

			if err != nil {
				return rsp, subsList, err
			}
			subsList = append(subsList, ti)
		}
	}
	return rsp, subsList, err
}

func (af *afData) afDeleteSubscription(nefCtx *nefContext,
	subID string) (rsp nefSBRspData, err error) {

	//Check if AF is already present
	sub, ok := af.subs[subID]

	if !ok {
		rsp.errorCode = 404
		rsp.pd.Title = subNotFound
		return rsp, errors.New(subNotFound)
	}

	rsp, err = sub.NEFSBDelete(sub, nefCtx)

	if err != nil {
		log.Err("Failed to Delete Subscription")
		return rsp, err
	}

	//Delete local entry in map
	delete(af.subs, subID)
	//af.subIDnum--

	return rsp, err
}

func (af *afData) afGetSubCount() (afCount int) {

	return len(af.subs)
}

/* unused function
func (af *afData) afDestroy(afid string) error {

	//Todo delete all subscriptions, needed in go ??
	//Needed for gracefully disconnecting
	return errors.New("AF data cleaned")
}
*/

// Generate the notification uri to be provided to PCF/UDR
func getNefNotificationURI(cfg *Config) URI {
	var uri string
	// If http2 port is configured use it else http port
	if cfg.HTTP2Config.Endpoint != "" {
		uri = "https://" + cfg.NefAPIRoot +
			cfg.HTTP2Config.Endpoint
	} else {
		uri = "http://" + cfg.NefAPIRoot +
			cfg.HTTPConfig.Endpoint
	}
	uri += cfg.UpfNotificationResURIPath
	return URI(uri)
}

func getNefLocationURLPrefix(cfg *Config) string {

	var uri string
	// If http2 port is configured use it else http port
	if cfg.HTTP2Config.Endpoint != "" {
		uri = "https://" + cfg.NefAPIRoot +
			cfg.HTTP2Config.Endpoint
	} else {
		uri = "http://" + cfg.NefAPIRoot +
			cfg.HTTPConfig.Endpoint
	}
	uri += cfg.LocationPrefix
	return uri

}

//Initialize the NEF component
func (nef *nefData) nefCreate(ctx context.Context, cfg Config) error {

	nef.ctx = ctx
	nef.afCount = 0
	nef.pcfClient = NewPCFClient(&cfg)
	if nef.pcfClient == nil {
		return errors.New("PCF Client creation failed")
	}
	nef.udrClient = NewUDRClient(&cfg)
	if nef.udrClient == nil {
		return errors.New("PCF Client creation failed")
	}
	nef.afs = make(map[string]*afData)
	nef.corrID = uint(cfg.SubStartID + correlationIDOffset)

	if cfg.NefAPIRoot == "" {
		return errors.New("NefAPIRoot is empty")
	}

	if cfg.LocationPrefix == "" {
		return errors.New("NEF LocationPrefix is empty")
	}

	// Generate the location url prefix
	nef.locationURLPrefix = getNefLocationURLPrefix(&cfg)
	log.Infof("NEF Location URL Prefix :%s", nef.locationURLPrefix)

	// Genereate the notification url
	if cfg.UpfNotificationResURIPath == "" {
		return errors.New("UpfNotificationResURIPath is empty")
	}
	nef.upfNotificationURL = getNefNotificationURI(&cfg)
	log.Infof("SMF UPF Notification URL :%s", nef.upfNotificationURL)
	return nil
}

/*
func NEFInit() error {

	return nef.nefCreate()
}*/

func (nef *nefData) nefAddAf(nefCtx *nefContext, afID string) (af *afData,
	err error) {

	var afe afData

	if len(nef.afs) >= nefCtx.cfg.MaxAFSupport {
		log.Infoln("MAX AF exceeded ")
		return af, errors.New("MAX AF exceeded")
	}

	//Check if AF is already present
	_, ok := nef.afs[afID]

	if ok {
		return nef.afs[afID], errors.New("AF already present")
	}

	//Create a new entry of AF

	_ = afe.afCreate(nefCtx, afID)
	nef.afs[afID] = &afe
	nef.afCount++

	return &afe, nil
}

func (nef *nefData) nefGetAf(afID string) (af *afData, err error) {

	//Check if AF is already present
	afe, ok := nef.afs[afID]

	if ok {
		return afe, nil
	}
	err = errors.New("AF entry not present")
	return afe, err
}

func (nef *nefData) nefDeleteAf(afID string) (err error) {

	//Check if AF is already present
	_, ok := nef.afs[afID]

	if ok {
		delete(nef.afs, afID)
		//nef.afCount--
		return nil
	}

	err = errors.New("AF entry not present")
	return err
}

func (nef *nefData) nefDestroy() {

	// Todo
}

// nefSBPCFPost : This function sends HTTP POST Request to PCF to create Policy
//             Authorization.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
//   - ti: This is Traffic Influence Subscription Data.
// Output Args:
//    - rsp: This is Policy Authorization Create Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBPCFPost(pcfSub *afSubscription, nefCtx *nefContext,
	ti TrafficInfluSub) (rsp nefSBRspData, err error) {

	var appSessID AppSessionID
	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	pcfSub.NotifCorreID = strconv.Itoa(int(nef.corrID))
	nef.corrID++

	appSessCtx := AppSessionContext{}
	pcfPolicyResp := PcfPolicyResponse{}

	//Populating App Session Context Data Req
	appSessCtx.AscReqData.AfAppID = AfAppID(ti.AfAppID)
	appSessCtx.AscReqData.AfRoutReq.AppReloc = ti.AppReloInd

	//Populating UP Path Chnage Subbscription Data in App Session Context
	appSessCtx.AscReqData.AfRoutReq.UpPathChgSub.DnaiChgType = ti.DnaiChgType

	// If http2 port is configured use it else http port
	if nefCtx.cfg.HTTP2Config.Endpoint != "" {
		appSessCtx.AscReqData.AfRoutReq.UpPathChgSub.NotificationURI =
			URI("https://" + nefCtx.cfg.NefAPIRoot +
				nefCtx.cfg.HTTP2Config.Endpoint)
	} else {
		appSessCtx.AscReqData.AfRoutReq.UpPathChgSub.NotificationURI =
			URI("http://" + nefCtx.cfg.NefAPIRoot +
				nefCtx.cfg.HTTPConfig.Endpoint)
	}

	/*+ nefCtx.cfg.UpfNotificationResUriPath*/
	appSessCtx.AscReqData.AfRoutReq.UpPathChgSub.NotifCorreID =
		pcfSub.NotifCorreID

	//Populating Traffic Routes in App Session Context
	appSessCtx.AscReqData.AfRoutReq.RouteToLocs = make([]RouteToLocation,
		len(ti.TrafficRoutes))
	_ = copy(appSessCtx.AscReqData.AfRoutReq.RouteToLocs, ti.TrafficRoutes)

	//Populating Temporal Validity in App Session Context
	appSessCtx.AscReqData.AfRoutReq.TempVals = make([]TemporalValidity,
		len(ti.TempValidities))
	_ = copy(appSessCtx.AscReqData.AfRoutReq.TempVals, ti.TempValidities)

	//Populating Spatial Validity in App Session Context
	_ = getSpatialValidityData(cliCtx, nefCtx,
		&appSessCtx.AscReqData.AfRoutReq.SpVal)

	//Populating IP and Mac Addresses in App Session Context
	appSessCtx.AscReqData.UeIpv4 = ti.Ipv4Addr
	appSessCtx.AscReqData.UeIpv6 = ti.Ipv6Addr
	appSessCtx.AscReqData.UeMac = ti.MacAddr

	//Populating DNN and NW Slice Info and SUPI in App Session Context
	for _, afServIdcounter := range nefCtx.cfg.AfServiceIDs {
		afServiceID := afServIdcounter.(map[string]interface{})
		if 0 == strings.Compare(ti.AfServiceID, afServiceID["id"].(string)) {
			appSessCtx.AscReqData.Dnn = Dnn(afServiceID["dnn"].(string))
			appSessCtx.AscReqData.SliceInfo.Sd = afServiceID["snssai"].(string)
			appSessCtx.AscReqData.SliceInfo.Sst =
				uint8(len(appSessCtx.AscReqData.SliceInfo.Sd))
		}
	}

	//Populating SUPI in App Session Context
	_ = getSupiData(cliCtx, nefCtx, &appSessCtx.AscReqData.Supi)

	appSessID, pcfPolicyResp, err =
		nef.pcfClient.PolicyAuthorizationCreate(cliCtx, appSessCtx)

	if err != nil {
		rsp.errorCode = int(pcfPolicyResp.ResponseCode)
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Create Failure. Response Code: %d",
			rsp.errorCode)
		return rsp, err
	}

	rsp.errorCode = int(pcfPolicyResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Create Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		pcfSub.appSessionID = appSessID
		log.Infof("PCF Policy Authorization Create Success. Response Code: %d",
			rsp.errorCode)
	}
	return rsp, err
}

// nefSBPCFGet : This function sends HTTP GET Request to PCF to fetch Policy
//             Authorization using App Session Context Key.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
// Output Args:
//    - sub: This is Traffic Influence Subscription Data.
//    - rsp: This is Policy Authorization Get Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBPCFGet(pcfSub *afSubscription, nefCtx *nefContext) (
	sub TrafficInfluSub, rsp nefSBRspData, err error) {

	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	pcfPolicyResp, err :=
		nef.pcfClient.PolicyAuthorizationGet(cliCtx, pcfSub.appSessionID)
	if err != nil {
		rsp.errorCode = int(pcfPolicyResp.ResponseCode)
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Get Failure. Response Code: %d",
			rsp.errorCode)
		return sub, rsp, err
	}

	rsp.errorCode = int(pcfPolicyResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Get Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		sub = pcfSub.ti
		log.Infof("PCF Policy Authorization Get Success. Response Code: %d",
			rsp.errorCode)
	}

	return sub, rsp, err
}

// nefSBPCFPut : This function returns error as HTTP PUT Request to PCF is not
//            supported.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
//   - ti: This is Traffic Influence Subscription Data.
// Output Args:
//    - rsp: This is Policy Authorization Put Response Data
//    - error: retruns error .
func nefSBPCFPut(pcfSub *afSubscription, nefCtx *nefContext,
	ti TrafficInfluSub) (rsp nefSBRspData, err error) {
	err = errors.New("PUT Method Not Supported")
	log.Errf("PCF Policy Authorization Put Not Supported")
	return rsp, err
}

// nefSBPCFPatch : This function sends HTTP PATCH Request to PCF to update
//             Policy Authorization using App Session Context Key.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
//   - tisp: This is Traffic Influence Subscription Patch Data.
// Output Args:
//    - rsp: This is Policy Authorization Patch Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBPCFPatch(pcfSub *afSubscription, nefCtx *nefContext,
	tisp TrafficInfluSubPatch) (rsp nefSBRspData, err error) {

	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	appSessCtxUpdtData := AppSessionContextUpdateData{}

	//Populating App Session Context Data Req
	//TODO: appSessCtxUpdtData.AfAppID = ti.AfAppID
	appSessCtxUpdtData.AfRoutReq.AppReloc = tisp.AppReloInd

	//Populating UP Path Chnage Subbscription Data in App Session Context
	appSessCtxUpdtData.AfRoutReq.UpPathChgSub.NotificationURI =
		nefCtx.nef.upfNotificationURL

	appSessCtxUpdtData.AfRoutReq.UpPathChgSub.NotifCorreID =
		pcfSub.NotifCorreID

	//Populating Traffic Routes in App Session Context
	appSessCtxUpdtData.AfRoutReq.RouteToLocs = make([]RouteToLocation,
		len(tisp.TrafficRoutes))
	_ = copy(appSessCtxUpdtData.AfRoutReq.RouteToLocs, tisp.TrafficRoutes)

	//Populating Temporal Validity in App Session Context
	appSessCtxUpdtData.AfRoutReq.TempVals = make([]TemporalValidity,
		len(tisp.TempValidities))
	_ = copy(appSessCtxUpdtData.AfRoutReq.TempVals, tisp.TempValidities)

	//Populating Spatial Validity in App Session Context
	_ = getSpatialValidityData(cliCtx, nefCtx,
		&appSessCtxUpdtData.AfRoutReq.SpVal)

	pcfPolicyResp, err := nef.pcfClient.PolicyAuthorizationUpdate(cliCtx,
		appSessCtxUpdtData, pcfSub.appSessionID)
	if err != nil {
		rsp.errorCode = int(pcfPolicyResp.ResponseCode)
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Update Failure. Response Code: %d",
			rsp.errorCode)
		return rsp, err
	}

	rsp.errorCode = int(pcfPolicyResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Update Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		log.Infof("PCF Policy Authorization Update Success. Response Code: %d",
			rsp.errorCode)
	}

	return rsp, err
}

// nefSBPCFDelete : This function sends HTTP DELETE Request to PCF to delete
// 				    Policy Authorization using App Session Context Key.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
// Output Args:
//    - rsp: This is Policy Authorization Delete Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBPCFDelete(pcfSub *afSubscription, nefCtx *nefContext) (
	rsp nefSBRspData, err error) {

	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	pcfPolicyResp, err :=
		nef.pcfClient.PolicyAuthorizationDelete(cliCtx, pcfSub.appSessionID)
	if err != nil {
		rsp.errorCode = int(pcfPolicyResp.ResponseCode)
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Delete Failure. Response Code: %d",
			rsp.errorCode)
		return rsp, err
	}

	rsp.errorCode = int(pcfPolicyResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if pcfPolicyResp.Pd != nil {
			rsp.pd = *pcfPolicyResp.Pd
		}
		log.Errf("PCF Policy Authorization Delete Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		log.Infof("PCF Policy Authorization Delete Success. Response Code: %d",
			rsp.errorCode)
	}

	return rsp, err
}

// nefSBUDRPost :  HTTP POST Request to UDR is to trigger Put
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
//   - ti: This is Traffic Influence Subscription Data.
// Output Args:
//    - rsp: This is Traffic Influence Data Put Response Data
//    - error: retruns error .
func nefSBUDRPost(udrSub *afSubscription, nefCtx *nefContext,
	ti TrafficInfluSub) (rsp nefSBRspData, err error) {

	rsp, err = nefSBUDRPut(udrSub, nefCtx, ti)
	return rsp, err

}

// nefSBUDRGet : This function sends HTTP GET Request to UDR to fetch
//               Traffic Influence Data.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
// Output Args:
//    - sub: This is Traffic Influence Subscription Data.
//    - rsp: This is Traffic Influence Data Delete Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBUDRGet(udrSub *afSubscription, nefCtx *nefContext) (
	sub TrafficInfluSub, rsp nefSBRspData, err error) {

	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	udrInfluenceResp, err := nef.udrClient.UdrInfluenceDataGet(cliCtx)
	if err != nil {
		rsp.errorCode = int(udrInfluenceResp.ResponseCode)
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Get Failure. Response Code: %d",
			rsp.errorCode)
		return sub, rsp, err
	}

	rsp.errorCode = int(udrInfluenceResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Get Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		sub = udrSub.ti
		log.Infof("UDR Traffic Influence Data Get Success. Response Code: %d",
			rsp.errorCode)
	}

	return sub, rsp, err
}

// nefSBUDRPut : This function sends HTTP PUT Request to UDR to create Traffic
//            Influence Data.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
//   - ti: This is Traffic Influence Subscription Data.
// Output Args:
//    - rsp: This is Traffic Influence Data Create Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBUDRPut(udrSub *afSubscription, nefCtx *nefContext,
	ti TrafficInfluSub) (rsp nefSBRspData, err error) {

	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	trafficInfluData := TrafficInfluData{}

	//Populating Traffic Influence Data
	trafficInfluData.AfAppID = ti.AfAppID

	//Populating DNN and NW Slice Info in Traffic Influence Data
	for _, afServIdcounter := range nefCtx.cfg.AfServiceIDs {
		afServiceID := afServIdcounter.(map[string]interface{})
		if 0 == strings.Compare(ti.AfServiceID, afServiceID["id"].(string)) {
			trafficInfluData.Dnn = Dnn(afServiceID["dnn"].(string))
			trafficInfluData.Snssai.Sd = afServiceID["snssai"].(string)
			trafficInfluData.Snssai.Sst = uint8(len(trafficInfluData.Snssai.Sd))
		}
	}

	trafficInfluData.AppReloInd = ti.AppReloInd
	trafficInfluData.InterGroupID = string(ti.ExternalGroupID)

	//Populating UP Path Chnage Subbscription Data in Traffic Influence Data
	trafficInfluData.UpPathChgNotifURI = nefCtx.nef.upfNotificationURL

	if len(ti.SubscribedEvents) > 0 &&
		0 == strings.Compare(string(ti.SubscribedEvents[0]), "UP_PATH_CHANGE") {
		udrSub.NotifCorreID = strconv.Itoa(int(nef.corrID))
		nef.corrID++
		trafficInfluData.UpPathChgNotifCorreID = udrSub.NotifCorreID
	}

	//Populating Traffic Filters in Traffic Influence Data
	trafficInfluData.TrafficFilters = make([]FlowInfo,
		len(ti.TrafficFilters))
	_ = copy(trafficInfluData.TrafficFilters, ti.TrafficFilters)

	//Populating Eth Traffic Filters in Traffic Influence Data
	trafficInfluData.EthTrafficFilters = make([]EthFlowDescription,
		len(ti.EthTrafficFilters))
	_ = copy(trafficInfluData.EthTrafficFilters, ti.EthTrafficFilters)

	//Populating Traffic Routes in Traffic Influence Data
	trafficInfluData.TrafficRoutes = make([]RouteToLocation,
		len(ti.TrafficRoutes))
	_ = copy(trafficInfluData.TrafficRoutes, ti.TrafficRoutes)

	//Populating Temporal Validity in Traffic Influence Data
	if 0 < len(ti.TempValidities) {
		trafficInfluData.ValidStartTime =
			DateTime(ti.TempValidities[0].StartTime)
		trafficInfluData.ValidEndTime =
			DateTime(ti.TempValidities[0].StopTime)
	}

	//Populating Spatial Validity in Traffic Influence Data
	_ = getNetworkAreaInfo(cliCtx, nefCtx, &trafficInfluData.NwAreaInfo)

	udrInfluenceResp, err := nef.udrClient.UdrInfluenceDataCreate(
		cliCtx, trafficInfluData, udrSub.iid)
	if err != nil {
		rsp.errorCode = int(udrInfluenceResp.ResponseCode)
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Put Failure. Response Code: %d",
			rsp.errorCode)
		return rsp, err
	}

	rsp.errorCode = int(udrInfluenceResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Put Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		log.Infof("UDR Traffic Influence Data Put Success. Response Code: %d",
			rsp.errorCode)
	}
	return rsp, err
}

// nefSBUDRPatch : This function sends HTTP PATCH Request to UDR to update
//            Traffic Influence Data.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
//   - tisp: This is Traffic Influence Subscription Patch Data.
// Output Args:
//    - rsp: This is Traffic Influence Data Update Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBUDRPatch(udrSub *afSubscription, nefCtx *nefContext,
	tisp TrafficInfluSubPatch) (rsp nefSBRspData, err error) {

	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	trafficInfluDataPatch := TrafficInfluDataPatch{}

	trafficInfluDataPatch.AppReloInd = tisp.AppReloInd

	//Populating Traffic Filters in Traffic Influence Data
	trafficInfluDataPatch.TrafficFilters = make([]FlowInfo,
		len(tisp.TrafficFilters))
	_ = copy(trafficInfluDataPatch.TrafficFilters, tisp.TrafficFilters)

	//Populating Eth Traffic Filters in Traffic Influence Data
	trafficInfluDataPatch.EthTrafficFilters = make([]EthFlowDescription,
		len(tisp.EthTrafficFilters))
	_ = copy(trafficInfluDataPatch.EthTrafficFilters, tisp.EthTrafficFilters)

	//Populating Traffic Routes in Traffic Influence Data
	trafficInfluDataPatch.TrafficRoutes = make([]RouteToLocation,
		len(tisp.TrafficRoutes))
	_ = copy(trafficInfluDataPatch.TrafficRoutes, tisp.TrafficRoutes)

	//Populating Temporal Validity in Traffic Influence Data
	if 0 < len(tisp.TempValidities) {
		trafficInfluDataPatch.ValidStartTime =
			DateTime(tisp.TempValidities[0].StartTime)
		trafficInfluDataPatch.ValidEndTime =
			DateTime(tisp.TempValidities[0].StopTime)
	}

	udrInfluenceResp, err := nef.udrClient.UdrInfluenceDataUpdate(
		cliCtx, trafficInfluDataPatch, udrSub.iid)
	if err != nil {
		rsp.errorCode = int(udrInfluenceResp.ResponseCode)
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Update Failure. Response Code: %d",
			rsp.errorCode)
		return rsp, err
	}

	rsp.errorCode = int(udrInfluenceResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Update Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		log.Infof("UDR Traffic Influence Data Update Success.Response Code: %d",
			rsp.errorCode)
	}
	return rsp, err
}

// nefSBUDRDelete : This function sends HTTP DELETE Request to UDR to delete
//               Traffic Influence Data.
// Input Args:
//   - nefCtx: This is NEF Module Context. This contains the NEF Module Data.
// Output Args:
//    - rsp: This is Traffic Influence Data Delete Response Data
//    - error: retruns error in case there is failure happened in sending the
//             request or any failure response is received.
func nefSBUDRDelete(udrSub *afSubscription, nefCtx *nefContext) (
	rsp nefSBRspData, err error) {

	nef := &nefCtx.nef

	cliCtx, cancel := context.WithCancel(nef.ctx)
	defer cancel()

	udrInfluenceResp, err := nef.udrClient.UdrInfluenceDataDelete(cliCtx,
		udrSub.iid)
	if err != nil {
		rsp.errorCode = int(udrInfluenceResp.ResponseCode)
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Delete Failure. Response Code: %d",
			rsp.errorCode)
		return rsp, err
	}

	rsp.errorCode = int(udrInfluenceResp.ResponseCode)
	if rsp.errorCode >= 300 && rsp.errorCode < 700 {
		if udrInfluenceResp.Pd != nil {
			rsp.pd = *udrInfluenceResp.Pd
		}
		log.Errf("UDR Traffic Influence Data Delete Failure. Response Code: %d",
			rsp.errorCode)
	} else {
		log.Infof("UDR Traffic Influence Data Delete Success."+
			"Response Code: %d", rsp.errorCode)
	}

	return rsp, err
}

func getSpatialValidityData(cliCtx context.Context, nefCtx *nefContext,
	spVal *SpatialValidity) error {
	_ = cliCtx
	_ = nefCtx

	spVal.PresenceInfoList.PraID = "PRA_01"
	spVal.PresenceInfoList.PresenceState = "IN_AREA"
	spVal.PresenceInfoList.EcgiList = make([]Ecgi, 1)
	spVal.PresenceInfoList.EcgiList[0].EutraCellID = "EUTRACELL_01"
	spVal.PresenceInfoList.EcgiList[0].PlmnID.Mcc = "634"
	spVal.PresenceInfoList.EcgiList[0].PlmnID.Mnc = "635"
	spVal.PresenceInfoList.NcgiList = make([]Ncgi, 1)
	spVal.PresenceInfoList.NcgiList[0].NrCellID = "NRCELL_01"
	spVal.PresenceInfoList.NcgiList[0].PlmnID.Mcc = "834"
	spVal.PresenceInfoList.NcgiList[0].PlmnID.Mnc = "835"
	spVal.PresenceInfoList.GlobalRanNodeIDList = make([]GlobalRanNodeID, 1)
	spVal.PresenceInfoList.GlobalRanNodeIDList[0].PlmnID.Mcc = "934"
	spVal.PresenceInfoList.GlobalRanNodeIDList[0].PlmnID.Mnc = "935"
	spVal.PresenceInfoList.GlobalRanNodeIDList[0].N3IwfID = "IWF_01"
	spVal.PresenceInfoList.GlobalRanNodeIDList[0].GNbID.BitLength = 48
	spVal.PresenceInfoList.GlobalRanNodeIDList[0].GNbID.GNBValue = "GNB_01"
	spVal.PresenceInfoList.GlobalRanNodeIDList[0].NgeNbID = "NB_01"

	return nil
}

func getSupiData(cliCtx context.Context, nefCtx *nefContext,
	supi *Supi) error {
	_ = cliCtx
	_ = nefCtx
	*supi = "imsi-8"
	return nil
}

func getNetworkAreaInfo(cliCtx context.Context, nefCtx *nefContext,
	nwAreaInfo *NetworkAreaInfo) error {
	_ = cliCtx
	_ = nefCtx

	nwAreaInfo.Ecgis = make([]Ecgi, 1)
	nwAreaInfo.Ecgis[0].EutraCellID = "EUTRACELL_01"
	nwAreaInfo.Ecgis[0].PlmnID.Mcc = "634"
	nwAreaInfo.Ecgis[0].PlmnID.Mnc = "635"
	nwAreaInfo.Ncgis = make([]Ncgi, 1)
	nwAreaInfo.Ncgis[0].NrCellID = "NRCELL_01"
	nwAreaInfo.Ncgis[0].PlmnID.Mcc = "834"
	nwAreaInfo.Ncgis[0].PlmnID.Mnc = "835"
	nwAreaInfo.GRanNodeIds = make([]GlobalRanNodeID, 1)
	nwAreaInfo.GRanNodeIds[0].PlmnID.Mcc = "934"
	nwAreaInfo.GRanNodeIds[0].PlmnID.Mnc = "935"
	nwAreaInfo.GRanNodeIds[0].N3IwfID = "IWF_01"
	nwAreaInfo.GRanNodeIds[0].GNbID.BitLength = 48
	nwAreaInfo.GRanNodeIds[0].GNbID.GNBValue = "GNB_01"
	nwAreaInfo.GRanNodeIds[0].NgeNbID = "NB_01"
	nwAreaInfo.Tais = make([]Tai, 1)
	nwAreaInfo.Tais[0].PlmnID.Mcc = "734"
	nwAreaInfo.Tais[0].PlmnID.Mnc = "735"
	nwAreaInfo.Tais[0].Tac = "TAC_01"

	return nil
}

gha_language: Go

extension: go


text:
// Package steam is wrapper for Steam Web API.
package steam

import (
	"encoding/json"
	"fmt"
	"net/url"

	"github.com/parnurzeal/gorequest"
)

// Steam is Steam Web API client.
type Steam struct {
	request *gorequest.SuperAgent
	apiKey  string
}

// PlayerSummaries represents GetPlayerSummaries response.
type PlayerSummaries struct {
	Response PlayerSummariesResponse `json:"response"`
}

// PlayerSummariesResponse represents GetPlayerSummaries response wrapped PlayerSummary.
type PlayerSummariesResponse struct {
	Players []PlayerSummariesResponsePlayers `json:"players"`
}

// PlayerSummariesResponsePlayers represents PlayerSummary.
type PlayerSummariesResponsePlayers struct {
	SteamID                  string `json:"steamid"`
	CommunityVisibilityState int    `json:"communityvisibilitystate"`
	ProfileState             int    `json:"profilestate"`
	PersonaName              string `json:"personaname"`
	LastLogoff               int    `json:"lastlogoff"`
	ProfileURL               string `json:"profileurl"`
	Avatar                   string `json:"avatar"`
	AvatarMedium             string `json:"avatarmedium"`
	AvatarFull               string `json:"avatarfull"`
	PersonaState             int    `json:"personastate"`
	RealName                 string `json:"realname"`
	PrimaryClanID            string `json:"primaryclanid"`
	TimeCreated              int    `json:"timecreated"`
	PersonaStateFlags        int    `json:"personastateflags"`
	LocCountryCode           string `json:"loccountrycode"`
	LocStateCode             string `json:"locstatecode"`
	LocCityID                int    `json:"loccityid"`
}

// OwnedGames represents GetOwnedGames response.
type OwnedGames struct {
	Response OwnedGamesResponse `json:"response"`
}

// OwnedGamesResponse represents GetOwnedGames response wrapped Games.
type OwnedGamesResponse struct {
	GameCount int                       `json:"game_count"`
	Games     []OwnedGamesResponseGames `json:"games"`
}

// OwnedGamesResponseGames represents Game.
type OwnedGamesResponseGames struct {
	AppID           int `json:"appid"`
	PlaytimeForever int `json:"playtime_forever"`
	Playtime2Weeks  int `json:"playtime_2weeks,omitempty"`
}

// NewSteam creates a new Steam Web API client.
func NewSteam(apiKey string) *Steam {
	return &Steam{
		request: gorequest.New(),
		apiKey:  apiKey,
	}
}

// ParsePlayerSummaries parses GetPlayerSummaries response
func ParsePlayerSummaries(resp *[]byte) (*PlayerSummaries, error) {
	p := PlayerSummaries{}
	if err := json.Unmarshal(*resp, &p); err != nil {
		return nil, err
	}
	return &p, nil
}

// ParseOwnedGames parses GetOwnedGames response
func ParseOwnedGames(resp *[]byte) (*OwnedGames, error) {
	o := OwnedGames{}
	if err := json.Unmarshal(*resp, &o); err != nil {
		return nil, err
	}
	return &o, nil
}

// PlayerSummaries fetches PlayerSummaries from Steam
func (s *Steam) PlayerSummaries(steamIDs string) (*PlayerSummaries, error) {
	uri, err := s.GenerateRequestURI("https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/", map[string]string{
		"steamids": steamIDs,
	})
	if err != nil {
		return nil, err
	}

	resp, body, errs := s.request.Get(uri).End()
	if errs != nil || resp.StatusCode != 200 {
		return nil, fmt.Errorf("HTTP Status Code: %d / %v", errs, resp.StatusCode)
	}
	tmp := []byte(body)

	return ParsePlayerSummaries(&tmp)
}

// OwnedGames fetches OwnedGames from Steam
func (s *Steam) OwnedGames(steamID string) (*OwnedGames, error) {
	uri, err := s.GenerateRequestURI("https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/", map[string]string{
		"steamid": steamID,
	})
	if err != nil {
		return nil, err
	}

	resp, body, errs := s.request.Get(uri).End()
	if errs != nil || resp.StatusCode != 200 {
		return nil, fmt.Errorf("HTTP Status Code: %d / %v", errs, resp.StatusCode)
	}
	tmp := []byte(body)

	return ParseOwnedGames(&tmp)
}

// GenerateRequestURI returns URI added key to query for Steam API
func (s *Steam) GenerateRequestURI(uri string, query map[string]string) (string, error) {
	u, err := url.Parse(uri)
	if err != nil {
		return "", fmt.Errorf("URI parsing failed: %s", uri)
	}

	q := u.Query()
	for key, val := range query {
		q.Set(key, val)
	}
	q.Set("key", s.apiKey)
	u.RawQuery = q.Encode()

	return u.String(), nil
}

license: MIT

repository: mikoim/steam-jp-finder

license_type: permissive

repo_name: mikoim/steam-jp-finder

branch_name: refs/heads/master


text:
/* Apache v2 license
*  Copyright (C) <2019> Intel Corporation
*
*  SPDX-License-Identifier: Apache-2.0
 */
package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/edgexfoundry/go-mod-core-contracts/models"
	"github.com/intel/rsp-sw-toolkit-im-suite-expect"
	"github.com/intel/rsp-sw-toolkit-im-suite-inventory-service/app/cloudconnector/event"
	"github.com/intel/rsp-sw-toolkit-im-suite-inventory-service/app/heartbeat"
	"github.com/intel/rsp-sw-toolkit-im-suite-inventory-service/app/tag"
	"github.com/intel/rsp-sw-toolkit-im-suite-inventory-service/pkg/integrationtest"
	"github.com/intel/rsp-sw-toolkit-im-suite-inventory-service/pkg/statemodel"
	"github.com/lib/pq"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"os"

	"github.com/intel/rsp-sw-toolkit-im-suite-inventory-service/app/config"
	"github.com/intel/rsp-sw-toolkit-im-suite-inventory-service/pkg/jsonrpc"
	"github.com/intel/rsp-sw-toolkit-im-suite-tagcode/epc"
	"github.com/intel/rsp-sw-toolkit-im-suite-utilities/helper"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

const tagsTable = "tags"
const jsonb = "data"
const epcColumn = "epc"

/* If useful this will be used for POSTGRESQL too in future */
var dbHost integrationtest.DBHost

func TestMain(m *testing.M) {
	dbHost = integrationtest.InitHost("main_test")
	exitCode := m.Run()
	dbHost.Close()
	os.Exit(exitCode)
}

// POC only implementation
func TestMarkDepartedIfUnseen(t *testing.T) {
	timestamp := 1500583263000 // Thursday, July 20, 2017 1:41:03 PM
	minutesBeforeAgeOut := 10

	tagEvent := jsonrpc.TagEvent{
		EventType:  "cycle_count",
		FacilityID: "testFacility",
		Timestamp:  int64(timestamp),
	}

	ageOuts := map[string]int{
		"testFacility": minutesBeforeAgeOut,
	}

	// calculate the future time after which we should consider this departed
	expectedFuture := int64(timestamp + (minutesBeforeAgeOut * 60 * 1000))

	markDepartedIfUnseen(&tagEvent, ageOuts, expectedFuture+1)
	if tagEvent.EventType != "departed" {
		t.Errorf("Expected tag to be changed to departed, but it's type is %s", tagEvent.EventType)
	}
}

func TestNotYetDeparted(t *testing.T) {
	timestamp := 1500583263000 // Thursday, July 20, 2017 1:41:03 PM
	minutesBeforeAgeOut := 10

	tagEvent := jsonrpc.TagEvent{
		EventType:  "cycle_count",
		FacilityID: "testFacility",
		Timestamp:  int64(timestamp),
	}

	ageOuts := map[string]int{
		"testFacility": minutesBeforeAgeOut,
	}

	// calculate the future time after which we should consider this departed
	expectedFuture := int64(timestamp + (minutesBeforeAgeOut * 60 * 1000))

	markDepartedIfUnseen(&tagEvent, ageOuts, expectedFuture-1)
	if tagEvent.EventType != "cycle_count" {
		t.Errorf("Expected tag to be stay as cycle_count, but it's type is %s", tagEvent.EventType)
	}
}

func TestNotACycleCount(t *testing.T) {
	timestamp := 1500583263000 // Thursday, July 20, 2017 1:41:03 PM
	minutesBeforeAgeOut := 10

	tagEvent := jsonrpc.TagEvent{
		EventType:  "arrival",
		FacilityID: "testFacility",
		Timestamp:  int64(timestamp),
	}

	ageOuts := map[string]int{
		"testFacility": minutesBeforeAgeOut,
	}

	// calculate the future time after which we should consider this departed
	// it won't become departed, though, because the event is not a cycle_count
	expectedFuture := int64(timestamp + (minutesBeforeAgeOut * 60 * 1000))

	markDepartedIfUnseen(&tagEvent, ageOuts, expectedFuture+1)
	if tagEvent.EventType != "arrival" {
		t.Errorf("Expected tag to be stay as arrival, but it's type is %s", tagEvent.EventType)
	}
}

func TestUnknownFacility(t *testing.T) {
	timestamp := 1500583263000 // Thursday, July 20, 2017 1:41:03 PM
	minutesBeforeAgeOut := 10

	tagEvent := jsonrpc.TagEvent{
		EventType:  "cycle_count",
		FacilityID: "testFacility",
		Timestamp:  int64(timestamp),
	}

	ageOuts := map[string]int{
		"someOtherFacility": minutesBeforeAgeOut,
	}

	// calculate the future time after which we should consider this departed
	// it won't become departed, though, because the facility isn't in the ageOuts config
	expectedFuture := int64(timestamp + (minutesBeforeAgeOut * 60 * 1000))

	markDepartedIfUnseen(&tagEvent, ageOuts, expectedFuture+1)
	if tagEvent.EventType != "cycle_count" {
		t.Errorf("Expected tag to be stay as cycle_count, but it's type is %s", tagEvent.EventType)
	}
}

//nolint:dupl
func TestFilter(t *testing.T) {
	testTag := jsonrpc.TagEvent{
		EpcCode: "302103201",
	}

	filters := []string{"123", "302", "456"}

	expected := true

	if result := statemodel.IsTagWhitelisted(testTag.EpcCode, filters); result != expected {
		t.Errorf("Filtering failed. Expected %v. Actual %v.", expected, result)
	}
}

//nolint:dupl
func TestFilterNotPresent(t *testing.T) {
	testTag := jsonrpc.TagEvent{
		EpcCode: "402103201",
	}

	filters := []string{"123", "302", "456"}
	expected := false

	if result := statemodel.IsTagWhitelisted(testTag.EpcCode, filters); result != expected {
		t.Errorf("Filtering failed. Expected %v. Actual %v.", expected, result)
	}
}

func TestFilterNoTags(t *testing.T) {
	testTag := jsonrpc.TagEvent{
		EpcCode: "402103201",
	}

	var filters []string
	expected := false

	if result := statemodel.IsTagWhitelisted(testTag.EpcCode, filters); result != expected {
		t.Errorf("Filtering failed. Expected %v. Actual %v.", expected, result)
	}
}

func TestDataProcessHandheld(t *testing.T) {

	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		defer func() {
			if _, err := ioutil.ReadAll(request.Body); err != nil {
				t.Error(err)
			}
		}()

		switch request.URL.EscapedPath() {
		case "/skus":
			jsonData := getMappingSkuSample()
			writer.Header().Set("Content-Type", "application/json")
			if _, err := writer.Write(jsonData); err != nil {
				t.Fatal(err)
			}
		case "/callwebhook":
			t.Log("webhook called")
		default:
			t.Fatalf("unexpected request for %s", request.URL.EscapedPath())
		}
	}))
	defer testServer.Close()

	config.AppConfig.RulesUrl = ""
	config.AppConfig.CloudConnectorRetrySeconds = 1

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	JSONSample := getJSONSampleHandheld(t)
	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	config.AppConfig.CloudConnectorUrl = testServer.URL

	// insert data as handheld
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample, "handheld", nil); err != nil {
		t.Errorf("error processing data %+v", err)
	}
}

func TestDataProcessFixedAllRulesTriggered(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		if request.URL.EscapedPath() != "/triggerrules" &&
			request.URL.EscapedPath() != "/skus" &&
			request.URL.EscapedPath() != "/callwebhook" {
			t.Fatalf("unexpected request path: %s", request.URL.EscapedPath())
		}

		var jsonData []byte
		if request.URL.EscapedPath() == "/triggerrules" {
			if request.Method != "POST" {
				t.Errorf("Expected 'POST' request, received '%s", request.Method)
			}

			queryString := request.URL.Query()
			ruleTypes := queryString["ruletype"]

			if len(ruleTypes) != 0 {
				t.Error("Expected trigger all rules")
			}

			var data interface{}

			jsonData, _ = json.Marshal(tag.Response{Results: data})
		}
		if request.URL.EscapedPath() == "/skus" {
			jsonData = getMappingSkuSample()
		}

		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write(jsonData)
	}))

	defer testServer.Close()

	config.AppConfig.TriggerRulesEndpoint = "/triggerrules"
	config.AppConfig.RulesUrl = testServer.URL
	config.AppConfig.CloudConnectorApiGatewayEndpoint = "/callwebhook"
	config.AppConfig.CloudConnectorUrl = testServer.URL
	config.AppConfig.TriggerRulesOnFixedTags = true

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	JSONSample := getJSONDepartedSample(t)
	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	// insert data as fixed
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample, "fixed", nil); err != nil {
		t.Errorf("error processing data %s", err.Error())
	}

}

func getJSONDepartedSample(t *testing.T) *jsonrpc.InventoryEvent {
	return createInventoryEvent(t, `{
				 "controller_id": "rsp-controller",
				 "total_event_segments": 1,
				 "event_segment_number": 1,
				 "data": [
							 {
								 "epc_code": "30143639F84191AD22900204",
								 "epc_encode_format": "tbd",
								 "event_type": "departed",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 }
						 ],
				 "sent_on": 1501872400247
   }`)
}

func TestDataProcessFixedNoOoSRulesTriggered(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		if request.URL.EscapedPath() != "/triggerrules" && request.URL.EscapedPath() != "/skus" && request.URL.EscapedPath() != "/callwebhook" {
			t.Errorf("Expected request to '/triggerrules', received %s", request.URL.EscapedPath())
		}
		var jsonData []byte
		if request.URL.EscapedPath() == "/triggerrules" {
			if request.Method != "POST" {
				t.Errorf("Expected 'POST' request, received '%s", request.Method)
			}

			queryString := request.URL.Query()
			ruleTypes := queryString["ruletype"]

			if len(ruleTypes) == 0 {
				t.Error("Expected to NOT trigger all rules")
			}

			if len(ruleTypes) != 1 || ruleTypes[0] != tag.StateChangeEvent {
				t.Error("Expected to only trigger State Change rules")
			}

			var data interface{}

			jsonData, _ = json.Marshal(tag.Response{Results: data})
		}
		if request.URL.EscapedPath() == "/skus" {
			jsonData = getMappingSkuSample()
		}
		if request.URL.EscapedPath() == "/callwebhook" {

			var data event.TagEventPayload
			body, err := ioutil.ReadAll(request.Body)
			if err != nil {
				t.Errorf(err.Error())
			}

			if err := json.Unmarshal(body, &data); err != nil {
				t.Errorf(err.Error())
			}

			for _, tag := range data.Body.TagEvent {
				if tag.Event != statemodel.DepartedEpcState {
					t.Errorf("Event type was not departed")
				}
			}
		}

		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write(jsonData)
	}))

	defer testServer.Close()

	config.AppConfig.TriggerRulesEndpoint = "/triggerrules"
	config.AppConfig.RulesUrl = testServer.URL
	config.AppConfig.TriggerRulesOnFixedTags = false

	config.AppConfig.CloudConnectorApiGatewayEndpoint = "/callwebhook"
	config.AppConfig.CloudConnectorUrl = testServer.URL

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	JSONSample := getJSONDepartedSample(t)
	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	// insert data as fixed
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample, "fixed", nil); err != nil {
		t.Errorf("error processing data %s", err.Error())
	}

}

func TestTagExistingArrivalReceiveCycleCountUpstreamCycleCount(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		if request.URL.EscapedPath() != "/triggerrules" && request.URL.EscapedPath() != "/skus" && request.URL.EscapedPath() != "/callwebhook" {
			t.Errorf("Expected request to '/triggerrules', received %s", request.URL.EscapedPath())
		}
		var jsonData []byte
		if request.URL.EscapedPath() == "/triggerrules" {
			if request.Method != "POST" {
				t.Errorf("Expected 'POST' request, received '%s", request.Method)
			}

			queryString := request.URL.Query()
			ruleTypes := queryString["ruletype"]

			if len(ruleTypes) == 0 {
				t.Error("Expected to NOT trigger all rules")
			}

			if len(ruleTypes) != 1 || ruleTypes[0] != tag.StateChangeEvent {
				t.Error("Expected to only trigger State Change rules")
			}

			var data interface{}

			jsonData, _ = json.Marshal(tag.Response{Results: data})
		}
		if request.URL.EscapedPath() == "/skus" {
			jsonData = getMappingSkuSample()
		}
		if request.URL.EscapedPath() == "/callwebhook" {

			var data event.TagEventPayload
			body, err := ioutil.ReadAll(request.Body)
			if err != nil {
				t.Errorf(err.Error())
			}

			if err := json.Unmarshal(body, &data); err != nil {
				t.Errorf(err.Error())
			}

			for _, tag := range data.Body.TagEvent {
				if tag.Event != statemodel.CycleCountEvent {
					t.Errorf("Event type was %s and not cycle_count", tag.Event)
				}
			}
		}

		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write(jsonData)
	}))

	defer testServer.Close()

	config.AppConfig.TriggerRulesEndpoint = "/triggerrules"
	config.AppConfig.RulesUrl = testServer.URL
	config.AppConfig.TriggerRulesOnFixedTags = false

	config.AppConfig.CloudConnectorApiGatewayEndpoint = "/callwebhook"
	config.AppConfig.CloudConnectorUrl = testServer.URL

	tagArray := make([]tag.Tag, 2)

	var tag0 tag.Tag
	tag0.Epc = "303400C0E43FF48000000002"
	sgtin, err := epc.DecodeSGTINString(tag0.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	uri := sgtin.URI()
	tag0.URI = uri
	tag0.Tid = t.Name() + "0"
	tag0.Source = "fixed"
	tag0.Event = statemodel.ArrivalEvent
	tag0.EpcState = statemodel.PresentEpcState
	tagArray[0] = tag0

	var tag1 tag.Tag
	tag1.Epc = "30143639F8419145DB601597"
	tag0.Epc = "303400C0E43FF48000000002"
	sgtin, err = epc.DecodeSGTINString(tag1.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	uri = sgtin.URI()
	tag0.URI = uri
	tag1.Tid = t.Name() + "1"
	tag1.Source = "fixed"
	tag1.Event = statemodel.ArrivalEvent
	tag1.EpcState = statemodel.PresentEpcState
	tagArray[1] = tag1

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	err = tag.Replace(testDB.DB, tagArray)
	if err != nil {
		t.Error("Unable to replace tags", err.Error())
	}

	JSONSample := createInventoryEvent(t, `{
				 "controller_id": "rsp-controller",
				 "total_event_segments": 1,
				 "event_segment_number": 1,
				 "data": [
							 {
								 "epc_code": "303400C0E43FF48000000002",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 },
							 {
								 "epc_code": "30143639F8419145DB601597",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501864735850
							 }
						 ],
				 "sent_on": 1501872400247
   }`)

	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	// insert data as fixed
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample, "fixed", nil); err != nil {
		t.Errorf("error processing data %s", err.Error())
	}

}

// TestTagExistingMovedReceiveCycleCountUpstreamCycleCount tests that current
// tags existed and were not changed thus the event type cycle_count
func TestTagExistingMovedReceiveCycleCountUpstreamCycleCount(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		if request.URL.EscapedPath() != "/triggerrules" && request.URL.EscapedPath() != "/skus" && request.URL.EscapedPath() != "/callwebhook" {
			t.Errorf("Expected request to '/triggerrules', received %s", request.URL.EscapedPath())
		}
		var jsonData []byte
		if request.URL.EscapedPath() == "/triggerrules" {
			if request.Method != "POST" {
				t.Errorf("Expected 'POST' request, received '%s", request.Method)
			}

			queryString := request.URL.Query()
			ruleTypes := queryString["ruletype"]

			if len(ruleTypes) == 0 {
				t.Error("Expected to NOT trigger all rules")
			}

			if len(ruleTypes) != 1 || ruleTypes[0] != tag.StateChangeEvent {
				t.Error("Expected to only trigger State Change rules")
			}

			var data interface{}

			jsonData, _ = json.Marshal(tag.Response{Results: data})
		}
		if request.URL.EscapedPath() == "/skus" {
			jsonData = getMappingSkuSample()
		}
		if request.URL.EscapedPath() == "/callwebhook" {

			var data event.TagEventPayload
			body, err := ioutil.ReadAll(request.Body)
			if err != nil {
				t.Errorf(err.Error())
			}

			if err := json.Unmarshal(body, &data); err != nil {
				t.Errorf(err.Error())
			}

			for _, tag := range data.Body.TagEvent {
				if tag.Event != statemodel.CycleCountEvent {
					t.Errorf("Event type was %s and not cycle_count", tag.Event)
				}
			}
		}

		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write(jsonData)
	}))

	defer testServer.Close()

	config.AppConfig.TriggerRulesEndpoint = "/triggerrules"
	config.AppConfig.RulesUrl = testServer.URL
	config.AppConfig.TriggerRulesOnFixedTags = false

	config.AppConfig.CloudConnectorApiGatewayEndpoint = "/callwebhook"
	config.AppConfig.CloudConnectorUrl = testServer.URL

	tagArray := make([]tag.Tag, 4)

	var tag0 tag.Tag
	tag0.Epc = "30143639F8419145DB601567"
	sgtin, err := epc.DecodeSGTINString(tag0.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	tag0.URI = sgtin.URI()
	tag0.Tid = t.Name() + "0"
	tag0.Source = "fixed"
	tag0.Event = statemodel.MovedEvent
	tag0.EpcState = statemodel.PresentEpcState
	tagArray[0] = tag0

	var tag1 tag.Tag
	tag1.Epc = "30343639F8419145DB601443"
	sgtin, err = epc.DecodeSGTINString(tag1.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	tag1.URI = sgtin.URI()
	tag1.Tid = t.Name() + "1"
	tag1.Source = "fixed"
	tag1.Event = statemodel.MovedEvent
	tag1.EpcState = statemodel.PresentEpcState
	tagArray[1] = tag1

	var tag2 tag.Tag
	tag2.Epc = "3014032F440010C5407BA3FB"
	sgtin, err = epc.DecodeSGTINString(tag2.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	tag2.URI = sgtin.URI()
	tag2.Tid = t.Name() + "0"
	tag2.Source = "fixed"
	tag2.Event = statemodel.MovedEvent
	tag2.EpcState = statemodel.PresentEpcState
	tagArray[2] = tag2

	var tag3 tag.Tag
	tag3.Epc = "30143639F8419145DB601543"
	sgtin, err = epc.DecodeSGTINString(tag3.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	tag3.URI = sgtin.URI()
	tag3.Tid = t.Name() + "1"
	tag3.Source = "fixed"
	tag3.Event = statemodel.MovedEvent
	tag3.EpcState = statemodel.PresentEpcState
	tagArray[3] = tag3

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	err = tag.Replace(testDB.DB, tagArray)
	if err != nil {
		t.Error("Unable to replace tags", err.Error())
	}

	JSONSample1 := createInventoryEvent(t, `{
				 "controller_id": "rsp-controller",
				 "event_segment_number": 1,
				 "total_event_segments": 2,
				 "data": [
							 {
								 "epc_code": "30143639F8419145DB601567",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 },
							 {
								 "epc_code": "30143639F8419145DB601567",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501864735850
							 }
						 ],
				 "sent_on": 1501872400247
  }`)
	JSONSample2 := createInventoryEvent(t, `{
				 "controller_id": "rsp-controller",
				 "event_segment_number": 2,
				 "total_event_segments": 2,
				 "data": [
							 {
								 "epc_code": "3014032F440010C5407BA3FB",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 },
							 {
								 "epc_code": "30143639F8419145DB601543",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501864735850
							 }
						 ],
				 "sent_on": 1501872400247
  }`)
	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	// insert data as fixed
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample1, "fixed", nil); err != nil {
		t.Errorf("error processing data %s", err.Error())
	}
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample2, "fixed", nil); err != nil {
		t.Errorf("error processing data %s", err.Error())
	}

}

// TestTagExistingDepartedReceiveCycleCountUpstreamArrival tests that current tags that have departed events will be
// changed to Arrival when a cycle count is recieved with those EPC tags
func TestTagExistingDepartedReceiveCycleCountUpstreamArrival(t *testing.T) {
	rulesTriggered := make(chan bool, 1)

	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		t.Log("request for", request.URL)

		defer func() {
			if _, err := ioutil.ReadAll(request.Body); err != nil {
				t.Error("error reading body", err)
			}
		}()

		var err error
		var jsonData []byte
		switch request.URL.Path {
		case "/triggerrules":
			if request.Method != "POST" {
				t.Errorf("Expected 'POST' request, received '%s", request.Method)
			}

			queryString := request.URL.Query()
			ruleTypes := queryString["ruletype"]

			if ruleTypes == nil {
				t.Error("missing ruletype from query string")
			} else if len(ruleTypes) == 0 {
				t.Error("Expected to NOT trigger all rules")
			} else if len(ruleTypes) != 1 || ruleTypes[0] != tag.StateChangeEvent {
				t.Error("Expected to only trigger State Change rules")
			} else {
				rulesTriggered <- true
			}

			var data interface{}
			jsonData, err = json.Marshal(tag.Response{Results: data})
			if err != nil {
				t.Error("marshaling failed", err)
			}
		case "/skus":
			jsonData = getMappingSkuSample()
		case "/callwebhook":
			var data event.TagEventPayload
			body, err := ioutil.ReadAll(request.Body)
			if err != nil {
				t.Fatal(err)
			}

			if err = json.Unmarshal(body, &data); err != nil {
				t.Error(err)
			}

			for tagIdx, tagEvent := range data.Body.TagEvent {
				if tagEvent.Event != statemodel.ArrivalEvent {
					t.Errorf("Event type for tag %d was not arrival", tagIdx)
				}
			}
		default:
			t.Error("unexpected request", request.URL.EscapedPath())
			return
		}

		writer.Header().Set("Content-Type", "application/json")
		if _, err = writer.Write(jsonData); err != nil {
			t.Error(err)
		}
	}))

	defer testServer.Close()

	config.AppConfig.TriggerRulesEndpoint = "/triggerrules"
	config.AppConfig.RulesUrl = testServer.URL
	config.AppConfig.TriggerRulesOnFixedTags = false
	config.AppConfig.CloudConnectorApiGatewayEndpoint = "/callwebhook"
	config.AppConfig.CloudConnectorUrl = testServer.URL

	tagArray := make([]tag.Tag, 2)

	var tag0 tag.Tag
	tag0.Epc = "30143639F8419145DB601529"
	sgtin, err := epc.DecodeSGTINString(tag0.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	tag0.URI = sgtin.URI()
	tag0.Tid = t.Name() + "0"
	tag0.Source = "fixed"
	tag0.Event = statemodel.DepartedEvent
	tag0.EpcState = statemodel.DepartedEpcState
	tagArray[0] = tag0

	var tag1 tag.Tag
	tag1.Epc = "30143639F8419145DB601565"
	sgtin, err = epc.DecodeSGTINString(tag1.Epc)
	if err != nil {
		t.Fatalf("%+v", err)
	}
	tag1.URI = sgtin.URI()
	tag1.Tid = t.Name() + "1"
	tag1.Source = "fixed"
	tag1.Event = statemodel.DepartedEvent
	tag1.EpcState = statemodel.DepartedEpcState
	tagArray[1] = tag1

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	err = tag.Replace(testDB.DB, tagArray)
	if err != nil {
		t.Errorf("Unable to replace tags: %+v", err)
	}

	JSONSample := createInventoryEvent(t, `{
				 "controller_id": "rsp-controller",
         "total_event_segments": 1,
         "event_segment_number": 1,
				 "data": [
							 {
								 "epc_code": "30143639F8419145DB601529",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 },
							 {
								 "epc_code": "30143639F8419145DB601565",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501864735850
							 }
						 ],
				 "sent_on": 1501872400247
   }`)

	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	// insert data as fixed
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample, "fixed", nil); err != nil {
		t.Errorf("error processing data %+v", err)
	}

	select {
	case <-rulesTriggered:
	case <-time.After(time.Second):
		t.Error("the rules were never triggered")
	}
}

// TestTagDoesNotExistReceiveCycleCountUpstreamArrival tests that current tags
// in the cycle count are new to the database and the event is changed to Arrival.
func TestTagDoesNotExistReceiveCycleCountUpstreamArrival(t *testing.T) {
	log.SetLevel(log.DebugLevel)
	rulesTriggered := make(chan bool, 1)

	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		t.Log("request for", request.URL)

		defer func() {
			if _, err := ioutil.ReadAll(request.Body); err != nil {
				t.Error("error reading body", err)
			}
		}()

		var err error
		var jsonData []byte

		switch request.URL.Path {
		case "/triggerrules":
			if request.Method != "POST" {
				t.Errorf("Expected 'POST' request, received '%s", request.Method)
			}

			queryString := request.URL.Query()
			ruleTypes := queryString["ruletype"]

			if ruleTypes == nil {
				t.Error("missing ruletype from query string")
			} else if len(ruleTypes) == 0 {
				t.Error("Expected to NOT trigger all rules")
			} else if len(ruleTypes) != 1 || ruleTypes[0] != tag.StateChangeEvent {
				t.Error("Expected to only trigger State Change rules")
			} else {
				rulesTriggered <- true
			}

			var data interface{}
			jsonData, err = json.Marshal(tag.Response{Results: data})
			if err != nil {
				t.Error(err)
			}
		case "/skus":
			jsonData = getMappingSkuSample()
		case "/callwebhook":
			var data event.TagEventPayload
			body, err := ioutil.ReadAll(request.Body)
			if err != nil {
				t.Errorf(err.Error())
			}

			if err := json.Unmarshal(body, &data); err != nil {
				t.Errorf(err.Error())
			}

			for tagIdx, tagEvent := range data.Body.TagEvent {
				log.Info(tagEvent)
				if tagEvent.Event != statemodel.ArrivalEvent {
					t.Errorf("When a cycle count event is recieved from the "+
						"RSP Controller AND the tag doesn't exist in the database, the event type "+
						"should be Arrival, but for tag %d, it was %s: %+v.",
						tagIdx, tagEvent.Event, tagEvent)
				}
			}
		default:
			t.Error("unexpected request", request.URL.EscapedPath())
			return
		}

		writer.Header().Set("Content-Type", "application/json")
		if _, err = writer.Write(jsonData); err != nil {
			t.Error(err)
		}
	}))

	defer testServer.Close()

	config.AppConfig.TriggerRulesEndpoint = "/triggerrules"
	config.AppConfig.RulesUrl = testServer.URL
	config.AppConfig.CloudConnectorApiGatewayEndpoint = "/callwebhook"
	config.AppConfig.CloudConnectorUrl = testServer.URL
	config.AppConfig.TriggerRulesOnFixedTags = false

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	JSONSample := getJSONCycleCountSample(t)
	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	// insert data as fixed
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample, "fixed", nil); err != nil {
		t.Errorf("error processing data %+v", err)
	}

	select {
	case <-rulesTriggered:
	case <-time.After(time.Second):
		t.Error("the rules were never triggered")
	}
}

func TestDataProcessFixedWhitelisted(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		if request.URL.EscapedPath() != "/triggerrules" && request.URL.EscapedPath() != "/skus" && request.URL.EscapedPath() != "/callwebhook" {
			t.Errorf("Expected request to '/triggerrules', received %s", request.URL.EscapedPath())
		}
		var jsonData []byte
		if request.URL.EscapedPath() == "/triggerrules" {
			if request.Method != "POST" {
				t.Errorf("Expected 'POST' request, received '%s", request.Method)
			}

			queryString := request.URL.Query()
			ruleTypes := queryString["ruletype"]

			if len(ruleTypes) == 0 {
				t.Error("Expected to NOT trigger all rules")
			}

			if len(ruleTypes) != 1 || ruleTypes[0] != tag.StateChangeEvent {
				t.Error("Expected to only trigger State Change rules")
			}

			var data interface{}

			jsonData, _ = json.Marshal(tag.Response{Results: data})
		}
		if request.URL.EscapedPath() == "/skus" {
			jsonData = getMappingSkuSample()
		}

		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write(jsonData)
	}))

	defer testServer.Close()

	config.AppConfig.TriggerRulesEndpoint = "/triggerrules"
	config.AppConfig.CloudConnectorApiGatewayEndpoint = "/callwebhook"
	config.AppConfig.CloudConnectorUrl = testServer.URL
	config.AppConfig.RulesUrl = testServer.URL
	config.AppConfig.TriggerRulesOnFixedTags = false

	// Filter through only those starting with "30"
	config.AppConfig.EpcFilters = []string{"30"}

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	JSONSample := createInventoryEvent(t, `{
				 "controller_id": "rsp-controller",
				 "data": [
							 {
								 "epc_code": "30243639F84191AD22900266",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 },
							 {
								 "epc_code": "0F0000000000AA00000014D2",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501864735850
							 }
						 ],
				 "sent_on": 1501872400247
   }`)
	skuMapping := NewSkuMapping(testServer.URL + "/skus")
	// insert data as fixed
	if err := skuMapping.processTagData(newInventoryApp(testDB.DB), JSONSample, "fixed", nil); err != nil {
		t.Errorf("error processing data %s", err.Error())
	}

	getNotWhitelistedTag, err := tag.FindByEpc(testDB.DB, "0F0000000000AA00000014D2")
	if err != nil {
		t.Fatalf("Error retrieving tag from database: %s", err.Error())
	}

	// it should be empty because it was not whitelisted
	if !getNotWhitelistedTag.IsEmpty() {
		t.Errorf("Tag was not whitelisted.  Should not be in database")
	}

	getWhitelistedTag, err := tag.FindByEpc(testDB.DB, "30243639F84191AD22900266")
	if err != nil {
		t.Fatalf("Error retrieving tag from database: %s", err.Error())
	}

	// it should be empty because it was not whitelisted
	if getWhitelistedTag.IsEmpty() {
		t.Errorf("Tag was whitelisted.  Should be in database")
	}
}

func TestProcessHeartBeat(t *testing.T) {

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	JSONSample := createHeartbeat(t, `{
		   "controller_id": "rsp-controller",
		   "device_id": "rsp-controller",
		   "facilities": [
			 "Tavern"
		   ],
		   "facility_groups_cfg": "auto-0802233641",
		   "mesh_id": null,
		   "mesh_node_id": null,
		   "personality_groups_cfg": null,
		   "schedule_cfg": "UNKNOWN",
		   "schedule_groups_cfg": null,
		   "sent_on": 1503700192960
	   }`)

	if err := heartbeat.ProcessHeartbeat(JSONSample, testDB.DB); err != nil {
		t.Errorf("error processing hearbeat data %s", err.Error())
	}
}

func TestProcessShippingNotice(t *testing.T) {

	testDB := dbHost.CreateDB(t)
	defer testDB.Close()
	clearAllData(t, testDB.DB)

	config.AppConfig.EpcFilters = []string{"303", "301"}
	JSONShippingNotice := []byte(`
		[
			{
				"asnId": "AS876422",
				"eventTime": "2018-03-12T12: 34: 56.789Z",
				"siteId": "0105",
				"items": [
					{
						"itemId": "large lamp",
						"itemGtin": "00888446671424",
						"itemEpcs": [
							"30343639F84191AD22900204",
							"30143639F84191AD22900205",
							"30143639F84191AD22900206"
						]
					}
				]
			}
		]
	`)

	// make sure the tag doesn't currently exist
	gotTag, err := tag.FindByEpc(testDB.DB, "30343639F84191AD22900204")
	if err != nil {
		t.Errorf("Error retrieving tag from database: %+v", err)
	} else {
		// the tag should be empty
		if !gotTag.IsEmpty() {
			t.Errorf("tag should be empty, but was: %+v", gotTag)
		}
		// new tags shouldn't default to being shipping notices
		if gotTag.IsShippingNoticeEntry() {
			t.Errorf("tag shouldn't yet have a Advance Shipping Notice: %+v", gotTag)
		}
	}

	// process the ASN
	if err = processShippingNotice(JSONShippingNotice, testDB.DB, nil); err != nil {
		t.Errorf("error processing data: %+v", err)
	}

	//now get the tag again; this time, it should exist
	gotTag, err = tag.FindByEpc(testDB.DB, "30343639F84191AD22900204")
	if err != nil {
		t.Fatalf("Error retrieving tag from database: %+v", err)
	}

	// it should not be empty, but it should only exist as an ASN
	if gotTag.IsEmpty() || !gotTag.IsShippingNoticeEntry() {
		t.Errorf("After processing ASN, new tag is not marked result of ASN: %+v", gotTag)
	}

	var asn tag.ASNContext
	if err := json.Unmarshal([]byte(gotTag.EpcContext), &asn); err != nil {
		t.Errorf("unable to unmarshal ASN in EPC context %+v", err)
	}
	checkASNContext(t, &asn)
}

func TestProcessShippingNoticeWhitelistedEPC(t *testing.T) {
	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	jsonShippingNotice := []byte(`
		[
			{
				"asnId": "AS876422",
				"eventTime": "2018-03-12T12: 34: 56.789Z",
				"siteId": "0105",
				"items": [
					{
						"itemId": "large lamp",
						"itemGtin": "00888446671424",
						"itemEpcs": [
							"3034257BF400B7800004CB2F"
						]
					}
				]
			}
		]
	`)

	// Filter through only those starting with "30"
	config.AppConfig.EpcFilters = []string{"30"}

	// make sure the tag doesn't currently exist
	gotTag, err := tag.FindByEpc(testDB.DB, "3034257BF400B7800004CB2F")
	if err != nil {
		t.Errorf("Error retrieving tag from database: %s", err.Error())
	} else {
		// the tag should be empty
		if !gotTag.IsEmpty() {
			t.Errorf("tag should be empty, but was: %v", gotTag)
		}
		// new tags shouldn't default to being shipping notices
		if gotTag.IsShippingNoticeEntry() {
			t.Errorf("tag shouldn't yet have a Advance Shipping Notice: %v", gotTag)
		}
	}

	// process the ASN
	if err = processShippingNotice(jsonShippingNotice, testDB.DB, nil); err != nil {
		t.Errorf("error processing data %s", err.Error())
	}

	clearAllData(t, testDB.DB)

	// now get the tag again; this time, it should exist
	gotTag, err = tag.FindByEpc(testDB.DB, "3034257BF400B7800004CB2F")
	if err != nil {
		t.Fatalf("Error retrieving tag from database: %s", err.Error())
	}
}

func TestProcessShippingNoticeExistingTag(t *testing.T) {
	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	jsonShippingNotice := []byte(`
		[
			{
				"asnId": "AS876422",
				"eventTime": "2018-03-12T12: 34: 56.789Z",
				"siteId": "0105",
				"items": [
					{
						"itemId": "large lamp",
						"itemGtin": "00888446671424",
						"itemEpcs": [
						  "3034257BF400B7800004CB2F"
						]
					}
				]
			}
		]
	`)

	// insert a known tag
	existingTag := getTagData()[0]
	existingTag.Epc = "3034257BF400B7800004CB2F"
	w := expect.WrapT(t).StopOnMismatch().As(existingTag)
	w.ShouldNotBeEqual(existingTag.LastRead, 0)
	w.ShouldSucceed(insert(testDB.DB, existingTag))
	w.ShouldSucceed(processShippingNotice(jsonShippingNotice, testDB.DB, nil))

	gotTag := w.ShouldHaveResult(tag.FindByEpc(testDB.DB, existingTag.Epc)).(tag.Tag)
	w = w.As(gotTag)
	w.ShouldBeFalse(gotTag.IsEmpty())
	w.ShouldBeFalse(gotTag.IsShippingNoticeEntry())
	w.ShouldBeEqual(gotTag.LastRead, existingTag.LastRead)

	clearAllData(t, testDB.DB)

	var asn tag.ASNContext
	w.As(gotTag.EpcContext).ShouldSucceed(json.Unmarshal([]byte(gotTag.EpcContext), &asn))
	checkASNContext(t, &asn)
}

func TestCallDeleteTagCollection(t *testing.T) {
	testDB := dbHost.CreateDB(t)
	defer testDB.Close()

	if err := callDeleteTagCollection(testDB.DB); err != nil {
		t.Fatalf("error on calling delete tag collection %s", err.Error())
	}
}

func getTagData() []tag.Tag {
	return []tag.Tag{
		{
			FacilityID:      "Tavern",
			LastRead:        helper.UnixMilli(time.Now().AddDate(0, 0, -1)),
			Epc:             "30143639F84191AD22900204",
			EpcEncodeFormat: "tbd",
			Event:           "cycle_count",
			LocationHistory: []tag.LocationHistory{
				{
					Location:  "RSP-950b44",
					Timestamp: 1506638821662,
					Source:    "fixed",
				}},
			Tid: "",
		},
	}
}

func getJSONCycleCountSample(t *testing.T) *jsonrpc.InventoryEvent {
	return createInventoryEvent(t, `{
				 "controller_id": "rsp-controller",
				 "total_event_segments": 1,
				 "event_segment_number": 1,
				 "data": [
							 {
								 "epc_code": "301430A55C0AC40000000008",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 },
							 {
								 "epc_code": "0F00000000000C00000014D2",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501864735850
							 }
						 ],
				 "sent_on": 1501872400247
   }`)
}

func createHeartbeat(t *testing.T, data string) *jsonrpc.Heartbeat {
	data = wrapJsonrpcParams("heartbeat", data)
	reading := &models.Reading{Value: data}
	js := new(jsonrpc.Heartbeat)
	if err := jsonrpc.Decode(reading.Value, js, nil); err != nil {
		t.Error(errors.Wrap(err, data))
	}
	return js
}

func checkASNContext(t *testing.T, asn *tag.ASNContext) {
	w := expect.WrapT(t).As(asn)

	switch asn.ASNID {
	case "AS876422":
		w.ShouldBeEqual(asn.EventTime, "2018-03-12T12: 34: 56.789Z")
		w.ShouldBeEqual(asn.SiteID, "0105")
	case "AS876423":
		w.ShouldBeEqual(asn.EventTime, "2019-03-12T12: 59: 56.789Z")
		w.ShouldBeEqual(asn.SiteID, "0106")
	default:
		w.Errorf("Wrong ASNID: %s", asn.ASNID)
		return
	}

	w.ShouldBeEqual(asn.ItemGTIN, "00888446671424")
	w.ShouldBeEqual(asn.ItemID, "large lamp")
}

// nolint :dupl
func insert(db *sql.DB, tag tag.Tag) error {
	obj, err := json.Marshal(tag)
	if err != nil {
		return err
	}

	upsertStmt := fmt.Sprintf(`INSERT INTO %s (%s) VALUES (%s)
									 ON CONFLICT (( %s  ->>  %s ))
									 DO UPDATE SET %s = %s.%s || %s;`,
		pq.QuoteIdentifier(tagsTable),
		pq.QuoteIdentifier(jsonb),
		pq.QuoteLiteral(string(obj)),
		pq.QuoteIdentifier(jsonb),
		pq.QuoteLiteral(epcColumn),
		pq.QuoteIdentifier(jsonb),
		pq.QuoteIdentifier(tagsTable),
		pq.QuoteIdentifier(jsonb),
		pq.QuoteLiteral(string(obj)),
	)

	_, err = db.Exec(upsertStmt)
	if err != nil {
		return err
	}
	return nil
}

// nolint :dupl
func clearAllData(t *testing.T, db *sql.DB) {

	selectQuery := fmt.Sprintf(`DELETE FROM %s`,
		pq.QuoteIdentifier(tagsTable),
	)

	_, err := db.Exec(selectQuery)
	if err != nil {
		t.Errorf("Unable to delete data from %s table: %s", tagsTable, err)
	}
}

func wrapJsonrpcParams(method string, params string) string {
	sb := strings.Builder{}
	sb.WriteString(`{"jsonrpc":"2.0","method":"`)
	sb.WriteString(method)
	sb.WriteString(`","params":`)
	sb.WriteString(params)
	sb.WriteString(`}`)
	return sb.String()
}

func createInventoryEvent(t *testing.T, data string) *jsonrpc.InventoryEvent {
	data = wrapJsonrpcParams("inventory_event", data)
	reading := &models.Reading{Value: data}
	js := new(jsonrpc.InventoryEvent)
	if err := jsonrpc.Decode(reading.Value, js, nil); err != nil {
		t.Error(errors.Wrap(err, data))
	}
	return js
}

// controller_id is empty for handheld data
func getJSONSampleHandheld(t *testing.T) *jsonrpc.InventoryEvent {
	return createInventoryEvent(t, `{
				 "controller_id": "",
				 "data": [
							 {
								 "epc_code": "30143639F84191AD22900104",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501863300375
							 },
							 {
								 "epc_code": "30143639F84191AD66100107",
								 "epc_encode_format": "tbd",
								 "event_type": "cycle_count",
								 "facility_id": "Tavern",
								 "location": "RSP-95bd71",
								 "tid": null,
								 "timestamp": 1501864735850
							 }
						 ],
				 "sent_on": 1501872400247
   }`)
}

func getMappingSkuSample() []byte {
	return []byte(`{
  "results": [
    {
      "_id": "5bd105f16136e8cf3f152ea7",
      "sku": "12345679",
      "productList": [
        {
          "becomingReadable": 0.0456,
          "beingRead": 0.0123,
          "dailyTurn": 0.0121,
          "exitError": 0.0789,
          "metadata": {
            "color": "red",
            "size": "M"
          },
          "productId": "00400013635631"
        }
      ]
    },
    {
      "_id": "6aa105f16136e8cf3f152ea7",
      "sku": "70727815015607",
      "productList": [
        {
          "becomingReadable": 0.0456,
          "beingRead": 0.0123,
          "dailyTurn": 0.0121,
          "exitError": 0.0789,
          "metadata": {
            "color": "red",
            "size": "M"
          },
          "productId": "70727815015607"
        }
      ]
    }
  ]
}`)
}