package controllers

import (
	"crypto/rand"
	"encoding/base64"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"gitlab.com/pactual1/backend/database/user"
	usr "gitlab.com/pactual1/backend/database/user"
	"gitlab.com/pactual1/backend/models"
	"gitlab.com/pactual1/backend/services/messaging"
	"gitlab.com/pactual1/backend/shared"
	"golang.org/x/crypto/bcrypt"
)

func ResetPassword(c *gin.Context) {
	var req models.ResetPasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	dbUser, err := user.GetUserByEmail(req.Email)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	resetToken, err := GenerateResetToken()

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	err = user.SaveResetTokenToDB(dbUser.ID, resetToken)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	subject := "Password Reset Request"
	body := "Here is your password reset link: https://pactualdev.com/setNewPassword?token=" + resetToken
	email := models.EmailNotification{Body: body, Subject: subject, Email: dbUser.Email}

	go func(email models.EmailNotification) {

		emailChannel := messaging.GetEmailChannel()
		emailChannel <- email

	}(email)

	c.JSON(http.StatusOK, gin.H{"message": "Reset email sent"})
}

func GenerateResetToken() (string, error) {
	// Generate 32 random bytes (256 bits)
	randomBytes := make([]byte, 32)
	_, err := rand.Read(randomBytes)
	if err != nil {
		return "", err // return an error if there was one
	}

	// Encode the random bytes into a URL-safe base64 string
	resetToken := base64.URLEncoding.EncodeToString(randomBytes)

	return resetToken, nil
}

func UpdatePassword(c *gin.Context) {
	var req models.UpdatePasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Find the PasswordTokens entry
	var passwordToken models.PasswordTokens
	if err := shared.GetDb().Where("token = ?", req.Token).First(&passwordToken).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Invalid token"})
		return
	}

	// Find the associated User
	var user models.User
	if err := shared.GetDb().Where("id = ?", passwordToken.UserID).First(&user).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// Hash the password before saving it
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Update the user's password and set them as active
	user.Password = string(hashedPassword)
	user.IsActive = true
	if err := shared.GetDb().Save(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Delete the PasswordTokens entry
	if err := shared.GetDb().Delete(&passwordToken).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"})
}

func Login(c *gin.Context) {
	var req models.User
	if err := c.BindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Bad request"})
		return
	}

	user, err := usr.GetUserByEmail(req.Email)
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
		return
	}

	if usr.CheckPassword(user.Password, req.Password) {
		if user.IsActive && user.LoginAttempts < 10 {
			// Proceed with creating JWT token and resetting login attempts
			if len(user.Companies) == 0 {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "User is not connected to a company"})
				return
			}

			token, err := usr.CreateSessionToken(user.ID, user.Companies[0].ID)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create JWT token"})
				return
			}

			usr.ResetLoginAttempts(*user)

			c.JSON(http.StatusOK, gin.H{"token": token})
		} else {
			c.JSON(http.StatusForbidden, gin.H{"error": "Account locked or too many attempts"})
		}
	} else {
		// Wrong password, increment login attempts
		usr.IncrementLoginAttempts(*user)
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
	}
}

func Logout(c *gin.Context) {
	// Extract the token from the request, typically from the Authorization header
	tokenString := c.GetHeader("Authorization")

	// If using a Bearer token, strip the 'Bearer ' prefix
	if len(tokenString) > 7 && strings.ToUpper(tokenString[0:7]) == "BEARER " {
		tokenString = tokenString[7:]
	}

	// Invalidate the session token
	err := usr.InvalidateSessionToken(tokenString)
	if err != nil {
		// Handle error, could be not found or database error
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to logout"})
		return
	}

	// Respond with success
	c.JSON(http.StatusOK, gin.H{"message": "Successfully logged out"})
}
