package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"

	"github.com/valyala/fasttemplate"
)

//ClientInformationStruct export
type ClientsInformationStruct []struct {
	ClientID     string   `json:"client-id"`
	ClientSecret string   `json:"client-secret"`
	Domain       string   `json:"domain"`
	ServiceName  string   `json:"service-name"`
	ServiceImage string   `json:"service-image"`
	Scope        []string `json:"scope"`
}

func main() {
	http.HandleFunc("/login", loginHandler)
	http.Handle("/asset/", http.StripPrefix("/asset/", http.FileServer(http.Dir("./asset"))))

	http.ListenAndServe(":8080", nil)
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
	responseType, ok := r.URL.Query()["response_type"]
	if !ok || len(responseType[0]) < 1 {
		errHandler(w, r, "Url param response_type was missing")
		return
	}
	if string(responseType[0]) != "code" {
		errHandler(w, r, "Url param response_type was incompatible")
		return
	}

	clientID, ok := r.URL.Query()["client_id"]
	if !ok || len(clientID[0]) < 1 {
		errHandler(w, r, "Url param client_id was missing")
		return
	}

	redirectURI, ok := r.URL.Query()["redirect_uri"]
	if !ok || len(redirectURI[0]) < 1 {
		errHandler(w, r, "Url param redirect_uri was missing")
		return
	}
	redirectURIParsed, err := url.Parse(redirectURI[0])
	host, _, _ := net.SplitHostPort(redirectURIParsed.Host)

	scope, ok := r.URL.Query()["scope"]
	if !ok || len(scope[0]) < 1 {
		errHandler(w, r, "Url param scope was missing")
		return
	}

	state, ok := r.URL.Query()["state"]
	if !ok || len(state[0]) < 1 {
		errHandler(w, r, "Url param state was missing")
		return
	}

	nonce, ok := r.URL.Query()["nonce"]
	if !ok || len(nonce[0]) < 1 {
		errHandler(w, r, "Url param nonce was missing")
		return
	}

	//let say the GET request was good, then let us find does client-id and domain match our record
	data, err := ioutil.ReadFile("./client-id.json")
	if err != nil {
		fmt.Print(err)
	}
	var ClientsInformation ClientsInformationStruct
	err = json.Unmarshal(data, &ClientsInformation)
	if err != nil {
		fmt.Println("error:", err)
	}
	//process DB
	DBClientID := ""
	//DBClientSecret := ""
	DBDomain := ""
	DBServiceName := ""
	DBServiceImage := ""
	var DBScope []string
	for _, ClientInformation := range ClientsInformation {
		if ClientInformation.ClientID == clientID[0] {
			DBClientID = ClientInformation.ClientID
			//DBClientSecret = ClientInformation.ClientSecret
			DBDomain = ClientInformation.Domain
			DBServiceName = ClientInformation.ServiceName
			DBServiceImage = ClientInformation.ServiceImage
			DBScope = ClientInformation.Scope
		}
	}
	if DBClientID == "" {
		errHandler(w, r, "client_id does not exist in our database.")
		return
	}
	//check the record does it match
	if DBDomain != host {
		errHandler(w, r, "client_id and redirect_uri not match the system record.")
		return
	}
	//check if scope is available for that client
	scopeArr := strings.Split(scope[0], " ")
	for _, scopeItem := range scopeArr {
		if !contains(DBScope, scopeItem) {
			errHandler(w, r, "scope not match our system record.")
			return
		}
	}

	//serve file
	//push assembled data to page
	parsedPage, err := templateLoad("login.html", map[string]interface{}{
		"service-name":  string(DBServiceName),
		"service-image": string(DBServiceImage),
	})

	if err != nil {
		log.Println("Error. Unable to show error. Additionally, the error page also had error.")
	}
	w.Write([]byte(parsedPage))
}

func errHandler(w http.ResponseWriter, r *http.Request, errorMsg string) {
	//push assembled data to page
	parsedPage, err := templateLoad("error.html", map[string]interface{}{
		"error": string(errorMsg),
	})

	if err != nil {
		log.Println("Error. Unable to show error. Additionally, the error page also had error.")
	}
	w.Write([]byte(parsedPage))
	//sendTextResponse(w, parsedPage)
}

func templateLoad(filename string, replacement map[string]interface{}) (string, error) {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return "", nil
	}
	t := fasttemplate.New(string(content), "{{", "}}")
	s := t.ExecuteString(replacement)
	return string(s), nil
}

//https://ispycode.com/GO/Collections/Arrays/Check-if-item-is-in-array
func contains(arr []string, str string) bool {
	for _, a := range arr {
		if a == str {
			return true
		}
	}
	return false
}
