package main

import (
		"net/http"
		"net/mail"
		"log"
		"sync"
		"regexp"
		"html/template"
		"database/sql"
		_ "github.com/go-sql-driver/mysql"
		"fmt"
		"encoding/json"
		"strconv"
		"bytes"
		"time"
		"errors"
		"strings"
		// pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
		"github.com/golang-jwt/jwt/v4"
)

type User struct {
	Id		int 	`json:"id"`
	Email 	string 	`json:"email"`
	FirstName 	string 	`json:"firstName"`
	LastName 	string 	`json:"lastName"`
	BranchId 	int 	`json:"branchId"`
	Status 	string 	`json:"status"`
	Country 	string 	`json:"country"`
	Title 	string 	`json:"title"`
	Verified 	bool 	`json:"verified"`
	Role 	string 	`json:"role"`
	Password 	string 	`json:"password,omitempty"`
}

type UserClaims struct {
	Id 	int 	`json:"id"`
	Role 	string 	`json:"role"`
	Exp 	string 	`json:"exp"`
}

type Page struct {
	tpl *template.Template
	Title string
	Name string
}

type Borrower struct {
	Id	int	`json:"id"`
	Credit	int 	`json:"credit"`
	Income	int 	`json:"income"`
	Num	int	`json:"num"`
}

type FeeTemplate struct {
	Id		int 	`json:"id"`
	User	int 	`json:"user"`
	Branch	int 	`json:"branch"`
	Amount	int 	`json:"amount"`
	Perc	int 	`json:"perc"`
	Type	string 	`json:"type"`
	Notes	string 	`json:"notes"`
	Name	string 	`json:"name"`
	Category	string 	`json:"category"`
	Auto	bool 	`json:"auto"`
}

type Fee struct {
	Id		int 	`json:"id"`
	LoanId	int 	`json:"loan_id"`
	Amount	int 	`json:"amount"`
	Perc	int 	`json:"perc"`
	Type	string 	`json:"type"`
	Notes	string 	`json:"notes"`
	Name	string 	`json:"name"`
	Category	string 	`json:"category"`
}

type LoanType struct {
	Id 	int 	`json:"id"`
	User 	int 	`json:"user"`
	Branch 	int 	`json:"branch"`
	Name 	string 	`json:"name"`
}

type Loan struct {
	Id 	int	`json:id`
	EstimateId	int	`json:estimate_id`
	Type	LoanType	`json:"loanType"`
	Amount	int 	`json:"loanAmount"`
	Amortization	string	`json:"loanAmount"`
	Term		int 	`json:"term"`
	Ltv 	float32 	`json:"ltv"`
	Dti 	float32 	`json:"dti"`
	Hoi		int 	`json:"hoi"`
	Interest	int 	`json:"interest"`
	Mi 	MI 	`json:"mi"`
	Fees		[]Fee 	`json:"fees"`
	Name		string 	`json:"name"`
}

type MI struct {
	Type	string	`json:"user"`
	Label	string	`json:"label"`
	Lender	string	`json:"lender"`
	Rate	float32	`json:"rate"`
	Premium	float32	`json:"premium"`
	Upfront	float32	`json:"upfront"`
	Monthly	bool	`json:"monthly"`
	FiveYearTotal	float32	`json:"fiveYearTotal"`
	InitialAllInPremium	float32	`json:"initialAllInPremium"`
	InitialAllInRate	float32	`json:"initialAllInRate"`
	InitialAmount	float32	`json:"initialAmount"`
}

type Result struct {
	User		int 	`json:"user"`
	Borrower	Borrower 	`json:"borrower"`
	Transaction	string 	`json:"transaction"`
	Price		int 	`json:"price"`
	Property	string 	`json:"property"`
	Occupancy	string	`json:"occupancy"`
	Zip		string 	`json:"zip"`
	Pud		bool 	`json:"pud"`
	Loans 	[]Loan 	`json:"loans"`
}

type Estimate struct {
	Id		int 	`json:"id"`
	User		int 	`json:"user"`
	Borrower	Borrower 	`json:"borrower"`
	Transaction	string 	`json:"transaction"`
	Price		int 	`json:"price"`
	Property	string 	`json:"property"`
	Occupancy	string	`json:"occupancy"`
	Zip		string 	`json:"zip"`
	Pud		bool 	`json:"pud"`
	Loans 	[]Loan 	`json:"loans"`
}

var (
    regexen = make(map[string]*regexp.Regexp)
    relock  sync.Mutex
	address = "127.0.0.1:8001"
)

var paths = map[string]string {
	"home": "home.tpl",
	"terms": "terms.tpl",
	"app": "app.tpl",
}

var pages = map[string]Page {
	"home": cache("home", "Home"),
	"terms": cache("terms", "Terms and Conditions"),
	"app": cache("app", "App"),
}

var roles = map[string]int{
	"User": 1,
	"Manager": 2,
	"Admin": 3,
}

// Used to validate claim in JWT token body. Checks if user id is greater than
// zero and time format is valid
func (c UserClaims) Valid() error {
	if c.Id < 1 { return errors.New("Invalid id") }
	t, err := time.Parse(time.UnixDate, c.Exp)
	if err != nil { return err }
	if t.Before(time.Now()) { return errors.New("Token expired.") }
	return err
}

func cache(name string, title string) Page {
	var p = []string{"master.tpl", paths[name]}
	tpl := template.Must(template.ParseFiles(p...))
	return Page{tpl: tpl,
				Title: title,
				Name: name,
				}
}

func (page Page) Render(w http.ResponseWriter) {
	err := page.tpl.Execute(w, page)
	if err != nil {
		log.Print(err)
	}
}

func match(path, pattern string, args *[]string) bool {
	relock.Lock()
	defer relock.Unlock()
	regex := regexen[pattern]

	if regex == nil {
		regex = regexp.MustCompile("^" + pattern + "$")
		regexen[pattern] = regex
	}

	matches := regex.FindStringSubmatch(path)
	if len(matches) <= 0 {
		return false
	}

	*args = matches[1:]
	return true
}

func getLoanType(
	db *sql.DB,
	user int,
	branch int,
	isUser bool) ([]LoanType, error) {
	var loans []LoanType

	// Should be changed to specify user
	rows, err :=
	db.Query(`SELECT * FROM loan_type WHERE user_id = ? AND branch_id = ? ` +
	"OR (user_id = 0 AND branch_id = 0)", user, branch)

	if err != nil {
		return nil, fmt.Errorf("loan_type error: %v", err)
	}

	defer rows.Close()

	for rows.Next() {
		var loan LoanType

		if err := rows.Scan(
			&loan.Id,
			&loan.User,
			&loan.Branch,
			&loan.Name)
		err != nil {
			log.Printf("Error occured fetching loan: %v", err)
			return nil, fmt.Errorf("Error occured fetching loan: %v", err)
        }

		loans = append(loans, loan)
	}

	log.Printf("The loans: %v", loans)
	return loans, nil
}

func getEstimate(db *sql.DB, id int) (Estimate, error) {
	var estimate Estimate
	var err error

	query := `SELECT e.id, e.user_id, e.transaction,
	e.price, e.property, e.occupancy, e.zip, e.pud,
	b.id, b.credit_score, b.monthly_income, b.num
	FROM estimate e
	INNER JOIN borrower b ON e.borrower_id = b.id
	WHERE e.id = ?
	`
	// Inner join should always be valid because a borrower is a required
	// foreign key.
	row := db.QueryRow(query, id)
	
	if err = row.Scan(
		&estimate.Id,
		&estimate.User,
		&estimate.Transaction,
		&estimate.Price,
		&estimate.Property,
		&estimate.Occupancy,
		&estimate.Zip,
		&estimate.Pud,
		&estimate.Borrower.Id,
		&estimate.Borrower.Credit,
		&estimate.Borrower.Income,
		&estimate.Borrower.Num,
		)
	err != nil {
		return estimate, fmt.Errorf("Estimate scanning error: %v", err)
        }

	estimate.Loans, err = getLoans(db, estimate.Id)

	return estimate, err
}

func getFees(db *sql.DB, loan int) ([]Fee, error) {
	var fees []Fee

	rows, err := db.Query(
		"SELECT * FROM fees " +
		"WHERE loan_id = ?",
	loan)
	
	if err != nil {
		return nil, fmt.Errorf("Fee query error %v", err)
	}

	defer rows.Close()

	for rows.Next() {
		var fee Fee

		if err := rows.Scan(
			&fee.Id,
			&fee.LoanId,
			&fee.Amount,
			&fee.Perc,
			&fee.Type,
			&fee.Notes,
			&fee.Name,
			&fee.Category,
			)
		err != nil {
			return nil, fmt.Errorf("Fees scanning error: %v", err)
        }

		fees = append(fees, fee)
	}
	
	return fees, nil
}

func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
	var fees []FeeTemplate
	rows, err := db.Query(
		"SELECT * FROM fee_template " +
		"WHERE user_id = ? OR branch_id = ?",
	user, branch)
	
	if err != nil {
		return nil, fmt.Errorf("Fee template query error %v", err)
	}

	defer rows.Close()

	for rows.Next() {
		var fee FeeTemplate

		if err := rows.Scan(
			&fee.Id,
			&fee.User,
			&fee.Branch,
			&fee.Amount,
			&fee.Perc,
			&fee.Type,
			&fee.Notes,
			&fee.Name,
			&fee.Category,
			&fee.Auto)
		err != nil {
			return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
        }

		fees = append(fees, fee)
	}
	
	return fees, nil
}

// Fetch fees from the database
func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	var fees []FeeTemplate
	claims, err := getClaims(r)
	if err != nil { w.WriteHeader(500); return }
	users, err := queryUsers(db, claims.Id)
	if err != nil { w.WriteHeader(422); return }

	fees, err = fetchFeesTemp(db, claims.Id, users[0].BranchId)
	json.NewEncoder(w).Encode(fees)
}

func getMi(db *sql.DB, loan int) (MI, error) {
	var mi MI

	query := `SELECT
	type, label, lender, rate, premium, upfront, five_year_total,
	initial_premium, initial_rate, initial_amount
	FROM mi WHERE loan_id = ?`

	row := db.QueryRow(query, loan)

	if err := row.Scan(
		&mi.Type,
		&mi.Label,
		&mi.Lender,
		&mi.Rate,
		&mi.Premium,
		&mi.Upfront,
		&mi.FiveYearTotal,
		&mi.InitialAllInPremium,
		&mi.InitialAllInRate,
		&mi.InitialAmount,
		)
	err != nil {
		return mi, err
	}

	return mi, nil
}

func getLoans(db *sql.DB, estimate int) ([]Loan, error) {
	var loans []Loan

	query := `SELECT
	l.id, l.amount, l.term, l.interest, l.ltv, l.dti, l.hoi,
	lt.id, lt.user_id, lt.branch_id, lt.name
	FROM loan l INNER JOIN loan_type lt ON l.type_id = lt.id
	WHERE l.estimate_id = ?
	`
	rows, err := db.Query(query, estimate)
	
	if err != nil {
		return nil, fmt.Errorf("Loan query error %v", err)
	}

	defer rows.Close()

	for rows.Next() {
		var loan Loan

		if err := rows.Scan(
			&loan.Id,
			&loan.Amount,
			&loan.Term,
			&loan.Interest,
			&loan.Ltv,
			&loan.Dti,
			&loan.Hoi,
			&loan.Type.Id,
			&loan.Type.User,
			&loan.Type.Branch,
			&loan.Type.Name,
			)
		err != nil {
			return loans, fmt.Errorf("Loans scanning error: %v", err)
        }
		mi, err := getMi(db, loan.Id)
		if err != nil {
			return loans, err
        }
		loan.Mi = mi
		loans = append(loans, loan)
	}
	
	return loans, nil
}

func getBorrower(db *sql.DB, id int) (Borrower, error) {
	var borrower Borrower

	row := db.QueryRow(
		"SELECT * FROM borrower " +
		"WHERE id = ? LIMIT 1",
	id)

	if err := row.Scan(
		&borrower.Id,
		&borrower.Credit,
		&borrower.Income,
		&borrower.Num,
		)
	err != nil {
		return borrower, fmt.Errorf("Borrower scanning error: %v", err)
        }

	return borrower, nil
}

// Query Lender APIs and parse responses into MI structs
func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
	var err error
	var loan Loan = estimate.Loans[pos]

	var ltv = func(l float32) string {
		switch {
		case l > 95: return "LTV97"
		case l > 90: return "LTV95"
		case l > 85: return "LTV90"
		default: return "LTV85"
		}
	}

	var term = func(t int) string {
		switch {
		case t <= 10: return "A10"
		case t <= 15: return "A15"
		case t <= 20: return "A20"
		case t <= 25: return "A25"
		case t <= 30: return "A30"
		default: return "A40"
		}
	}

	var propertyCodes = map[string]string {
		"Single Attached": "SFO",
		"Single Detached": "SFO",
		"Condo Lo-rise": "CON",
		"Condo Hi-rise": "CON",
	}

	var purposeCodes = map[string]string {
		"Purchase": "PUR",
		"Refinance": "RRT",
	}

	body, err := json.Marshal(map[string]any{
		"zipCode": estimate.Zip,
		"stateCode": "CA",
		"address": "",
		"propertyTypeCode": propertyCodes[estimate.Property],
		"occupancyTypeCode": "PRS",
		"loanPurposeCode": purposeCodes[estimate.Transaction],
		"loanAmount": loan.Amount,
		"loanToValue": ltv(loan.Ltv),
		"amortizationTerm": term(loan.Term),
		"loanTypeCode": "FXD",
		"duLpDecisionCode": "DAE",
		"loanProgramCodes": []any{},
		"debtToIncome": loan.Dti,
		"wholesaleLoan": 0,
		"coveragePercentageCode": "L30",
		"productCode": "BPM",
		"renewalTypeCode": "CON",
		"numberOfBorrowers": 1,
		"coBorrowerCreditScores": []any{},
		"borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
		"masterPolicy": nil,
		"selfEmployedIndicator": false,
		"armType": "",
		"userId": 44504,
	})
	
	if err != nil {
		log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
		bytes.NewBuffer(body), err)
	}

	req, err := http.NewRequest("POST",
	"https://rate-gps.nationalmi.com/rates/productRateQuote",
	bytes.NewBuffer(body))
	req.Header.Add("Content-Type", "application/json")

	req.AddCookie(&http.Cookie{
		Name: "nmirategps_email",
		Value: config["NationalMIEmail"]})

	resp, err := http.DefaultClient.Do(req)
	var res map[string]interface{}
	var result []MI

	if resp.StatusCode != 200 {
		log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
		resp.Status, resp, req.Body, bytes.NewBuffer(body))
	} else {
		json.NewDecoder(resp.Body).Decode(&res)
		// estimate.Loans[pos].Mi = res
		// Parse res into result here
	}

	return result
}

// Make comparison PDF
func generatePDF(w http.ResponseWriter, db *sql.DB, r *http.Request) {
}

func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	var id int
	var role string
	var err error
	var user User
	json.NewDecoder(r.Body).Decode(&user)

	row := db.QueryRow(
		`SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
		user.Email, user.Password,
		)

	err = row.Scan(&id, &role)
	if err != nil {
		http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
		return
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256,
	UserClaims{ Id: id, Role: role,
		Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})

	tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
	if err != nil {
		log.Println("Token could not be signed: ", err, tokenStr)
		http.Error(w, "Token generation error.", http.StatusInternalServerError)
		return
	}

	cookie := http.Cookie{Name: "skouter",
	Value: tokenStr,
	Path: "/",
	Expires: time.Now().Add(time.Hour * 24)}
	http.SetCookie(w, &cookie)
	_, err = w.Write([]byte(tokenStr))
	if err != nil {
		http.Error(w,
		"Could not complete token write.",
		http.StatusInternalServerError)}
}

func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	claims, err := getClaims(r)
	// Will verify existing signature and expiry time
	token := jwt.NewWithClaims(jwt.SigningMethodHS256,
	UserClaims{ Id: claims.Id, Role: claims.Role,
		Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})

	tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
	if err != nil {
		log.Println("Token could not be signed: ", err, tokenStr)
		http.Error(w, "Token generation error.", http.StatusInternalServerError)
		return
	}

	cookie := http.Cookie{Name: "skouter",
	Value: tokenStr,
	Path: "/",
	Expires: time.Now().Add(time.Hour * 24)}
	http.SetCookie(w, &cookie)
	_, err = w.Write([]byte(tokenStr))
	if err != nil {
		http.Error(w,
		"Could not complete token write.",
		http.StatusInternalServerError)}
}

func validateEstimate() {
	return
}

func getClaims(r *http.Request) (UserClaims, error) {
	claims := new(UserClaims)
	_, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")

	if !found {
		return *claims, errors.New("Token not found")
	}

	// Pull token payload into UserClaims
	_, err := jwt.ParseWithClaims(tokenStr, claims,
	func(token *jwt.Token) (any, error) {
		return []byte(config["JWT_SECRET"]), nil
	})

	if err != nil {
		return *claims, err
	}

	if err = claims.Valid(); err != nil {
		return *claims, err
	}

	return *claims, nil
}

func guard(r *http.Request, required int) bool {
	claims, err := getClaims(r)
	if err != nil { return false }
	if roles[claims.Role] < required { return false }

	return true
}

func queryUsers(db *sql.DB, id int) ( []User, error ) {
	var users []User
	var query string
	var rows *sql.Rows
	var err error

	query = `SELECT
	u.id,
	u.email,
	u.first_name,
	u.last_name,
	u.branch_id,
	u.country,
	u.title,
	u.status,
	u.verified,
	u.role
	FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
	`
	rows, err = db.Query(query, id)


	if err != nil {
		return users, err
	}

	defer rows.Close()

	for rows.Next() {
		var user User

		if err := rows.Scan(
			&user.Id,
			&user.Email,
			&user.FirstName,
			&user.LastName,
			&user.BranchId,
			&user.Country,
			&user.Title,
			&user.Status,
			&user.Verified,
			&user.Role,
			)
		err != nil {
			return users, err
        }
		users = append(users, user)
	}

	// Prevents runtime panics
	if len(users) == 0 { return users, errors.New("User not found.") }

	return users, nil
}

func insertUser(db *sql.DB, user User) (User, error){
	var query string
	var row *sql.Row
	var err error
	var id int // Inserted user's id

	query = `INSERT INTO user
	(
		email,
		first_name,
		last_name,
		password,
		created,
		role,
		verified,
		last_login
	)
	VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
	RETURNING id 
	`
	row = db.QueryRow(query,
		user.Email,
		user.FirstName,
		user.LastName,
		user.Password,
		user.Role,
		user.Verified,
	)

	err = row.Scan(&id)
	if err != nil { return User{}, err }

	users, err := queryUsers(db, id)
	if err != nil { return User{}, err }

	return users[0], nil
}

func updateUser(user User, db *sql.DB) error {
	query := `
	UPDATE user
	SET
	email = CASE @e := ? WHEN '' THEN email ELSE @e END,
	first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
	last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
	role = CASE @r := ? WHEN '' THEN role ELSE @r END,
	password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
	WHERE id = ?
	`

	_, err := db.Exec(query,
	user.Email,
	user.FirstName,
	user.LastName,
	user.Role,
	user.Password,
	user.Id,
	)

	return err
}


func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	claims, err := getClaims(r)
	if err != nil { w.WriteHeader(500); return }
	users, err := queryUsers(db, claims.Id)
	if err != nil { w.WriteHeader(422); log.Println(err); return }
	json.NewEncoder(w).Encode(users)
}

func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	users, err := queryUsers(db, 0)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(users)
}

// Updates a user using only specified values in the JSON body
func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	var user User
	err := json.NewDecoder(r.Body).Decode(&user)

	_, err = mail.ParseAddress(user.Email)
	if err != nil { http.Error(w, "Invalid email.", 422); return }

	if roles[user.Role] == 0 {
		http.Error(w, "Invalid role.", 422)
		return
	}

	err = updateUser(user, db)
	if err != nil { http.Error(w, "Bad form values.", 422); return }

	users, err := queryUsers(db, user.Id)
	if err != nil { http.Error(w, "Bad form values.", 422); return }
	json.NewEncoder(w).Encode(users[0])
}

// Update specified fields of the user specified in the claim
func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	claim, err := getClaims(r)
	var user User
	json.NewDecoder(r.Body).Decode(&user)

	// First check that the target user to be updated is the same as the claim id, and
	// their role is unchanged.
	if err != nil || claim.Id != user.Id {
		http.Error(w, "Target user's id does not match claim.", 401)
		return
	}

	if claim.Role != user.Role && user.Role != "" {
		http.Error(w, "Administrator required to escalate role.", 401)
		return
	}

	patchUser(w, db, r)
}

func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	var user User
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil {
		http.Error(w, "Invalid fields.", 422)
		return
	}

	query := `DELETE FROM user WHERE id = ?`
	_, err = db.Exec(query, user.Id)

	if err != nil {
		http.Error(w, "Could not delete.", 500)
	}
}

func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	var user User
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil { http.Error(w, "Invalid fields.", 422); return }

	_, err = mail.ParseAddress(user.Email)
	if err != nil { http.Error(w, "Invalid email.", 422); return }

	if roles[user.Role] == 0 {
		http.Error(w, "Invalid role.", 422)
	}

	user, err = insertUser(db, user)
	if err != nil { http.Error(w, "Error creating user.", 422); return }

	json.NewEncoder(w).Encode(user)
}

func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
	var estimate Estimate
	err := json.NewDecoder(r.Body).Decode(&estimate)
	if err != nil { http.Error(w, "Invalid fields.", 422); return }

	if err != nil { http.Error(w, "Error creating user.", 422); return }

	json.NewEncoder(w).Encode(estimate)
}

func api(w http.ResponseWriter, r *http.Request) {
	var args []string

	p := r.URL.Path
	db, err := sql.Open("mysql",
		fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
			config["DBUser"],
			config["DBPass"]))

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	err = db.Ping()
	if err != nil {
		fmt.Println("Bad database configuration: %v\n", err)
		panic(err)
		// maybe os.Exit(1) instead
	}
	
	switch {
	case match(p, "/api/login", &args) &&
	r.Method == http.MethodPost:
		login(w, db, r)
	case match(p, "/api/token", &args) &&
	r.Method == http.MethodGet && guard(r, 1):
		getToken(w, db, r)
	case match(p, "/api/users", &args) && // Array of all users
	r.Method == http.MethodGet && guard(r, 3):
		getUsers(w, db, r)
	case match(p, "/api/user", &args) &&
	r.Method == http.MethodGet && guard(r, 1):
		getUser(w, db, r)
	case match(p, "/api/user", &args) &&
	r.Method == http.MethodPost &&
	guard(r, 3):
		createUser(w, db, r)
	case match(p, "/api/user", &args) &&
	r.Method == http.MethodPatch &&
	guard(r, 3): // For admin to modify any user
		patchUser(w, db, r)
	case match(p, "/api/user", &args) &&
	r.Method == http.MethodPatch &&
	guard(r, 2): // For employees to modify own accounts
		patchSelf(w, db, r)
	case match(p, "/api/user", &args) &&
	r.Method == http.MethodDelete &&
	guard(r, 3):
		deleteUser(w, db, r)
	case match(p, "/api/fees", &args) &&
	r.Method == http.MethodGet &&
	guard(r, 1):
		getFeesTemp(w, db, r)
	case match(p, "/api/estimate", &args) &&
	r.Method == http.MethodPost &&
	guard(r, 1):
		createEstimate(w, db, r)
	}

	db.Close()
}

func route(w http.ResponseWriter, r *http.Request) {
    var page Page
	var args []string
    p := r.URL.Path

    switch {
    case r.Method == "GET" && match(p, "/", &args):
        page = pages[ "home" ]
    case match(p, "/terms", &args):
        page = pages[ "terms" ]
    case match(p, "/app", &args):
        page = pages[ "app" ]
    default:
        http.NotFound(w, r)
        return
    }

    page.Render(w)
}

func main() {
	files := http.FileServer(http.Dir(""))

	http.Handle("/assets/", files)
	http.HandleFunc("/api/", api)
	http.HandleFunc("/", route)
	log.Fatal(http.ListenAndServe(address, nil))
}