package login

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

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

func (lm *LoginManager) AddUser(c *gin.Context) {
	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
	}

	db, err := sql.Open(lm.dbType, lm.dbFile)
	if dbRequest.CheckDBError(c, user.Name, err) {
		return
	}
	defer db.Close()

	var exists bool

	if err := db.QueryRow(dbRequest.DBUserLookup, user.Name).Scan(&exists); dbRequest.CheckDBError(c, user.Name, err) {
		return
	}

	if exists {
		c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(fmt.Sprintf("user '%s' exists already", user.Name)))
		return
	}

	hash, err := utils.HashPassword(user.Password)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}
	if _, err := db.Exec(dbRequest.DBNewUser, user.Role, user.Name, hash); dbRequest.CheckDBError(c, user.Name, err) {
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": fmt.Sprintf("user '%s' successfully added", user.Name),
	})
}

func (lm *LoginManager) RemoveUser(c *gin.Context) {
	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
	}

	db, err := sql.Open(lm.dbType, lm.dbFile)
	if dbRequest.CheckDBError(c, user.Name, err) {
		return
	}
	defer db.Close()

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

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

	if _, err := db.Exec(dbRequest.DBRemoveUser, user.Name); dbRequest.CheckDBError(c, user.Name, err) {
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": fmt.Sprintf("user '%s' successfully removed", user.Name),
	})
}

func (lm *LoginManager) Login(c *gin.Context) {
	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
	}

	db, err := sql.Open(lm.dbType, lm.dbFile)
	if dbRequest.CheckDBError(c, user.Name, err) {
		return
	}
	defer db.Close()

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

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

	// Create token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"username": user.Name,
		"role":     user.Role,
		"exp":      time.Now().Add(time.Minute * 60).Unix(), // expires in 72h
	})

	secret, err := utils.GenerateJWTSecret(32) // 32 bytes = 256 bits
	if err != nil {
		c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("error generate jwt token"))
		return
	}

	// Sign and get the complete encoded token as a string
	tokenString, err := token.SignedString(secret)
	if err != nil {
		c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("Could not generate token"))
		return
	}

	c.JSON(http.StatusOK, models.User{
		Token: tokenString,
	})
}
