package user

import (
	"backend/dbRequest"
	"backend/models"
	"backend/utils"
	"encoding/json"
	"io"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	_ "modernc.org/sqlite"
)

var JWT_SECRET = []byte("4h5Jza1Fn_zuzu&417%8nH*UH100+55-")
var DOMAIN = "localhost"
var ACCESS_TOKEN_TIME = 15 * time.Minute
var REFRESH_TOKEN_TIME = 72 * time.Hour

func (um *UserManager) Login(c *gin.Context) {
	if !um.databaseOpened(c) {
		return
	}

	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	user := models.User{}
	err = json.Unmarshal(body, &user)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	if !user.IsValid() {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty"))
		return
	}

	var storedPassword, settingsJsonString string
	if err := um.database.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&user.Id, &user.Role, &storedPassword, &settingsJsonString); dbRequest.CheckDBError(c, user.Name, err) {
		return
	}

	err = json.Unmarshal([]byte(settingsJsonString), &user.Settings)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse(err.Error()))
		return
	}

	if !utils.CheckPassword(user.Password, storedPassword) {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("wrong password"))
		return
	}

	// ---- Create JWT tokens ----
	accessTokenExp := time.Now().Add(ACCESS_TOKEN_TIME)
	refreshTokenExp := time.Now().Add(REFRESH_TOKEN_TIME)

	// Create token
	accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       user.Id,
		"username": user.Name,
		"role":     user.Role,
		"type":     "access",
		"exp":      accessTokenExp.Unix(),
	})

	refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       user.Id,
		"username": user.Name,
		"role":     user.Role,
		"type":     "refresh",
		"exp":      refreshTokenExp.Unix(),
	})

	accessString, err := accessToken.SignedString(JWT_SECRET)
	if err != nil {
		c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("could not create access token"))
		return
	}

	refreshString, err := refreshToken.SignedString(JWT_SECRET)
	if err != nil {
		c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("could not create refresh token"))
		return
	}

	// ---- Set secure cookies ----
	secure := gin.Mode() == gin.ReleaseMode
	c.SetCookie("access_token", accessString, int(time.Until(accessTokenExp).Seconds()),
		"/", "", secure, true) // Path=/, Secure=true (only HTTPS), HttpOnly=true
	c.SetCookie("refresh_token", refreshString, int(time.Until(refreshTokenExp).Seconds()),
		"/", "", secure, true)

	c.JSON(http.StatusOK, gin.H{
		"message":  "login successful",
		"id":       user.Id,
		"user":     user.Name,
		"role":     user.Role,
		"settings": user.Settings,
	})
}

func (um *UserManager) Refresh(c *gin.Context) {
	refreshCookie, err := c.Cookie("refresh_token")
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"message": "no refresh token"})
		return
	}

	token, err := jwt.Parse(refreshCookie, func(token *jwt.Token) (any, error) {
		return JWT_SECRET, nil
	})
	if err != nil || !token.Valid {
		c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid refresh token"})
		return
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok || claims["type"] != "refresh" {
		c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid token type"})
		return
	}

	username := claims["username"].(string)
	id := claims["id"].(float64)
	role := claims["role"].(string)

	// new access token
	accessExp := time.Now().Add(ACCESS_TOKEN_TIME)
	newAccess := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       id,
		"username": username,
		"role":     role,
		"exp":      accessExp.Unix(),
	})
	accessString, _ := newAccess.SignedString(JWT_SECRET)

	c.SetCookie("access_token", accessString, int(time.Until(accessExp).Seconds()), "/", DOMAIN, gin.Mode() == gin.ReleaseMode, true)
	c.JSON(http.StatusOK, gin.H{"message": "token refreshed"})
}

func (um *UserManager) Me(c *gin.Context) {
	// Read access token from cookie
	cookie, err := c.Cookie("access_token")
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"message": "not logged in"})
		return
	}

	// Verify token
	token, err := jwt.Parse(cookie, func(t *jwt.Token) (any, error) {
		return JWT_SECRET, nil
	})
	if err != nil || !token.Valid {
		c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid token"})
		return
	}

	claims := token.Claims.(jwt.MapClaims)
	c.JSON(http.StatusOK, gin.H{
		"id":   claims["id"],
		"user": claims["username"],
		"role": claims["role"],
	})
}

func (um *UserManager) Logout(c *gin.Context) {
	secure := gin.Mode() == gin.ReleaseMode

	c.SetCookie("access_token", "", -1, "/", DOMAIN, secure, true)
	c.SetCookie("refresh_token", "", -1, "/", DOMAIN, secure, true)

	c.JSON(http.StatusOK, gin.H{"message": "logged out"})
}
