package users

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"reflect"
	"strings"
	"time"

	"fr.latosa-escrima/core"
	"fr.latosa-escrima/core/models"
	"fr.latosa-escrima/utils"
	"github.com/google/uuid"
	"github.com/uptrace/bun"
)

type UpdateUserArgs struct {
	FirstName  *string                `json:"firstname,omitempty"`
	LastName   *string                `json:"lastname,omitempty"`
	Email      *string                `json:"email,omitempty"`
	Password   *string                `json:"password,omitempty"`
	Phone      *string                `json:"phone,omitempty"`
	Attributes *models.UserAttributes `json:"attributes"`
	Roles      *[]models.Role         `json:"roles"`
}

func HandleUpdate(w http.ResponseWriter, r *http.Request) {
	ctx := context.Background()
	var updateArgs UpdateUserArgs
	err := json.NewDecoder(r.Body).Decode(&updateArgs)
	if err != nil {
		core.JSONError{
			Status:  core.Error,
			Message: err.Error(),
		}.Respond(w, http.StatusInternalServerError)
		return
	}

	user_uuid := r.PathValue("user_uuid")
	uid, err := uuid.Parse(user_uuid)
	if err != nil {
		return
	}
	var user models.User
	err = core.DB.
		NewSelect().
		Model(&user).
		Where("user_id = ?", user_uuid).
		Relation("Roles").
		Scan(ctx)
	if err != nil {
		core.JSONError{
			Status:  core.Error,
			Message: err.Error(),
		}.Respond(w, http.StatusInternalServerError)
		return
	}
	updateQuery := core.DB.NewUpdate().Model(&user)

	rolesInsert := []*bun.InsertQuery{}
	rolesRemoved := []*bun.DeleteQuery{}

	val := reflect.ValueOf(updateArgs)
	typ := reflect.TypeOf(updateArgs)

	for i := range val.NumField() {
		field := val.Field(i)
		fieldname := typ.Field(i).Name

		tag := typ.Field(i).Tag.Get("bun")
		if tag == "" {
			tag = typ.Field(i).Tag.Get("json")
		}

		// Only add fields that are non-nil and non-zero
		if field.IsValid() && !field.IsNil() && !field.IsZero() {
			if fieldname == "Password" {
				updateQuery.Set(fmt.Sprintf("%s = crypt(?, gen_salt('bf'))", strings.Split(tag, ",")[0]), field.Interface())
			} else if fieldname == "Roles" {
				_roles := field.Interface().(*[]models.Role)
				if _roles == nil {
					continue
				}

				currentRoles := utils.Map(user.Roles, func(role models.Role) uuid.UUID {
					return role.ID
				})

				roles := utils.Map(*_roles, func(role models.Role) uuid.UUID {
					return role.ID
				})

				log.Println(user.Roles)
				toAdd, toRemove := utils.GetDiff(currentRoles, roles)
				fmt.Println(toAdd, toRemove)

				rolesInsert = utils.Map(toAdd, func(id uuid.UUID) *bun.InsertQuery {
					userRole := models.UserToRole{
						UserID: uid,
						RoleID: id,
					}
					return core.DB.NewInsert().Model(&userRole).Ignore()
				})

				rolesRemoved = utils.Map(toRemove, func(id uuid.UUID) *bun.DeleteQuery {
					return core.DB.NewDelete().Model((*models.UserToRole)(nil)).
						Where("user_id = ? AND role_id = ?", uid, id)
				})

			} else {
				updateQuery.Set(fmt.Sprintf("%s = ?", strings.Split(tag, ",")[0]), field.Interface())
			}
		}
	}

	// Always update the `updated_at` field
	updateQuery.Set("updated_at = ?", time.Now())

	_, err = updateQuery.
		Where("user_id = ?", user_uuid).
		Exec(ctx)

	if err != nil {
		core.JSONError{
			Status:  core.Error,
			Message: err.Error(),
		}.Respond(w, http.StatusInternalServerError)
		return
	}

	for _, insert := range rolesInsert {
		_, err = insert.Exec(ctx)
		if err != nil {
			core.JSONError{
				Status:  core.Error,
				Message: err.Error(),
			}.Respond(w, http.StatusInternalServerError)
			return
		}
	}

	for _, remove := range rolesRemoved {
		_, err = remove.Exec(ctx)
		if err != nil {
			core.JSONError{
				Status:  core.Error,
				Message: err.Error(),
			}.Respond(w, http.StatusInternalServerError)
			return
		}
	}

	user.Password = ""

	core.JSONSuccess{
		Status:  core.Success,
		Message: "User updated.",
		Data:    user,
	}.Respond(w, http.StatusOK)
}
