package main

import (
		"net/http"
		"log"
		"sync"
		"regexp"
		"html/template"
		"database/sql"
		_ "github.com/go-sql-driver/mysql"
		"fmt"
		"encoding/json"
		// "io"
		"strconv"
		"bytes"
)

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"`
	Term		int 	`json:"term"`
	Ltv 	int 	`json:"ltv"`
	Dti 	int 	`json:"dti"`
	Hoi		int 	`json:"hoi"`
	Interest	int 	`json:"interest"`
	Lender		string 	`json:"lender"`
	MiName		string 	`json:"miName"`
	MiAmount	int 	`json:"miAmount"`
	Fees		[]Fee 	`json:"fees"`
	Name		string 	`json:"name"`
}

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"),
}

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

	// Inner join should always be valid because a borrower is a required
	// foreign key.
	row := db.QueryRow(
		"SELECT * FROM estimate "+
		"WHERE id = ? " + 
		"INNER JOIN borrower ON estimate.borrower = borrower.id",
	id)
	
	if err := row.Scan(
		&estimate.Id,
		&estimate.User,
		&estimate.Borrower.Id,
		&estimate.Transaction,
		&estimate.Price,
		&estimate.Property,
		&estimate.Occupancy,
		&estimate.Zip,
		&estimate.Pud,
		)
	err != nil {
		return estimate, fmt.Errorf("Estimate scanning error: %v", err)
        }

	return estimate, nil
}

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
}

// Fetch fees from the database
func getFeesTemp(db *sql.DB, user int) ([]FeeTemplate, error) {
	var fees []FeeTemplate

	rows, err := db.Query(
		"SELECT * FROM fee_template " +
		"WHERE user_id = ? OR user_id = 0",
	user)
	
	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)
	}
	
	est, err := getEstimate(db, 1)
	fmt.Printf("the estimate: %v,\nthe error %v\n", est, err)
	// getMi(db, getEstimate(db, 1), getBorrower(db, 1))

	return fees, nil
}

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

	query := `SELECT
	loan.id, loan.amount, loan.term, loan.interest, loan.ltv, loan.dti,
	loan.hoi, loan.mi_name, loan.mi_amount
	loan_type.id, loan_type.user_id, loan_type.branch_id, loan_type.name
	FROM loan INNER JOIN loan_type ON loan.type_id = loan_type(id)
	WHERE loan.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.MiName,
			&loan.MiAmount,
			&loan.Type.Id,
			&loan.Type.User,
			&loan.Type.Branch,
			&loan.Type.Name,
			)
		err != nil {
			return loans, fmt.Errorf("Loans scanning error: %v", err)
        }

		loans = append(loans, loan)
	}
	
	est, err := getEstimate(db, 1)
	fmt.Printf("the loan: %v,\nthe error %v\n", est, err)
	// getMi(db, getEstimate(db, 1), getBorrower(db, 1))

	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
}

func getMi(db *sql.DB, estimate Estimate, borrower Borrower) {
	// body := map[string]string{
	// 	"zipCode": estimate.Zip,
	// 	// "stateCode": "CA",
	// 	// "address": "",
	// 	"propertyTypeCode": "SFO",
	// 	"occupancyTypeCode": "PRS",
	// 	"loanPurposeCode": "PUR",
	// 	"loanAmount": "1000000",
	// 	"loanToValue": "LTV95",
	// 	"amortizationTerm": "A30",
	// 	"loanTypeCode": "FXD",
	// 	"duLpDecisionCode": "DAE",
	// 	"loanProgramCodes": [],
	// 	"debtToIncome": "5",
	// 	"wholesaleLoan": 0,
	// 	"coveragePercentageCode": "L30",
	// 	"productCode": "BPM",
	// 	"renewalTypeCode": "CON",
	// 	"numberOfBorrowers": 1,
	// 	"coBorrowerCreditScores": [],
	// 	"borrowerCreditScore": "740",
	// 	"masterPolicy": null,
	// 	"selfEmployedIndicator": false,
	// 	"armType": "",
	// 	"userId": 44504
	// }
	
	var propertyCodes = map[string]string {
		"Single Family Attached": "SFO",
		"Single Family Detached": "SFO",
		"Condominium Lo-rise": "CON",
		"Condominium Hi-rise": "CON",
		}

	/* var occupancyCodes = map[string]string {
		"Primary Residence": "PRS",
		"Second Home": "SCH",
		"Condominium Lo-rise": "CON",
		"Condominium Hi-rise": "CON",
		} */

	body, _ := json.Marshal(map[string]any{
		"zipCode": estimate.Zip,
		// "stateCode": "CA",
		// "address": "",
		"propertyTypeCode": propertyCodes[estimate.Property],
		"occupancyTypeCode": "PRS",
		"loanPurposeCode": "PUR",
		"loanAmount": strconv.Itoa(500000 / 100),
		"loanToValue": "LTV95",
		"amortizationTerm": "A30",
		"loanTypeCode": "FXD",
		"duLpDecisionCode": "DAE",
		"debtToIncome": 5,
		"wholesaleLoan": 0,
		"coveragePercentageCode": "L30",
		"productCode": "BPM",
		"renewalTypeCode": "CON",
		"numberOfBorrowers": 1,
		"borrowerCreditScore": strconv.Itoa(borrower.Credit),
		"masterPolicy": nil,
		"selfEmployedIndicator": false,
		"armType": "",
		"userId": 44504,
	})
	log.Println(bytes.NewBuffer(body))

}

func validateEstimate() {
	return
}

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" ]
    case match(p, "/assets", &args):
        page = pages[ "app" ]
    default:
        http.NotFound(w, r)
        return
    }

    page.Render(w)
}

func api(w http.ResponseWriter, r *http.Request) {
	var args []string
	// var response 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 {
		print("Bad database configuration: %v", err)
		panic(err)
		// maybe os.Exit(1) instead
	}

	switch {
	case match(p, "/api/loans", &args):
		resp, err := getLoanType(db, 0, 0, true)

		if resp != nil {
			json.NewEncoder(w).Encode(resp)
		} else {
			json.NewEncoder(w).Encode(err)
		}

	case match(p, "/api/fees", &args):
		resp, err := getFeesTemp(db, 0)

		if resp != nil {
			json.NewEncoder(w).Encode(resp)
		} else {
			json.NewEncoder(w).Encode(err)
		}
	}

}

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

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