package main

import (
	"log/slog"
	"net/http"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type User struct {
	ID       uint   `gorm:"primaryKey"` // Standard field for the primary key
	Username string // A regular string field
	Password *[]byte
	Email    string         // A pointer to a string, allowing for null values
	Updated  int64          `gorm:"autoUpdateTime:milli"` // Use unix milli seconds as updating time
	Created  int64          `gorm:"autoCreateTime"`       // Use unix seconds as creating time
	Deleted  gorm.DeletedAt `gorm:"index"`
}

type Login struct {
	Email    string `form:"email" json:"email" xml:"email"  binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

type Register struct {
	Email                string `form:"email" json:"email" xml:"email"  binding:"required"`
	Password             string `form:"password" json:"password" xml:"password" binding:"required"`
	PasswordConfirmation string `form:"password_confirm" json:"password_confirm" xml:"password_confirm" binding:"required"`
	Username             string `form:"username" json:"username" xml:"username" binding:"required"`
}

func main() {
	jsonHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
		Level:     slog.LevelDebug,
		AddSource: true,
	})
	myslog := slog.New(jsonHandler)

	dsn := "host=localhost user=postgres password=asd dbname=postgres port=5432"
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		myslog.Error("Could not establish connection to database!")
		panic("Could not establish connection to database!")
	}

	db.AutoMigrate(&User{})

	sqlDB, err := db.DB()

	if err != nil {
		myslog.Error("Could not create connection pool for database!")
		panic("Could not create connection pool for database!")
	}

	// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
	sqlDB.SetMaxIdleConns(10)

	// SetMaxOpenConns sets the maximum number of open connections to the database.
	sqlDB.SetMaxOpenConns(100)

	// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
	sqlDB.SetConnMaxLifetime(time.Hour)

	password := "asd" // Replace this with the password you want to encrypt
	hashedPassword, err := hashPassword(password)
	if err != nil {
		myslog.Info("Encryption failed:", "err", err)
		return
	}
	myslog.Info("Hashed Password:", "password", string(hashedPassword))

	router := gin.Default()
	router.POST("/login", func(c *gin.Context) {
		var json Login
		if err := c.ShouldBindJSON(&json); err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized: payload mismatch"})
			myslog.Info("Payload:", "json", &c.Request.Body)
			return
		}

		var user *User
		user, exists := userExists(json.Email, "", db)

		if exists || !doPasswordsMatch(*user.Password, json.Password) {
			c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized: email or password incorrect"})
			return
		}

		c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
	})

	router.POST("/register", func(c *gin.Context) {
		var json Register
		if err := c.ShouldBindJSON(&json); err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized: payload mismatch"})
			myslog.Info("Payload:", "json", &c.Request.Body)
			return
		}

		c.JSON(http.StatusOK, gin.H{"status": "registration complete"})
	})

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")

}

func doPasswordsMatch(hashedPassword []byte, currPassword string) bool {
	err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(currPassword))
	return err == nil
}

func hashPassword(password string) ([]byte, error) {
	return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
}

func userExists(username string, email string, db *gorm.DB) (*User, bool) {
	var user = User{Username: username, Email: email}
	result := db.Where("username = ? OR email = ?", username, email).First(&user)
	if result.Error != nil {
		return &user, true
	}
	return nil, false
}
