//FindByCustId retrieves a card's information by the unique id from a CRM system
//this id was provided when a card was saved
//this func is used when making api style request to semi-automate the charging of a card.
//first memcache is checked for the data, then the datastore
func FindByCustId(c context.Context, customerId string) (CustomerDatastore, error) {
	//check for card in memcache
	var r CustomerDatastore
	_, err := memcache.Gob.Get(c, customerId, &r)
	if err == nil {
		return r, nil
	}

	//card data not found in memcache
	//look up data in datastore
	//save card to memcache after it is found
	if err == memcache.ErrCacheMiss {
		//only getting the fields we need to show data in the charge card panel
		data, err := datastoreFindOne(c, "CustomerId =", customerId, []string{"CustomerName", "Cardholder", "CardLast4", "CardExpiration"})
		if err != nil {
			return data, err
		}

		//save to memcache
		//ignore errors since we already got the data
		memcacheutils.Save(c, customerId, data)

		//done
		return data, nil

	} else {
		return CustomerDatastore{}, err
	}

	return CustomerDatastore{}, err
}
//findByDatastoreId retrieves a card's information by its datastore id
//this returns all the info on a card that is needed to build the ui
//first memcache is checked for the data, then the datastore
func findByDatastoreId(c context.Context, datastoreId int64) (CustomerDatastore, error) {
	//check for card in memcache
	var r CustomerDatastore
	datastoreIdStr := strconv.FormatInt(datastoreId, 10)
	_, err := memcache.Gob.Get(c, datastoreIdStr, &r)
	if err == nil {
		return r, nil
	}

	//card data not found in memcache
	//look up data in datastore
	//save card to memcache after it is found
	if err == memcache.ErrCacheMiss {
		key := getCustomerKeyFromId(c, datastoreId)
		data, err := datastoreFindOne(c, "__key__ =", key, []string{"CustomerId", "CustomerName", "Cardholder", "CardLast4", "CardExpiration", "StripeCustomerToken"})
		if err != nil {
			return data, err
		}

		//save to memcache
		//ignore errors since we already got the data
		memcacheutils.Save(c, datastoreIdStr, data)

		//done
		return data, nil

	} else {
		return CustomerDatastore{}, err
	}

	return CustomerDatastore{}, err
}
//getCompanyInfo actually retrienves the information from memcache or the datastore
//putting this into a separate func cleans up code elsewhere
func getCompanyInfo(r *http.Request) (companyInfo, error) {
	//check memcache
	c := appengine.NewContext(r)
	var result companyInfo
	_, err := memcache.Gob.Get(c, memcacheKeyName, &result)
	if err == nil {
		return result, nil
	}

	//data not found in memcache
	//get from datastore
	if err == memcache.ErrCacheMiss {
		key := datastore.NewKey(c, datastoreKind, datastoreKey, 0, nil)

		//get data
		err := datastore.Get(c, key, &result)
		if err == datastore.ErrNoSuchEntity {
			return result, ErrCompanyDataDoesNotExist
		} else if err != nil {
			return result, err
		}

		//save to memcache
		//ignore errors since we already got the data
		memcacheutils.Save(c, memcacheKeyName, result)

		//return data
		return result, nil

	} else if err != nil {
		return companyInfo{}, err
	}

	return companyInfo{}, err
}
//SaveCompanyInfo saves new or updates existing company info in the datastore
func SaveCompanyInfo(w http.ResponseWriter, r *http.Request) {
	//get form values
	name := strings.TrimSpace(r.FormValue("name"))
	street := strings.TrimSpace(r.FormValue("street"))
	suite := strings.TrimSpace(r.FormValue("suite"))
	city := strings.TrimSpace(r.FormValue("city"))
	state := strings.TrimSpace(r.FormValue("state"))
	postal := strings.TrimSpace(r.FormValue("postal"))
	country := strings.TrimSpace(r.FormValue("country"))
	phone := strings.TrimSpace(r.FormValue("phone"))

	//look up data for this company
	//may return a blank struct if this data does not exist yet
	data, err := getCompanyInfo(r)
	if err != nil && err != ErrCompanyDataDoesNotExist {
		output.Error(err, "", w, r)
		return
	}

	//context
	c := appengine.NewContext(r)

	//generate entity key
	//keyname is hard coded so only one entity exists
	key := datastore.NewKey(c, datastoreKind, datastoreKey, 0, nil)

	//build entity to save
	//or update existing entity
	data.CompanyName = name
	data.Street = street
	data.Suite = suite
	data.City = city
	data.State = strings.ToUpper(state)
	data.PostalCode = postal
	data.Country = strings.ToUpper(country)
	data.PhoneNum = phone

	//save company info
	_, err = datastore.Put(c, key, &data)
	if err != nil {
		output.Error(err, "", w, r)
		return
	}

	//save company into to memcache
	//ignoring errors since we can always get data from the datastore
	memcacheutils.Save(c, memcacheKeyName, data)

	//done
	output.Success("dataSaved", data, w)
	return
}
//GetAll retrieves the list of all users in the datastore
//the data is pulled from memcache or the datastore
//the data is returned as a json to populate select menus in the gui
func GetAll(w http.ResponseWriter, r *http.Request) {
	//check if list of users is in memcache
	result := make([]userList, 0, 5)
	c := appengine.NewContext(r)
	_, err := memcache.Gob.Get(c, listOfUsersKey, &result)
	if err == nil {
		output.Success("userList-cached", result, w)
		return
	}

	//list of cards not found in memcache
	//get list from datastore
	//only need to get username and entity key to cut down on datastore usage
	//save the list to memcache for faster retrieval next time
	if err == memcache.ErrCacheMiss {
		q := datastore.NewQuery(datastoreKind).Order("Username").Project("Username")
		users := make([]User, 0, 5)
		keys, err := q.GetAll(c, &users)
		if err != nil {
			output.Error(err, "Error retrieving list of users from datastore.", w, r)
			return
		}

		//build result
		//format data to show just datastore id and username
		//creates a map of structs
		idsAndNames := make([]userList, 0, 5)
		for i, r := range users {
			x := userList{
				Username: r.Username,
				Id:       keys[i].IntID(),
			}

			idsAndNames = append(idsAndNames, x)
		}

		//save the list of users to memcache
		//ignore errors since we still retrieved the data
		memcacheutils.Save(c, listOfUsersKey, idsAndNames)

		//return data to clinet
		output.Success("userList", idsAndNames, w)
		return

	} else if err != nil {
		output.Error(err, "Unknown error retrieving list of users.", w, r)
		return
	}

	return
}
//GetAll retrieves the list of all cards in the datastore (datastore id and customer name only)
//the data is pulled from memcache or the datastore
//the data is returned as json to populate the datalist in the html ui
func GetAll(w http.ResponseWriter, r *http.Request) {
	//check if list of cards is in memcache
	c := appengine.NewContext(r)
	result := make([]CardList, 0, 50)
	_, err := memcache.Gob.Get(c, listOfCardsKey, &result)
	if err == nil {
		output.Success("cardlist-cached", result, w)
		return
	}

	//list of cards not found in memcache
	//get list from datastore
	//only need to get entity keys and customer names: cuts down on datastore usage
	//save the list to memcache for faster retrieval next time
	if err == memcache.ErrCacheMiss {
		q := datastore.NewQuery(datastoreKind).Order("CustomerName").Project("CustomerName")
		cards := make([]CustomerDatastore, 0, 50)
		keys, err := q.GetAll(c, &cards)
		if err != nil {
			output.Error(err, "Error retrieving list of cards from datastore.", w, r)
			return
		}

		//build result
		//format data to show just datastore id and customer name
		//creates a map of structs
		idAndNames := make([]CardList, 0, 50)
		for i, r := range cards {
			x := CardList{r.CustomerName, keys[i].IntID()}
			idAndNames = append(idAndNames, x)
		}

		//save list of cards to memcache
		//ignore errors since we already got the data
		memcacheutils.Save(c, listOfCardsKey, idAndNames)

		//return data to client
		output.Success("cardList-datastore", idAndNames, w)
		return

	} else if err != nil {
		output.Error(err, "Unknown error retrieving list of cards.", w, r)
		return
	}

	return
}
//saveUser does the actual saving of a user to the datastore
//separate function to clean up code
func saveUser(c context.Context, key *datastore.Key, user User) (*datastore.Key, error) {
	//save to datastore
	completeKey, err := datastore.Put(c, key, &user)
	if err != nil {
		return completeKey, err
	}

	//save user to memcache
	mKey := strconv.FormatInt(completeKey.IntID(), 10)
	err = memcacheutils.Save(c, mKey, user)
	if err != nil {
		return completeKey, err
	}

	//done
	return completeKey, nil
}
//save does the actual saving of a card to the datastore
//separate function to clean up code
func save(c context.Context, key *datastore.Key, customer CustomerDatastore) (*datastore.Key, error) {
	//save customer
	completeKey, err := datastore.Put(c, key, &customer)
	if err != nil {
		return key, err
	}

	//save customer to memcache
	//have to generate a memcache key b/c memcache keys must be strings
	mKey := strconv.FormatInt(completeKey.IntID(), 10)
	err = memcacheutils.Save(c, mKey, customer)
	if err != nil {
		return completeKey, err
	}

	//done
	return completeKey, nil
}
//Find gets the data for a given user id
//this returns all the info on a user
//first memcache is checked for the data, then the datastore
func Find(c context.Context, userId int64) (User, error) {
	//check for card in memcache
	var r User
	userIdStr := strconv.FormatInt(userId, 10)
	_, err := memcache.Gob.Get(c, userIdStr, &r)
	if err == nil {
		return r, nil
	}

	//user data not found in memcache
	//look up data in datastore
	//save to memcache after it is found
	if err == memcache.ErrCacheMiss {
		key := getUserKeyFromId(c, userId)
		q := datastore.NewQuery(datastoreKind).Filter("__key__ =", key).Limit(1)
		result := make([]User, 0, 1)
		_, err := q.GetAll(c, &result)
		if err != nil {
			return User{}, err
		}

		//return error if no result exists
		if len(result) == 0 {
			return User{}, ErrUserDoesNotExist
		}

		//one result
		userData := result[0]

		//save to memcache
		//ignore errors since we already got the data
		memcacheutils.Save(c, userIdStr, userData)

		//done
		return userData, nil

	} else {
		return User{}, err
	}

	return User{}, err
}
//Charge charges a credit card
func Charge(w http.ResponseWriter, r *http.Request) {
	//get form values
	datastoreId := r.FormValue("datastoreId")
	customerName := r.FormValue("customerName")
	amount := r.FormValue("amount")
	invoice := r.FormValue("invoice")
	poNum := r.FormValue("po")

	//validation
	if len(datastoreId) == 0 {
		output.Error(ErrMissingInput, "A customer ID should have been submitted automatically but was not. Please contact an administrator.", w, r)
		return
	}
	if len(amount) == 0 {
		output.Error(ErrMissingInput, "No amount was provided. You cannot charge a card nothing!", w, r)
		return
	}

	//get amount as cents
	amountCents, err := getAmountAsIntCents(amount)
	if err != nil {
		output.Error(err, "An error occured while converting the amount to charge into cents. Please try again or contact an administrator.", w, r)
		return
	}

	//check if amount is greater than the minimum charge
	//min charge may be greater than 0 because of transactions costs
	//for example, stripe takes 30 cents...it does not make sense to charge a card for < 30 cents
	if amountCents < minCharge {
		output.Error(ErrChargeAmountTooLow, "You must charge at least "+strconv.FormatInt(minCharge, 10)+" cents.", w, r)
		return
	}

	//create context
	//need to adjust deadline in case stripe takes longer than 5 seconds
	//default timeout for a urlfetch is 5 seconds
	//sometimes charging a card through stripe api takes longer
	//calls seems to take roughly 2 seconds normally with a few near 5 seconds (old deadline)
	//the call might still complete via stripe but appengine will return to the gui that it failed
	//10 secodns is a bit over generous but covers even really strange senarios
	c := appengine.NewContext(r)
	c, _ = context.WithTimeout(c, 10*time.Second)

	//look up stripe customer id from datastore
	datastoreIdInt, _ := strconv.ParseInt(datastoreId, 10, 64)
	custData, err := findByDatastoreId(c, datastoreIdInt)
	if err != nil {
		output.Error(err, "An error occured while looking up the customer's Stripe information.", w, r)
		return
	}

	//make sure customer name matches
	//just another catch in case of strange errors and mismatched data
	if customerName != custData.CustomerName {
		output.Error(err, "The customer name did not match the data for the customer ID. Please log out and try again.", w, r)
		return
	}

	//get username of logged in user
	//used for tracking who processed a charge
	//for audits and reports
	session := sessionutils.Get(r)
	username := session.Values["username"].(string)

	//init stripe
	sc := createAppengineStripeClient(c)

	//build charge object
	chargeParams := &stripe.ChargeParams{
		Customer:  custData.StripeCustomerToken,
		Amount:    amountCents,
		Currency:  currency,
		Desc:      "Charge for invoice: " + invoice + ", purchase order: " + poNum + ".",
		Statement: formatStatementDescriptor(),
	}

	//add metadata to charge
	//used for reports and receipts
	chargeParams.AddMeta("customer_name", customerName)
	chargeParams.AddMeta("datastore_id", datastoreId)
	chargeParams.AddMeta("customer_id", custData.CustomerId)
	chargeParams.AddMeta("invoice_num", invoice)
	chargeParams.AddMeta("po_num", poNum)
	chargeParams.AddMeta("charged_by", username)

	//process the charge
	chg, err := sc.Charges.New(chargeParams)

	//handle errors
	//*url.Error can be thrown if urlfetch reaches timeout (request took too long to complete)
	//*stripe.Error is a error with the stripe api and should return a human readable error message
	if err != nil {
		errorMsg := ""

		switch err.(type) {
		default:
			errorMsg = "There was an error processing this charge. Please check the Report to see if this charge was successful."
			break
		case *url.Error:
			errorMsg = "Charging this card timed out. The charge may have succeeded anyway. Please check the Report to see if this charge was successful."
			break
		case *stripe.Error:
			stripeErr := err.(*stripe.Error)
			errorMsg = stripeErr.Msg
		}

		output.Error(ErrStripe, errorMsg, w, r)
		return
	}

	//charge successful
	//save charge to memcache
	//less data to get from stripe if receipt is needed
	//errors are ignores since if we can't save this data to memcache we can always get it from the datastore/stripe
	memcacheutils.Save(c, chg.ID, chg)

	//save count of card types
	//used for negotiating rates with Stripe and just extra info
	saveChargeDetails(c, chg)

	//build struct to output a success message to the client
	out := chargeSuccessful{
		CustomerName:   customerName,
		Cardholder:     custData.Cardholder,
		CardExpiration: custData.CardExpiration,
		CardLast4:      custData.CardLast4,
		Amount:         amount,
		Invoice:        invoice,
		Po:             poNum,
		Datetime:       timestamps.ISO8601(),
		ChargeId:       chg.ID,
	}
	output.Success("cardCharged", out, w)
	return
}
//Show builds an html page that display a receipt
//this is a very boring, plain test, monospaced font page designed for easy printing and reading
//the receipt is generated from the charge id
//the data for the charge may be in memcache or will have to be retrieved from stripe
func Show(w http.ResponseWriter, r *http.Request) {
	//get charge id from form value
	chargeId := r.FormValue("chg_id")

	//try looking up charge data in memcache
	var chg *stripe.Charge
	c := appengine.NewContext(r)
	_, err := memcache.Gob.Get(c, chargeId, &chg)

	//charge not found in memcache
	//look up charge data from stripe
	if err == memcache.ErrCacheMiss {
		//init stripe
		c := appengine.NewContext(r)
		stripe.SetBackend(stripe.APIBackend, nil)
		stripe.SetHTTPClient(urlfetch.Client(c))

		//get charge data
		chg, err = charge.Get(chargeId, nil)
		if err != nil {
			fmt.Fprint(w, "An error occured and the receipt cannot be displayed.\n")
			fmt.Fprint(w, err)
			return
		}

		//save to memcache
		//just in case we want to view the receipt again
		memcacheutils.Save(c, chg.ID, chg)
	}

	//extract charge data
	d := chargeutils.ExtractData(chg)

	//get company info
	info, err := getCompanyInfo(r)
	name, street, suite, city, state, postal, country, phone := "", "", "", "", "", "", "", ""
	if err == ErrCompanyDataDoesNotExist {
		name = "**Company info has not been set yet.**"
		street = "**Please contact an administrator to fix this.**"
	} else {
		name = info.CompanyName
		street = info.Street
		suite = info.Suite
		city = info.City
		state = info.State
		postal = info.PostalCode
		country = info.Country
		phone = info.PhoneNum
	}

	//display receipt
	output := templateData{
		CompanyName: name,
		Street:      street,
		Suite:       suite,
		City:        city,
		State:       state,
		Postal:      postal,
		Country:     country,
		PhoneNum:    phone,
		Customer:    d.Customer,
		Cardholder:  d.Cardholder,
		CardBrand:   d.CardBrand,
		LastFour:    d.LastFour,
		Expiration:  d.Expiration,
		Captured:    d.CapturedStr,
		Timestamp:   d.Timestamp,
		Amount:      d.AmountDollars,
		Invoice:     d.Invoice,
		Po:          d.Po,
	}
	templates.Load(w, "receipt", output)
	return
}