//Root is used to show the login page of the app
//when a user browses to the page (usually just the domain minus any path), the user is checked for a session
//if a session exists, the app attempts to auto-login the user
//otherwise a user is shown the log in prompt
//this also handles the "first run" of the app in which no users exist yet...it forces creation of the "super admin"
func Root(w http.ResponseWriter, r *http.Request) {
	//check that session store was initialized correctly
	if err := sessionutils.CheckSession(); err != nil {
		notificationPage(w, "panel-danger", sessionInitError, err, "btn-default", "/", "Go Back")
		return
	}

	//check that stripe private key and statement desecriptor were read correctly
	if err := card.CheckStripe(); err != nil {
		notificationPage(w, "panel-danger", sessionInitError, err, "btn-default", "/", "Go Back")
		return
	}

	//check if the admin user exists
	//redirect user to create admin if it does not exist
	err := users.DoesAdminExist(r)
	if err == users.ErrAdminDoesNotExist {
		http.Redirect(w, r, "/setup/", http.StatusFound)
		return
	} else if err != nil {
		notificationPage(w, "panel-danger", adminInitError, err, "btn-default", "/", "Go Back")
		return
	}

	//check if user is already signed in
	//if user is already logged in, redirect to /main/ page
	session := sessionutils.Get(r)
	if session.IsNew == false {
		uId := session.Values["user_id"].(int64)
		c := appengine.NewContext(r)
		u, err := users.Find(c, uId)
		if err != nil {
			sessionutils.Destroy(w, r)
			notificationPage(w, "panel-danger", "Autologin Error", "There was an issue looking up your user account. Please go back and try logging in.", "btn-default", "/", "Go Back")
			return
		}

		//user data was found
		//check if user is allowed access
		if users.AllowedAccess(u) == false {
			sessionutils.Destroy(w, r)
			notificationPage(w, "panel-danger", "Autologin Error", "You are not allowed access. Please contact an administrator.", "btn-default", "/", "Go Back")
		}

		//user account is found an allowed access
		//redirect user
		http.Redirect(w, r, "/main/", http.StatusFound)
		return
	}

	//load the login page
	templates.Load(w, "root", nil)
	return
}
//CreateAdminShow loads the page used to create the initial admin user
//this is done only upon the app running for the first time (per project on app engine since nothing exists in this project's datastore yet)
func CreateAdminShow(w http.ResponseWriter, r *http.Request) {
	//check if the admin user already exists
	//no need to show this page if it does exist
	err := users.DoesAdminExist(r)
	if err == nil {
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}

	templates.Load(w, "create-admin", nil)
	return
}
//Diagnostics shows a bunch of app engine's information for the app/project
//useful for figuring out which version of an app is serving
func Diagnostics(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	out := map[string]interface{}{
		"App ID":                   appengine.AppID(c),
		"Instance ID":              appengine.InstanceID(),
		"Default Version Hostname": appengine.DefaultVersionHostname(c),
		"Version ID":               appengine.VersionID(c),
		"Datacenter":               appengine.Datacenter(c),
		"Module Name":              appengine.ModuleName(c),
		"Server Software":          appengine.ServerSoftware(),
	}

	templates.Load(w, "diagnostics", out)
	return
}
//notificationPage is used to show html page for errors
//same as pages.notificationPage but have to have separate function b/c of dependency circle
func notificationPage(w http.ResponseWriter, panelType, title string, err interface{}, btnType, btnPath, btnText string) {
	templates.Load(w, "notifications", templates.NotificationPage{panelType, title, err, btnType, btnPath, btnText})
	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
}
//Main loads the main UI of the app
//this is the page the user sees once they are logged in
//this ui is a single page app and holds almost all the functionality of the app
//the user only sees the parts of the ui they have access to...the rest is removed via go's contemplating
//we also check if this page was loaded with a bunch of extra data in the url...this would be used to perform the api-like semi-automated charging of the card
//  if a link to the page has a "customer_id" form value, this will automatically find the customer's card data and show it in the panel
//  if "amount", "invoice", and/or "po" form values are given, these will also automatically be filled into the charge panel's form
//  if "customer_id" is not given, no auto filling will occur of any fields
//  "amount" must be in cents
//  card is not automatically charged, user still has to click "charge" button
func Main(w http.ResponseWriter, r *http.Request) {
	//placeholder for sending data back to template
	var tempData autoloader

	//get logged in user data
	//catch instances where session is not working and redirect user to log in page
	//use the user's data to show/hide certain parts of the ui per the users access rights
	session := sessionutils.Get(r)
	if session.IsNew == true {
		notificationPage(w, "panel-danger", "Cannot Load Page", "Your session has expired or there is an error.  Please try logging in again or contact an administrator.", "btn-default", "/", "Log In")
		return
	}

	userId := session.Values["user_id"].(int64)

	c := appengine.NewContext(r)
	user, err := users.Find(c, userId)
	if err != nil {
		notificationPage(w, "panel-danger", "Cannot Load Page", err, "btn-default", "/", "Try Again")
		return
	}

	//check for url form values for autofilling charge panel
	//if data in url does not exist, just load the page with user data only
	custId := r.FormValue("customer_id")
	if len(custId) == 0 {
		tempData.UserData = user
		templates.Load(w, "main", tempData)
		return
	}

	//data in url does exist
	//look up card data by customer id
	//get the card data to show in the panel so user can visually confirm they are charging the correct card
	//if an error occurs, just load the page normally
	custData, err := card.FindByCustId(c, custId)
	if err != nil {
		tempData.Error = "The form could not be autofilled because the customer ID you provided could not be found.  The ID is either incorrect or the customer's credit card has not been added yet."
		tempData.UserData = user
		templates.Load(w, "main", tempData)
		return
	}

	tempData.CardData = custData
	tempData.UserData = user

	//if amount was given, it is in cents
	//display it in html input as dollars
	amountUrl := r.FormValue("amount")
	amountFloat, _ := strconv.ParseFloat(amountUrl, 64)
	amountDollars := amountFloat / 100
	tempData.Amount = amountDollars

	//check for other form values and build template
	tempData.Invoice = r.FormValue("invoice")
	tempData.Po = r.FormValue("po")

	//load the page with the card data
	templates.Load(w, "main", tempData)
	return
}
//Report gets the data for charges and refunds by the defined filters (date range and customer) and builds the reports page
//the reports show up in a different page so they are easily printable and more easily inspected
//date range is inclusive of start and end days
func Report(w http.ResponseWriter, r *http.Request) {
	//get form valuess
	datastoreId := r.FormValue("customer-id")
	startString := r.FormValue("start-date")
	endString := r.FormValue("end-date")
	hoursToUTC := r.FormValue("timezone")

	//get report data form stripe
	//make sure inputs are given
	if len(startString) == 0 {
		output.Error(ErrMissingInput, "You must supply a 'start-date'.", w, r)
		return
	}
	if len(endString) == 0 {
		output.Error(ErrMissingInput, "You must supply a 'end-date'.", w, r)
		return
	}
	if len(hoursToUTC) == 0 {
		output.Error(ErrMissingInput, "You must supply a 'timezone'.", w, r)
		return
	}

	//get timezone offset
	//adjust for the local timezone the user is in so that the date range is correct
	//hoursToUTC is a number generated by JS (-4 for EST)
	tzOffset := calcTzOffset(hoursToUTC)

	//get datetimes from provided strings
	startDt, err := time.Parse("2006-01-02 -0700", startString+" "+tzOffset)
	if err != nil {
		output.Error(err, "Could not convert start date to a time.Time datetime.", w, r)
		return
	}
	endDt, err := time.Parse("2006-01-02 -0700", endString+" "+tzOffset)
	if err != nil {
		output.Error(err, "Could not convert end date to a time.Time datetime.", w, r)
		return
	}

	//get end of day datetime
	//need to get 23:59:59 so we include the whole day
	endDt = endDt.Add((24*60-1)*time.Minute + (59 * time.Second))

	//get unix timestamps
	//stripe only accepts timestamps for filtering charges
	startUnix := startDt.Unix()
	endUnix := endDt.Unix()

	//init stripe
	c := appengine.NewContext(r)
	sc := createAppengineStripeClient(c)

	//retrieve data from stripe
	//date is a range inclusive of the days the user chose
	//limit of 100 is the max per stripe
	params := &stripe.ChargeListParams{}
	params.Filters.AddFilter("created", "gte", strconv.FormatInt(startUnix, 10))
	params.Filters.AddFilter("created", "lte", strconv.FormatInt(endUnix, 10))
	params.Filters.AddFilter("limit", "", "100")

	//check if we need to filter by a specific customer
	//look up stripe customer id by the datastore id
	if len(datastoreId) != 0 {
		datastoreIdInt, _ := strconv.ParseInt(datastoreId, 10, 64)
		custData, err := findByDatastoreId(c, datastoreIdInt)
		if err != nil {
			output.Error(err, "An error occured and this report could not be generated.", w, r)
			return
		}

		params.Filters.AddFilter("customer", "", custData.StripeCustomerToken)
	}

	//get results
	//loop through each charge and extract charge data
	//add up total amount of all charges
	charges := sc.Charges.List(params)
	data := make([]chargeutils.Data, 0, 10)
	var amountTotal uint64 = 0
	var numCharges uint16 = 0
	for charges.Next() {
		//get each charges data
		chg := charges.Charge()
		d := chargeutils.ExtractData(chg)

		//make sure this charge was captured
		//do not count charges that failed
		if d.Captured == false {
			continue
		}

		data = append(data, d)

		//increment totals
		amountTotal += d.AmountCents
		numCharges++
	}

	//convert total amount to dollars
	amountTotalDollars := strconv.FormatFloat((float64(amountTotal) / 100), 'f', 2, 64)

	//retrieve refunds
	eventParams := &stripe.EventListParams{}
	eventParams.Filters.AddFilter("created", "gte", strconv.FormatInt(startUnix, 10))
	eventParams.Filters.AddFilter("created", "lte", strconv.FormatInt(endUnix, 10))
	eventParams.Filters.AddFilter("limit", "", "100")
	eventParams.Filters.AddFilter("type", "", "charge.refunded")

	events := sc.Events.List(eventParams)
	refunds := chargeutils.ExtractRefunds(events)

	//get logged in user's data
	//for determining if receipt/refund buttons need to be hidden or shown based on user's access rights
	session := sessionutils.Get(r)
	userId := session.Values["user_id"].(int64)
	userdata, _ := users.Find(c, userId)

	//store data for building template
	result := reportData{
		UserData:    userdata,
		StartDate:   startDt,
		EndDate:     endDt,
		Charges:     data,
		Refunds:     refunds,
		TotalAmount: amountTotalDollars,
		NumCharges:  numCharges,
	}

	//build template to display report
	//separate page in gui
	templates.Load(w, "report", result)
	return
}