package middleware

import (
	"bytes"
	"encoding/json"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"

	"ordr-api/auth"
	"ordr-api/dto"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt"

	"github.com/google/go-querystring/query"
)

func TokenIsNotExpired(access_token string) bool {
	parser := new(jwt.Parser)

	token, err := parser.Parse(access_token, nil)
	if err != nil && err.Error() != "no Keyfunc was provided." {
		log.Fatal(err.Error())
	}

	claims, claims_ok := token.Claims.(jwt.MapClaims)
	if !claims_ok {
		log.Fatal("failed to read claims")
	}

	exp, exp_ok := claims["exp"].(float64)
	if !exp_ok {
		log.Fatal("exp claim not present")
	}

	return time.Now().Before(time.Unix(int64(exp), 0))
}

func GetUserProfile(context *gin.Context) {

	session := sessions.Default(context)

	var user_profile dto.UserProfileResponse
	profile_session := session.Get("profile").(map[string]interface{})
	user_profile.Sub = profile_session["sub"].(string)
	user_profile.Email = profile_session["email"].(string)
	user_profile.Verified = profile_session["email_verified"].(bool)
	user_profile.PictureUrl = profile_session["picture"].(string)
	user_profile.Nickname = profile_session["nickname"].(string)
	user_profile.Updated_at = profile_session["updated_at"].(string)
	context.Set("user_profile", user_profile)
	context.Next()
}

func HandleRefreshToken(session sessions.Session) bool {
	token_url, token_url_err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + os.Getenv("AUTH0_TOKEN_ENDPOINT"))
	if token_url_err != nil {
		log.Fatal("Failed to parse token url", token_url_err.Error())
	}

	refresh_token := session.Get("refresh_token")
	if refresh_token == nil {
		return false
	}
	refresh_request_dto := dto.RefreshTokenRequest{
		GrantType:    "refresh_token",
		ClientId:     os.Getenv("AUTH0_CLIENT_ID"),
		ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"),
		RefreshToken: refresh_token.(string),
	}

	refresh_body, marshal_err := query.Values(refresh_request_dto)
	if marshal_err != nil {
		log.Fatal("failed to marshal object: ", marshal_err)
	}

	refresh_client := http.Client{}
	refresh_request, refresh_request_err := http.NewRequest("POST", token_url.String(), bytes.NewReader([]byte(refresh_body.Encode())))
	if refresh_request_err != nil {
		log.Fatal("Failed to create a request: ", refresh_request_err.Error())
	}
	refresh_request.Header.Add("content-type", "application/x-www-form-urlencoded")
	refresh_response, refresh_response_err := refresh_client.Do(refresh_request)
	if refresh_response_err != nil {
		log.Fatal("Failed to fetch refresh token and response: ", refresh_response_err.Error())
	}
	defer refresh_response.Body.Close()

	if refresh_response.StatusCode == http.StatusOK {
		var response_body dto.RefreshTokenResponse
		json_decoder := json.NewDecoder(refresh_response.Body)
		content_unmarshal_err := json_decoder.Decode(&response_body)

		if content_unmarshal_err != nil {
			log.Println("Faileed to unmarshal data")
			return false
		}

		session.Set("access_token", response_body.AccessToken)
		session.Set("refresh_token", response_body.RefreshToken)
		session.Options(sessions.Options{Path: "/"})
		session_save_error := session.Save()

		if session_save_error != nil {
			log.Println("Failed to set session: " + session_save_error.Error())
			return false
		}
		return true
	} else {
		return false
	}
}

func IsAuthenticated(auth *auth.Authenticator) gin.HandlerFunc {
	return func(context *gin.Context) {
		session := sessions.Default(context)

		refresh_token := session.Get("refresh_token")

		access_token := session.Get("access_token")

		log.Printf("%s", refresh_token)

		if access_token != nil {
			if TokenIsNotExpired(access_token.(string)) {
				context.Next()
				return
			}
		}

		if !HandleRefreshToken(session) {
			context.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		context.Next()
	}
}
