package handlers

import (
	"time"
	"ttime/internal/types"

	"github.com/gofiber/fiber/v2/log"

	"github.com/gofiber/fiber/v2"
	"github.com/golang-jwt/jwt/v5"
)

// Register is a simple handler that registers a new user
//
//	@Summary		Register
//	@Description	Register a new user
//	@Tags			User
//	@Accept			json
//	@Produce		plain
//	@Param			NewUser	body		types.NewUser	true	"User to register"
//	@Success		200		{string}	string			"User added"
//	@Failure		400		{string}	string			"Bad request"
//	@Failure		500		{string}	string			"Internal server error"
//	@Router			/register [post]
func (gs *GState) Register(c *fiber.Ctx) error {
	u := new(types.NewUser)
	if err := c.BodyParser(u); err != nil {
		log.Warn("Error parsing body")
		return c.Status(400).SendString(err.Error())
	}

	log.Info("Adding user:", u.Username)
	if err := gs.Db.AddUser(u.Username, u.Password); err != nil {
		log.Warn("Error adding user:", err)
		return c.Status(500).SendString(err.Error())
	}

	log.Info("User added:", u.Username)
	return c.Status(200).SendString("User added")
}

// This path should obviously be protected in the future
// UserDelete deletes a user from the database
//
//	@Summary		UserDelete
//	@Description	UserDelete deletes a user from the database
//	@Tags			User
//	@Accept			json
//	@Produce		plain
//	@Success		200	{string}	string	"User deleted"
//	@Failure		403	{string}	string	"You can only delete yourself"
//	@Failure		500	{string}	string	"Internal server error"
//	@Failure		401	{string}	string	"Unauthorized"
//	@Router			/userdelete/{username} [delete]
func (gs *GState) UserDelete(c *fiber.Ctx) error {
	// Read from path parameters
	username := c.Params("username")

	// Read username from Locals
	auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)

	if username != auth_username {
		log.Info("User tried to delete another user")
		return c.Status(403).SendString("You can only delete yourself")
	}

	if err := gs.Db.RemoveUser(username); err != nil {
		log.Warn("Error deleting user:", err)
		return c.Status(500).SendString(err.Error())
	}

	log.Info("User deleted:", username)
	return c.Status(200).SendString("User deleted")
}

// Login is a simple login handler that returns a JWT token
//
//	@Summary		login
//	@Description	logs the user in and returns a jwt token
//	@Tags			User
//	@Accept			json
//	@Param			NewUser	body	types.NewUser	true	"login info"
//	@Produce		plain
//	@Success		200	Token		types.Token	"Successfully signed token for user"
//	@Failure		400	{string}	string		"Bad request"
//	@Failure		401	{string}	string		"Unauthorized"
//	@Failure		500	{string}	string		"Internal server error"
//	@Router			/login [post]
func (gs *GState) Login(c *fiber.Ctx) error {
	// The body type is identical to a NewUser

	u := new(types.NewUser)
	if err := c.BodyParser(u); err != nil {
		log.Warn("Error parsing body")
		return c.Status(400).SendString(err.Error())
	}

	log.Info("Username logging in:", u.Username)
	if !gs.Db.CheckUser(u.Username, u.Password) {
		log.Info("User not found")
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	isAdmin, err := gs.Db.IsSiteAdmin(u.Username)
	if err != nil {
		log.Info("Error checking admin status:", err)
		return c.Status(500).SendString(err.Error())
	}
	// Create the Claims
	claims := jwt.MapClaims{
		"name":  u.Username,
		"admin": isAdmin,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// Create token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	log.Info("Token created for user:", u.Username)

	// Generate encoded token and send it as response.
	t, err := token.SignedString([]byte("secret"))
	if err != nil {
		log.Warn("Error signing token")
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	println("Successfully signed token for user:", u.Username)
	return c.JSON(types.Token{Token: t})
}

// LoginRenew is a simple handler that renews the token
//
//	@Summary		LoginRenews
//	@Description	renews the users token
//	@Security		bererToken
//	@Tags			User
//	@Accept			json
//	@Produce		plain
//	@Success		200	Token		types.Token	"Successfully signed token for user"
//	@Failure		401	{string}	string		"Unauthorized"
//	@Failure		500	{string}	string		"Internal server error"
//	@Router			/loginerenew [post]
func (gs *GState) LoginRenew(c *fiber.Ctx) error {
	user := c.Locals("user").(*jwt.Token)

	log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"])

	claims := user.Claims.(jwt.MapClaims)
	claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
	renewed := jwt.MapClaims{
		"name":  claims["name"],
		"admin": claims["admin"],
		"exp":   claims["exp"],
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed)
	t, err := token.SignedString([]byte("secret"))
	if err != nil {
		log.Warn("Error signing token")
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"])
	return c.JSON(types.Token{Token: t})
}

// ListAllUsers is a handler that returns a list of all users in the application database
//
//	@Summary		ListsAllUsers
//	@Description	lists all users
//	@Tags			User
//	@Accept			json
//	@Produce		plain
//	@Success		200	{json}		json	"Successfully signed token for user"
//	@Failure		401	{string}	string	"Unauthorized"
//	@Failure		500	{string}	string	"Internal server error"
//	@Router			/users/all [get]
func (gs *GState) ListAllUsers(c *fiber.Ctx) error {
	// Get all users from the database
	users, err := gs.Db.GetAllUsersApplication()
	if err != nil {
		log.Info("Error getting users from db:", err) // Debug print
		return c.Status(500).SendString(err.Error())
	}

	log.Info("Returning all users")
	// Return the list of users as JSON
	return c.JSON(users)
}

func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error {
	// Get all users from a project
	projectName := c.Params("projectName")
	users, err := gs.Db.GetAllUsersProject(projectName)
	if err != nil {
		log.Info("Error getting users from project:", err) // Debug print
		return c.Status(500).SendString(err.Error())
	}

	log.Info("Returning all users")
	// Return the list of users as JSON
	return c.JSON(users)
}

// @Summary		PromoteToAdmin
// @Description	promote chosen user to admin
// @Tags			User
// @Accept			json
// @Produce		plain
// @Param			NewUser	body		types.NewUser	true	"user info"
// @Success		200		{json}		json			"Successfully promoted user"
// @Failure		400		{string}	string			"Bad request"
// @Failure		401		{string}	string			"Unauthorized"
// @Failure		500		{string}	string			"Internal server error"
// @Router			/promoteToAdmin [post]
func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error {
	// Extract the username from the request body
	var newUser types.NewUser
	if err := c.BodyParser(&newUser); err != nil {
		return c.Status(400).SendString("Bad request")
	}
	username := newUser.Username

	log.Info("Promoting user to admin:", username) // Debug print

	// Promote the user to a site admin in the database
	if err := gs.Db.PromoteToAdmin(username); err != nil {
		log.Info("Error promoting user to admin:", err) // Debug print
		return c.Status(500).SendString(err.Error())
	}

	log.Info("User promoted to admin successfully:", username) // Debug print

	// Return a success message
	return c.SendStatus(fiber.StatusOK)
}

// ChangeUserName changes a user's username in the database
func (gs *GState) ChangeUserName(c *fiber.Ctx) error {
	// Check token and get username of current user
	user := c.Locals("user").(*jwt.Token)
	claims := user.Claims.(jwt.MapClaims)
	adminUsername := claims["name"].(string)
	log.Info(adminUsername)

	// Extract the necessary parameters from the request
	data := new(types.StrNameChange)
	if err := c.BodyParser(data); err != nil {
		log.Info("Error parsing username")
		return c.Status(400).SendString(err.Error())
	}

	// Check if the current user is an admin
	isAdmin, err := gs.Db.IsSiteAdmin(adminUsername)
	if err != nil {
		log.Warn("Error checking if admin:", err)
		return c.Status(500).SendString(err.Error())
	} else if !isAdmin {
		log.Warn("Tried changing name when not admin")
		return c.Status(401).SendString("You cannot change name unless you are an admin")
	}

	// Change the user's name in the database
	if err := gs.Db.ChangeUserName(data.PrevName, data.NewName); err != nil {
		return c.Status(500).SendString(err.Error())
	}

	// Return a success message
	return c.SendStatus(fiber.StatusOK)
}