// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.

package controllers

import (
	"errors"
	"fmt"
	"strconv"

	"git.smarteching.com/goffee/core"
	"git.smarteching.com/goffee/core/template/components"
	"git.smarteching.com/goffee/cup/events"
	"git.smarteching.com/goffee/cup/models"
	"git.smarteching.com/goffee/cup/utils"
	"gorm.io/gorm"
)

func AdminUsersList(c *core.Context) *core.Response {

	// initiate authority
	auth := new(utils.Authority)
	var session = new(utils.SessionUser)
	// true if session is active
	hassession := session.Init(c)

	if !hassession {
		type emptytemplate struct{}
		emptyData := emptytemplate{}
		return c.Response.Template("nopermission.html", emptyData)
	}

	session_uid := session.GetUserID()
	// check if user has role admin
	is_admin, _ := auth.CheckUserRole(c, session_uid, "admin")

	if !is_admin {
		type emptytemplate struct{}
		emptyData := emptytemplate{}
		return c.Response.Template("nopermission.html", emptyData)
	}

	// continue if has session and is admin
	var users []models.User
	db := c.GetGorm()
	db.Find(&users)

	// -- response template
	type templateData struct {
		TableUsers components.ContentTable
		AddButton  components.ContentHref
	}

	cols := []components.ContentTableTH{
		{
			Value:     "UID",
			ValueType: "number",
		},
		{
			Value:     "Name",
			ValueType: "string",
		},
		{
			Value:     "Fullname",
			ValueType: "string",
		},
		{
			Value:     "Email",
			ValueType: "string",
		},
		{
			Value:     "Roles",
			ValueType: "string",
		},
		{
			Value: "Created",
		},
		{
			Value: "Updated",
		},
		{
			ValueType: "href",
		},
	}

	var listroles string
	rows := make([][]components.ContentTableTD, len(users))
	for i, u := range users {

		roles, _ := auth.GetUserRoles(c, u.ID)
		listroles = ""

		for _, role := range roles {
			if listroles != "" {
				listroles += ", "
			}
			listroles += role.Name
		}

		row := []components.ContentTableTD{
			{Value: strconv.Itoa(int(u.ID))},
			{Value: u.Name},
			{Value: u.Fullname},
			{Value: u.Email},
			{Value: listroles},
			{Value: utils.FormatUnix(u.Created)},
			{Value: utils.FormatUnix(u.Updated)},
			{Value: components.ContentHref{
				Text:      "edit",
				Link:      "/admin/users/edit/" + strconv.Itoa(int(u.ID)),
				TypeClass: "outline-secondary",
			}},
		}
		rows[i] = row
	}

	tmplData := templateData{
		TableUsers: components.ContentTable{
			ID:    "table_demo",
			AllTH: cols,
			AllTD: rows,
		},
		AddButton: components.ContentHref{
			Text:      "Register",
			Link:      "/admin/users/add",
			IsButton:  true,
			TypeClass: "outline-primary",
		},
	}
	return c.Response.Template("admin_userlist.html", tmplData)
}

func AdminUsersAdd(c *core.Context) *core.Response {

	// initiate authority
	auth := new(utils.Authority)
	var session = new(utils.SessionUser)
	// true if session is active
	hassession := session.Init(c)

	if !hassession {
		type emptytemplate struct{}
		emptyData := emptytemplate{}
		return c.Response.Template("nopermission.html", emptyData)
	}

	session_uid := session.GetUserID()
	// check if user has role admin
	is_admin, _ := auth.CheckUserRole(c, session_uid, "admin")

	if !is_admin {
		type emptytemplate struct{}
		emptyData := emptytemplate{}
		return c.Response.Template("nopermission.html", emptyData)
	}

	// check if is submit
	submit := c.GetRequestParam("submit").(string)
	errormessages := make([]string, 0)
	var listroles []components.FormCheckboxItem
	systemroles, _ := auth.GetAllRoles(c)

	for _, systemrole := range systemroles {
		var userrole components.FormCheckboxItem
		userrole.Label = systemrole.Name
		userrole.Name = "roles"
		userrole.Value = systemrole.Slug
		if systemrole.Slug == "authenticated" {
			userrole.IsChecked = true
		}
		listroles = append(listroles, userrole)
	}

	name := ""
	fullname := ""
	email := ""
	password := ""

	if submit != "" {

		name = c.GetRequestParam("name").(string)
		fullname = c.GetRequestParam("fullname").(string)
		email = c.GetRequestParam("email").(string)
		password = c.GetRequestParam("password").(string)
		roles := c.GetRequesForm("roles").([]string)

		// check if email exists
		var user models.User
		res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
		if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
			c.GetLogger().Error(res.Error.Error())
			errormessages = append(errormessages, "Error")
		}
		if res.Error == nil {
			errormessages = append(errormessages, "Error, email already exists in the database")
		}

		// validation data
		data := map[string]interface{}{
			"name":     name,
			"fullname": fullname,
			"email":    email,
			"password": password,
		}
		// validation rules
		rules := map[string]interface{}{
			"name":     "required|alphaNumeric",
			"fullname": "required",
			"email":    "required|email",
			"password": "required|length:6,20",
		}
		// validate
		v := c.GetValidator().Validate(data, rules)
		if v.Failed() {
			c.GetLogger().Error(v.GetErrorMessagesJson())
			for _, v := range v.GetErrorMessagesMap() {
				errormessages = append(errormessages, v)
			}
		}

		if len(errormessages) == 0 {

			//hash the password
			passwordHashed, _ := c.GetHashing().HashPassword(c.CastToString(password))
			// store the record in db
			user = models.User{
				Name:     c.CastToString(name),
				Fullname: c.CastToString(fullname),
				Email:    c.CastToString(email),
				Password: passwordHashed,
			}
			res = c.GetGorm().Create(&user)
			if res.Error != nil {
				c.GetLogger().Error(res.Error.Error())
				errormessages = append(errormessages, res.Error.Error())
			} else {

				// assign roles
				for _, role := range roles {
					auth.AssignRoleToUser(c, user.ID, role)
				}

				// fire user registered event
				err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{
					"user": user,
				}})
				if err != nil {
					c.GetLogger().Error(err.Error())
					errormessages = append(errormessages, "Internal server error")
				} else {
					// redirect to list
					return c.Response.Redirect("/admin/users")
				}
			}
		}
	}
	// -- response template
	type templateData struct {
		FieldName     components.FormInput
		FieldFullname components.FormInput
		FieldEmail    components.FormInput
		FieldRoles    components.FormCheckbox
		FieldPassword components.FormInput
		ErrorMessages []string
		SubmitButton  components.FormButton
	}

	tmplData := templateData{
		FieldName: components.FormInput{
			ID:         "name",
			Label:      "Name",
			Type:       "text",
			Value:      name,
			IsRequired: true,
		},
		FieldFullname: components.FormInput{
			ID:         "fullname",
			Label:      "Full name",
			Type:       "text",
			Value:      fullname,
			IsRequired: true,
		},
		FieldEmail: components.FormInput{
			ID:    "email",
			Label: "e-mail",
			Type:  "text",
			Value: email,
			//Autocomplete: true,
			IsRequired: true,
		},
		FieldRoles: components.FormCheckbox{
			Label:       "Roles",
			AllCheckbox: listroles,
		},
		FieldPassword: components.FormInput{
			ID:         "password",
			Label:      "Password",
			Type:       "password",
			Value:      password,
			IsRequired: true,
		},
		SubmitButton: components.FormButton{
			ID:        "submit",
			Text:      "Add user",
			Value:     "submit",
			IsSubmit:  true,
			TypeClass: "primary",
		},
		ErrorMessages: errormessages,
	}
	return c.Response.Template("admin_useradd.html", tmplData)

}

func AdminUsersEdit(c *core.Context) *core.Response {

	// check if is submit
	submit := c.GetRequestParam("submit").(string)

	user_id := c.GetPathParam("id")

	errormessages := make([]string, 0)
	// initiate authority
	auth := new(utils.Authority)

	var listroles []components.FormCheckboxItem

	systemroles, _ := auth.GetAllRoles(c)
	user_id_uint, _ := strconv.ParseUint(user_id.(string), 10, 32)

	userroles, _ := auth.GetUserRoles(c, uint(user_id_uint))

	for _, systemrole := range systemroles {
		var userrole components.FormCheckboxItem
		userrole.Label = systemrole.Name
		userrole.Name = "roles"
		userrole.Value = systemrole.Slug
		for _, ur := range userroles {
			if ur.Slug == systemrole.Slug {
				userrole.IsChecked = true
				break
			}
		}
		listroles = append(listroles, userrole)
	}

	var origin_user models.User

	db := c.GetGorm()
	// check if existes
	result_db := db.First(&origin_user, user_id)
	if result_db.RowsAffected == 0 {
		c.GetLogger().Error("User ID not found")
		return c.Response.Redirect("/admin/users")
	}

	name := origin_user.Name
	fullname := origin_user.Fullname
	email := origin_user.Email
	password := ""
	user_id_string := c.GetPathParam("id").(string)

	if submit != "" {

		name = c.GetRequestParam("name").(string)
		fullname = c.GetRequestParam("fullname").(string)
		email = c.GetRequestParam("email").(string)
		password = c.GetRequestParam("password").(string)
		roles := c.GetRequesForm("roles").([]string)
		key := c.GetRequestParam("key")

		// check if email exists
		var user models.User
		res := c.GetGorm().Where("email = ? AND id != ?", c.CastToString(email), key).First(&user)
		if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
			c.GetLogger().Error(res.Error.Error())
			errormessages = append(errormessages, "Error")
		}
		if res.Error == nil {
			errormessages = append(errormessages, "Error, email already exists to another user")
		}

		// validation data
		data := map[string]interface{}{
			"name":     name,
			"fullname": fullname,
			"email":    email,
		}
		// validation rules
		rules := map[string]interface{}{
			"name":     "required|alphaNumeric",
			"fullname": "required",
			"email":    "required|email",
		}

		// nee update password
		if password != "" {
			data["password"] = password
			rules["password"] = "required|length:6,20"
		}
		// validate
		v := c.GetValidator().Validate(data, rules)
		if v.Failed() {
			c.GetLogger().Error(v.GetErrorMessagesJson())
			for _, v := range v.GetErrorMessagesMap() {
				errormessages = append(errormessages, v)
			}
		}

		if len(errormessages) == 0 {

			//hash the password
			if password != "" {
				passwordHashed, _ := c.GetHashing().HashPassword(c.CastToString(password))
				origin_user.Password = passwordHashed
			}
			// store the record in db
			origin_user.Name = name
			origin_user.Fullname = fullname
			origin_user.Email = email

			result_db = db.Save(&origin_user)
			if result_db.RowsAffected == 0 {
				c.GetLogger().Error("Admin user: error updating")
				errormessages = append(errormessages, fmt.Sprintf("Error updating user %s:", user_id_string))
			} else {

				// delete roles
				auth.RevokeAllUserRole(c, origin_user.ID)
				// assign roles
				for _, role := range roles {
					auth.AssignRoleToUser(c, origin_user.ID, role)
				}

				return c.Response.Redirect("/admin/users")
			}
		}
	}
	// -- response template
	type templateData struct {
		FieldName     components.FormInput
		FieldFullname components.FormInput
		FieldEmail    components.FormInput
		FieldRoles    components.FormCheckbox
		FieldPassword components.FormInput
		FieldKey      components.FormInput
		ErrorMessages []string
		SubmitButton  components.FormButton
		DeleteButton  components.FormButton
	}

	tmplData := templateData{
		FieldName: components.FormInput{
			ID:         "name",
			Label:      "Name",
			Type:       "text",
			Value:      name,
			IsRequired: true,
		},
		FieldFullname: components.FormInput{
			ID:         "fullname",
			Label:      "Full name",
			Type:       "text",
			Value:      fullname,
			IsRequired: true,
		},
		FieldEmail: components.FormInput{
			ID:    "email",
			Label: "e-mail",
			Type:  "text",
			Value: email,
			//Autocomplete: true,
			IsRequired: true,
		},
		FieldRoles: components.FormCheckbox{
			Label:       "Roles",
			AllCheckbox: listroles,
		},
		FieldPassword: components.FormInput{
			ID:         "password",
			Label:      "Password",
			Type:       "password",
			Hint:       "Leave blank if you don't want to change it",
			Value:      password,
			IsRequired: false,
		},
		FieldKey: components.FormInput{
			ID:    "key",
			Type:  "hidden",
			Value: user_id_string,
		},
		SubmitButton: components.FormButton{
			ID:        "submit",
			Text:      "Update user",
			Value:     "submit",
			IsSubmit:  true,
			TypeClass: "primary",
		},
		DeleteButton: components.FormButton{
			ID:        "submit",
			Text:      "Delete user",
			Value:     "submit",
			IsSubmit:  true,
			TypeClass: "warning",
		},
		ErrorMessages: errormessages,
	}
	return c.Response.Template("admin_useredit.html", tmplData)

}

func AdminUsersDelete(c *core.Context) *core.Response {

	user_id := c.GetRequestParam("key").(string)

	errormessages := make([]string, 0)
	warningmessages := make([]string, 0)

	var origin_user models.User

	db := c.GetGorm()
	// check if existes
	result_db := db.First(&origin_user, user_id)
	if result_db.RowsAffected == 0 {
		errormessages = append(errormessages, "User ID not found")
	} else {
		// check if is the seed user
		seed := "1"
		if user_id == seed {
			errormessages = append(errormessages, "You can't delete the seed user")
		}

	}

	// sample warning message
	warningmessages = append(warningmessages, fmt.Sprintf("Are you sure you want to cancel the account %s?", origin_user.Name))

	// -- response template
	type templateData struct {
		ErrorMessages   []string
		WarningMessages []string
		FieldKey        components.FormInput
		ConfirmButton   components.FormButton
		BackButton      components.ContentHref
	}

	tmplData := templateData{
		FieldKey: components.FormInput{
			ID:    "key",
			Type:  "hidden",
			Value: user_id,
		},
		ConfirmButton: components.FormButton{
			ID:        "submit",
			Text:      "Confirm",
			Value:     "submit",
			IsSubmit:  true,
			TypeClass: "primary",
		},
		BackButton: components.ContentHref{
			Link:     "/admin/users",
			Text:     "Cancel",
			IsButton: true,
		},
		ErrorMessages:   errormessages,
		WarningMessages: warningmessages,
	}
	return c.Response.Template("admin_confirmuserdel.html", tmplData)

}

func AdminUsersDelConfirm(c *core.Context) *core.Response {

	user_id := c.GetRequestParam("key").(string)

	var origin_user models.User

	db := c.GetGorm()
	// check if existes
	result_db := db.First(&origin_user, user_id)
	if result_db.RowsAffected != 0 {
		// check if is the seed user
		seed := "1"
		if user_id != seed {

			// initiate authority
			auth := new(utils.Authority)
			// Delete the user
			// fire user delete event
			err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_DELETED, Payload: map[string]interface{}{
				"user": origin_user,
			}})
			if err != nil {
				c.GetLogger().Error(err.Error())
			}
			auth.RevokeAllUserRole(c, origin_user.ID)
			result_db.Unscoped().Delete(&origin_user)
		}
	}

	return c.Response.Redirect("/admin/users")

}