package user

import (
	"backend/dbRequest"
	"backend/models"
	"backend/utils"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
)

type UserManager struct {
	database *sql.DB
}

func NewUserManager(dir string) (*UserManager, error) {
	if dir == "" {
		dir = "."
	}

	var err error
	var um UserManager
	file := fmt.Sprintf("%s/user.db", dir)

	um.database, err = sql.Open("sqlite", file)
	if err != nil {
		return nil, err
	}

	if _, err := os.Stat(file); err != nil {
		_, err = um.database.Exec(dbRequest.CreateUserTable)
		if err != nil {
			return nil, err
		}

		hash, err := utils.HashPassword("tecamino@2025")
		if err != nil {
			return nil, err
		}
		_, err = um.database.Exec(dbRequest.NewUser, "admin", "", "admin", hash, `{"databaseName":"members.dba","primaryColor":"#1976d2", "secondaryColor":"#26a69a"}`)
		if err != nil {
			return nil, err
		}
	}
	return &um, nil
}

func (um *UserManager) databaseOpened(c *gin.Context) bool {
	if um.database == nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "no database opened",
		})
		return false
	}
	return true
}

func (um *UserManager) AddUser(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 exists bool

	if err := um.database.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 !utils.IsValidEmail(user.Email) {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(errors.New("not valid email address")))
		return
	}

	if _, err := um.database.Exec(dbRequest.NewUser, user.Name, user.Email, user.Role, hash, "{}"); dbRequest.CheckDBError(c, user.Name, err) {
		return
	}

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

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

	var i int
	var err error

	id := c.Query("id")
	if id != "" {
		i, err = strconv.Atoi(id)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": err.Error(),
			})
			return
		}
	}

	query := `SELECT id, username, email, role, settings FROM users`
	var args any
	if i > 0 {
		query = `
		SELECT  id, username, email, role, settings FROM users
		WHERE id = ? 
	`
		args = i
	}

	rows, err := um.database.Query(query, args)
	if err != nil {
		return
	}
	defer rows.Close()

	var users []models.User

	for rows.Next() {
		var id int
		var name, email, role, settingsString string
		if err = rows.Scan(&id, &name, &email, &role, &settingsString); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": err.Error(),
			})
			return
		}

		var settings models.Settings
		err := json.Unmarshal([]byte(settingsString), &settings)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": err.Error(),
			})
			return
		}

		users = append(users, models.User{
			Id:       id,
			Name:     name,
			Email:    email,
			Role:     role,
			Settings: settings,
		})
	}

	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, users)
}

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

	queryId := c.Query("id")

	if queryId == "" || queryId == "null" || queryId == "undefined" {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "id query missing or wrong value: " + queryId,
		})
		return
	}

	var request struct {
		Ids []int `json:"ids"`
	}

	err := c.BindJSON(&request)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": err.Error(),
		})
		return
	}

	if len(request.Ids) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "no ids given to be deleted",
		})
		return
	}

	var ownId string
	placeholders := make([]string, len(request.Ids))
	args := make([]any, len(request.Ids))
	for i, id := range request.Ids {
		if queryId == fmt.Sprint(id) {
			ownId = queryId
			continue
		}

		placeholders[i] = "?"
		args[i] = id
	}

	query := fmt.Sprintf("DELETE FROM users WHERE id IN (%s)", strings.Join(placeholders, ","))

	_, err = um.database.Exec(query, args...)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": err.Error(),
		})
		return
	}

	if ownId != "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "can not delete logged in member id: " + queryId,
			"id":      queryId,
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "member(s) deleted",
	})
}
