package main

import (
	"encoding/json"
	"flag"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"os"
	"strconv"
	"strings"

	log "github.com/sirupsen/logrus"
)

var (
	httpPort    		= flag.Int("http_port", LookupEnvOrInt("HTTP_PORT", 80), "HTTP port to serve on (defaults to 80)")
	logLevel    		= flag.String("loglevel", LookupEnvOrString("LOGLEVEL", "debug"), "log level (debug, info, warning, error) (defaults to debug)")
	cozyDomain 			= flag.String("cozy_domain", LookupEnvOrString("COZY_DOMAIN", "cozy.wf.alpha.grandlyon.com"), "Cozy domain (defaults to cozy.wf.alpha.grandlyon.com)")
	cozyRedirectURI 	= flag.String("cozy_redirect_uri", LookupEnvOrString("COZY_REDIRECT_URI", "/accounts/enedisgrandlyon/redirect"), "Cozy redirect URI (defaults to /accounts/enedisgrandlyon/redirect)")
)

type TokenResponse struct {
	AccessToken          string `json:"access_token"`
	TokenType            string `json:"token_type"`
	ExpiresIn            int    `json:"expires_in"`
	RefreshToken         string `json:"refresh_token"`
	Scope                string `json:"scope"`
	RefreshTokenIssuedAt string `json:"refresh_token_issued_at"`
	IssueAt              string `json:"issued_at"`
	UsagePointId         string `json:"usage_points_id"`
}

func LookupEnvOrString(key string, defaultVal string) string {
	if val, ok := os.LookupEnv(key); ok {
		return val
	}
	return defaultVal
}

func LookupEnvOrInt(key string, defaultVal int) int {
	if val, ok := os.LookupEnv(key); ok {
		v, err := strconv.Atoi(val)
		if err != nil {
			log.Fatalf("LookupEnvOrInt[%s]: %v", key, err)
		}
		return v
	}
	return defaultVal
}

func findItem(arrayType interface{}, item interface{}) bool {
	arr := reflect.ValueOf(arrayType)
	if arr.Kind() != reflect.Array {
		panic("Invalid data-type")
	}
	for i := 0; i < arr.Len(); i++ {
		if arr.Index(i).Interface() == item {
			return true
		}
	}

	return false
}

func main() {
	// Parse the flags
	flag.Parse()

	// Init logging
	log.SetOutput(os.Stdout)
	log.SetFormatter(&log.TextFormatter{
		PadLevelText:     true,
		ForceQuote:       true,
		DisableTimestamp: false,
		FullTimestamp:    true,
		TimestampFormat:  "2006-01-02 15:04:05",
	})

	// Configure log level
	switch strings.ToLower(*logLevel) {
	case "error":
		log.SetLevel(log.ErrorLevel)
	case "warning":
		log.SetLevel(log.WarnLevel)
	case "info":
		log.SetLevel(log.InfoLevel)
	case "debug":
		log.SetLevel(log.DebugLevel)
	default:
		log.SetLevel(log.DebugLevel)
		log.Fatalf("Unknown logging level %s. Choose between debug, info, warning or error.", *logLevel)
	}

	mux := http.NewServeMux()
	log.Infof("Starting Server on port %d\n", *httpPort)

	mux.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "OK\n")
	})

	// ENEDIS AUTH ENDPOINT
	mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
		log.Debug("New auth request")
		query := r.URL.Query()
		log.Debug("Query received - ", query)

		clientId := query.Get("client_id")
		state := query.Get("state")
		responseType := "code"
		// here we use the redirect_uri param to transmit our stack url
		// We keep only the instance name to not reach the 100 max char of redirectUrl
		cozyOrigin := query.Get("redirect_uri")
		splitIndexStart := strings.Index(cozyOrigin, ":")
		if splitIndexStart == -1 {
			log.Error("redirect_uri bad format " + cozyOrigin)
			http.Error(w, http.StatusText(500), 500)
		}
		splitIndexEnd := strings.Index(cozyOrigin, ".")
		if splitIndexEnd == -1 {
			log.Error("redirect_uri bad format " + cozyOrigin)
			http.Error(w, http.StatusText(500), 500)
		}
		instanceName := cozyOrigin[splitIndexStart+3:splitIndexEnd]

		// DEV API
		// authURL := "https://gw.hml.api.enedis.fr/dataconnect/v1/oauth2/authorize"
		// PROD API
		authURL := "https://mon-compte-particulier.enedis.fr/dataconnect/v1/oauth2/authorize"

		redirectUrl := authURL + "?client_id=" + clientId + "&duration=P6M&response_type=" + responseType + "&state=" + state + "-" + instanceName

		log.Debug("Redirect to - ", redirectUrl)
		http.Redirect(w, r, redirectUrl, 302)
	})

	// GRDF ADICT ACCESS_TOKEN ENDPOINT
	mux.HandleFunc("/grdfAccess", func(w http.ResponseWriter, r *http.Request) {
		log.Debug("New grdf auth request")
		query := r.URL.Query()
		log.Debug("Query received - ", query)

		clientId := query.Get("client_id")
		state := query.Get("state")
		scope := "/adict/v1"
		// here we use the redirect_uri param to transmit our stack url
		// We keep only the instance name to not reach the 100 max char of redirectUrl
		cozyOrigin := query.Get("redirect_uri")
		splitIndexStart := strings.Index(cozyOrigin, ":")
		if splitIndexStart == -1 {
			log.Error("redirect_uri bad format " + cozyOrigin)
			http.Error(w, http.StatusText(500), 500)
		}
		splitIndexEnd := strings.Index(cozyOrigin, ".")
		if splitIndexEnd == -1 {
			log.Error("redirect_uri bad format " + cozyOrigin)
			http.Error(w, http.StatusText(500), 500)
		}
		instanceName := cozyOrigin[splitIndexStart+3:splitIndexEnd]

		// DEV API
		// authURL := "https://gw.hml.api.enedis.fr/dataconnect/v1/oauth2/authorize"
		// PROD API
		authURL := "https://mon-compte-particulier.enedis.fr/dataconnect/v1/oauth2/authorize"

		redirectUrl := authURL + "?client_id=" + clientId + "&duration=P6M&response_type=" + responseType + "&state=" + state + "-" + instanceName

		log.Debug("Redirect to - ", redirectUrl)
		http.Redirect(w, r, redirectUrl, 302)
	})

	//ENEDIS REDIRECT ENDPOINT
	mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
		log.Debug("New redirect request")
		query := r.URL.Query()
		log.Debug(query)

		code := query.Get("code")
		req_state := query.Get("state")
		statusCodes := [4]string{"400", "403", "500", "503"}

		if (findItem(statusCodes, code)) {
			intCode, err := strconv.Atoi(code)
			if err != nil {
				log.Print("status code string to int error: ", err)
			}
			log.Print("status code error : ", code)
			http.Error(w, http.StatusText(intCode), intCode)
		} else {
			splitIndex := strings.Index(req_state, "-")
			if splitIndex == -1 {
				log.Warning("No host found")
			}
			state := req_state[0:splitIndex]
			host := req_state[splitIndex+1:]

			usagePointId := query.Get("usage_point_id")

			cozyURL := "https://" + host + "." + *cozyDomain + *cozyRedirectURI

			redir := cozyURL + "?code=" + code + "&state=" + state + "&usage_point_id=" + usagePointId
			log.Debug("Redirect to -", redir)
			http.Redirect(w, r, redir, 302)
		}
	})

	//ENEDIS TOKEN ENDPOINT
	mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
		log.Debug("New token request")
		query := r.URL.Query()
		log.Debug(query)

		clientId := ""
		clientSecret := ""
		code := ""
		grantType := ""
		refreshToken := ""

		// For request token params are into query parameters
		if len(query) == 0 {
			log.Warn("No params found in url query - Trying to catch them from body")
			contents, err := ioutil.ReadAll(r.Body)
			if err != nil {
				log.Error(err)
			}
			pageContent := string(contents)
			//Check for client_id
			clientIdStartIndex := strings.Index(pageContent, "client_id=")
			if clientIdStartIndex == -1 {
				log.Error("No client_id found")
				http.Error(w, http.StatusText(500), 500)
			}
			clientIdStartIndex += 10
			clientId = pageContent[clientIdStartIndex : clientIdStartIndex+36]
			//Check for client_secret
			clientSecretStartIndex := strings.Index(pageContent, "client_secret=")
			if clientSecretStartIndex == -1 {
				log.Error("No client_secret found")
				http.Error(w, http.StatusText(500), 500)
			}
			clientSecretStartIndex += 14
			clientSecret = pageContent[clientSecretStartIndex : clientSecretStartIndex+36]
			//Check for code
			codeStartIndex := strings.Index(pageContent, "code=")
			if codeStartIndex == -1 {
				log.Info("No code found (optional param)")
			} else {
				codeStartIndex += 5
				code = pageContent[codeStartIndex : codeStartIndex+30]
			}
			//Check for grant_type
			grandTypeStartIndex := strings.Index(pageContent, "grant_type=")
			if grandTypeStartIndex == -1 {
				log.Error("No grant_type found")
				http.Error(w, http.StatusText(500), 500)
			}
			grandTypeStartIndex += 11
			tempGrandTypeString := pageContent[grandTypeStartIndex:]
			grandTypeEndIndex := strings.Index(tempGrandTypeString, "&")
			if grandTypeEndIndex == -1 {
				log.Error("No closing tag for grant_type found")
				http.Error(w, http.StatusText(500), 500)
			}
			grantType = tempGrandTypeString[0:grandTypeEndIndex]
			//Check for refresh_token
			refershTokenStartIndex := strings.Index(pageContent, "refresh_token=")
			if refershTokenStartIndex == -1 {
				log.Error("No refresh_token found")
				http.Error(w, http.StatusText(500), 500)
			}
			refershTokenStartIndex += 14
			refreshToken = pageContent[refershTokenStartIndex : refershTokenStartIndex+46]
		} else {
			// Retrieve params from query
			clientId = query.Get("client_id")
			clientSecret = query.Get("client_secret")
			code = query.Get("code")
			grantType = query.Get("grant_type")
			refreshToken = query.Get("refresh_token")
		}
		// Print out the result
		log.WithFields(log.Fields{
			"client_id":     clientId,
			"client_secret": clientSecret,
			"code":          code,
			"grant_type":    grantType,
			"refresh_token": refreshToken,
		}).Debug("result")

		// DEV API
		// tokenUrl := "https://gw.hml.api.enedis.fr/v1/oauth2/token"
		// PROD API
		tokenUrl := "https://gw.prd.api.enedis.fr/v1/oauth2/token"

		data := url.Values{}
		data.Set("client_id", clientId)
		data.Set("client_secret", clientSecret)
		data.Set("code", code)
		data.Set("grant_type", grantType)
		if refreshToken != "" {
			data.Set("refresh_token", refreshToken)
			data.Set("grant_type", "refresh_token")
		}

		log.Debug("Send request to token endpoint: ", tokenUrl)
		response, err := http.PostForm(tokenUrl, data)
		if err != nil {
			log.Error(err)
		} else {
			log.Debug("Endpoint response with status", response.Status)
			defer response.Body.Close()
			if response.StatusCode >= 200 && response.StatusCode <= 299 {
				// Set Content-Type in response header
				w.Header().Add("Content-Type", "application/json")

				// Decode response Body using the defined type "TokenResponse"
				data := TokenResponse{}
				decodeError := json.NewDecoder(response.Body).Decode(&data)
				if decodeError != nil {
					http.Error(w, decodeError.Error(), 500)
					return
				}

				// Response with json data
				jsonError := json.NewEncoder(w).Encode(data)
				if jsonError != nil {
					http.Error(w, jsonError.Error(), 500)
					return
				}
			} else {
				http.Error(w, http.StatusText(response.StatusCode), response.StatusCode)
			}
		}
	})

	log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*httpPort), mux))
}