// Copyright (c) 2025 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 utils

import (
	"errors"
	"fmt"

	"git.smarteching.com/goffee/core"
	"git.smarteching.com/jacs/goffeetabler/models"
	"gorm.io/gorm"
)

type Authority struct{}

var auth *Authority

var (
	ErrPermissionInUse    = errors.New("cannot delete assigned permission")
	ErrPermissionNotFound = errors.New("permission not found")
	ErrRoleInUse          = errors.New("cannot delete assigned role")
	ErrRoleNotFound       = errors.New("role not found")
)

// Add a new role to the database
func (a *Authority) CreateRole(c *core.Context, r models.Role) error {
	roleSlug := r.Slug
	var dbRole models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).First(&dbRole)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			// create
			createRes := c.GetGorm().Create(&r)
			if createRes.Error != nil {
				return createRes.Error
			}
			return nil
		}
		return res.Error
	}

	return errors.New(fmt.Sprintf("role '%v' already exists", roleSlug))
}

// Add a new permission to the database
func (a *Authority) CreatePermission(c *core.Context, p models.Permission) error {
	permSlug := p.Slug
	var dbPerm models.Permission
	res := c.GetGorm().Where("slug = ?", permSlug).First(&dbPerm)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			// create
			createRes := c.GetGorm().Create(&p)
			if createRes.Error != nil {
				return createRes.Error
			}
			return nil
		}
		return res.Error
	}

	return errors.New(fmt.Sprintf("permission '%v' already exists", permSlug))
}

// Assigns a group of permissions to a given role
func (a *Authority) AssignPermissionsToRole(c *core.Context, roleSlug string, permSlugs []string) error {
	var role models.Role
	rRes := c.GetGorm().Where("slug = ?", roleSlug).First(&role)
	if rRes.Error != nil {
		if errors.Is(rRes.Error, gorm.ErrRecordNotFound) {
			return ErrRoleNotFound
		}
		return rRes.Error
	}
	var perms []models.Permission
	for _, permSlug := range permSlugs {
		var perm models.Permission
		pRes := c.GetGorm().Where("slug = ?", permSlug).First(&perm)
		if pRes.Error != nil {
			if errors.Is(pRes.Error, gorm.ErrRecordNotFound) {
				return ErrPermissionNotFound
			}
			return pRes.Error
		}
		perms = append(perms, perm)
	}
	tx := c.GetGorm().Begin()
	for _, perm := range perms {
		var rolePerm models.RolePermission
		res := c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id =?", perm.ID).First(&rolePerm)
		if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
			cRes := tx.Create(&models.RolePermission{RoleID: role.ID, PermissionID: perm.ID})
			if cRes.Error != nil {
				tx.Rollback()
				return cRes.Error
			}
		}
		if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
			tx.Rollback()
			return res.Error
		}
		if rolePerm != (models.RolePermission{}) {
			tx.Rollback()
			return errors.New(fmt.Sprintf("permission '%v' is aleady assigned to the role '%v'", perm.Name, role.Name))
		}
		rolePerm = models.RolePermission{}
	}
	return tx.Commit().Error
}

// Assigns a role to a given user
func (a *Authority) AssignRoleToUser(c *core.Context, userID uint, roleSlug string) error {
	var role models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).First(&role)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return ErrRoleNotFound
		}
		return res.Error
	}
	var userRole models.UserRole
	res = c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).First(&userRole)
	if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
		c.GetGorm().Create(&models.UserRole{UserID: userID, RoleID: role.ID})
		return nil
	}
	if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
		return res.Error
	}

	return errors.New(fmt.Sprintf("this role '%v' is aleady assigned to the user", roleSlug))
}

// Checks if a role is assigned to a user
func (a *Authority) CheckUserRole(c *core.Context, userID uint, roleSlug string) (bool, error) {
	// find the role
	var role models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).First(&role)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return false, ErrRoleNotFound
		}
		return false, res.Error
	}

	// check if the role is a assigned
	var userRole models.UserRole
	res = c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).First(&userRole)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return false, nil
		}
		return false, res.Error
	}

	return true, nil
}

// Checks if a permission is assigned to a user
func (a *Authority) CheckUserPermission(c *core.Context, userID uint, permSlug string) (bool, error) {
	// the user role
	var userRoles []models.UserRole
	res := c.GetGorm().Where("user_id = ?", userID).Find(&userRoles)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return false, nil
		}
		return false, res.Error
	}

	//prepare an array of role ids
	var roleIDs []interface{}
	for _, r := range userRoles {
		roleIDs = append(roleIDs, r.RoleID)
	}

	// find the permission
	var perm models.Permission
	res = c.GetGorm().Where("slug = ?", permSlug).First(&perm)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return false, ErrPermissionNotFound
		}
		return false, res.Error
	}

	// find the role permission
	var rolePermission models.RolePermission
	res = c.GetGorm().Where("role_id IN (?)", roleIDs).Where("permission_id = ?", perm.ID).First(&rolePermission)
	if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
		return false, nil
	}
	if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
		return false, res.Error
	}

	return true, nil
}

// Checks if a permission is assigned to a role
func (a *Authority) CheckRolePermission(c *core.Context, roleSlug string, permSlug string) (bool, error) {
	// find the role
	var role models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).First(&role)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return false, ErrRoleNotFound
		}
		return false, res.Error
	}

	// find the permission
	var perm models.Permission
	res = c.GetGorm().Where("slug = ?", permSlug).First(&perm)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return false, ErrPermissionNotFound
		}
		return false, res.Error
	}

	// find the rolePermission
	var rolePermission models.RolePermission
	res = c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id = ?", perm.ID).First(&rolePermission)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return false, nil
		}
		return false, res.Error
	}

	return true, nil
}

// Revokes a roles's permission
func (a *Authority) RevokeRolePermission(c *core.Context, roleSlug string, permSlug string) error {
	// find the role
	var role models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).First(&role)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return ErrRoleNotFound
		}
		return res.Error
	}

	// find the permission
	var perm models.Permission
	res = c.GetGorm().Where("slug = ?", permSlug).First(&perm)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return ErrPermissionNotFound
		}
		return res.Error
	}

	// revoke the permission
	rRes := c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id = ?", perm.ID).Delete(models.RolePermission{})
	if rRes.Error != nil {
		return rRes.Error
	}

	return nil
}

// Revokes a user's role
func (a *Authority) RevokeUserRole(c *core.Context, userID uint, roleSlug string) error {
	// find the role
	var role models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).First(&role)
	if res.Error != nil {
		if errors.Is(res.Error, gorm.ErrRecordNotFound) {
			return ErrRoleNotFound
		}
		return res.Error
	}

	// revoke the role
	rRes := c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).Delete(models.UserRole{})
	if rRes.Error != nil {
		return rRes.Error
	}

	return nil
}

// Revokes all user's role
func (a *Authority) RevokeAllUserRole(c *core.Context, userID uint) error {
	// revoke the role
	rRes := c.GetGorm().Where("user_id = ?", userID).Delete(models.UserRole{})
	if rRes.Error != nil {
		return rRes.Error
	}

	return nil
}

// Returns all stored roles
func (a *Authority) GetAllRoles(c *core.Context) ([]models.Role, error) {
	var roles []models.Role
	res := c.GetGorm().Find(&roles)
	if res.Error != nil {
		return nil, res.Error
	}

	return roles, nil
}

// Returns all user assigned roles
func (a *Authority) GetUserRoles(c *core.Context, userID uint) ([]models.Role, error) {
	var userRoles []models.UserRole
	res := c.GetGorm().Where("user_id = ?", userID).Find(&userRoles)
	if res.Error != nil {
		return nil, res.Error
	}

	var roleIDs []interface{}
	for _, r := range userRoles {
		roleIDs = append(roleIDs, r.RoleID)
	}

	var roles []models.Role
	res = c.GetGorm().Where("id IN (?)", roleIDs).Find(&roles)
	if res.Error != nil {
		return nil, res.Error
	}

	return roles, nil
}

// Returns all role assigned permissions
func (a *Authority) GetRolePermissions(c *core.Context, roleSlug string) ([]models.Permission, error) {
	var role models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).Find(&role)
	if res.Error != nil {
		return nil, res.Error
	}

	var rolePerms []models.RolePermission
	res = c.GetGorm().Where("role_id = ?", role.ID).Find(&rolePerms)
	if res.Error != nil {
		return nil, res.Error
	}
	var permIDs []interface{}
	for _, rolePerm := range rolePerms {
		permIDs = append(permIDs, rolePerm.PermissionID)
	}

	var perms []models.Permission
	res = c.GetGorm().Where("id IN (?)", permIDs).Find(&perms)
	if res.Error != nil {
		return nil, res.Error
	}

	return perms, nil
}

// Returns all stored permissions
func (a *Authority) GetAllPermissions(c *core.Context) ([]models.Permission, error) {
	var perms []models.Permission
	res := c.GetGorm().Find(&perms)
	if res.Error != nil {
		return nil, res.Error
	}

	return perms, nil
}

// Deletes a given role even if it's has assigned permissions
func (a *Authority) DeleteRole(c *core.Context, roleSlug string) error {
	// find the role
	var role models.Role
	res := c.GetGorm().Where("slug = ?", roleSlug).First(&role)
	if res.Error != nil {
		return res.Error
	}

	// check if the role is assigned to a user
	var ca int64
	res = c.GetGorm().Model(models.UserRole{}).Where("role_id = ?", role.ID).Count(&ca)
	if res.Error != nil {
		return res.Error
	}

	if ca != 0 {
		// role is assigned
		return ErrRoleInUse
	}
	tx := c.GetGorm().Begin()
	// revoke the assignment of permissions before deleting the role
	dRes := tx.Where("role_id = ?", role.ID).Delete(models.RolePermission{})
	if dRes.Error != nil {
		tx.Rollback()
		return dRes.Error
	}

	// delete the role
	dRes = c.GetGorm().Where("slug = ?", roleSlug).Delete(models.Role{})
	if dRes.Error != nil {
		tx.Rollback()
		return dRes.Error
	}

	return tx.Commit().Error
}

// Deletes a given permission
func (a *Authority) DeletePermission(c *core.Context, permSlug string) error {
	// find the permission
	var perm models.Permission
	res := c.GetGorm().Where("slug = ?", permSlug).First(&perm)
	if res.Error != nil {
		return res.Error
	}

	// check if the permission is assigned to a role
	var rolePermission models.RolePermission
	res = c.GetGorm().Where("permission_id = ?", perm.ID).First(&rolePermission)
	if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
		return res.Error
	}

	if res.Error == nil {
		return ErrPermissionInUse
	}

	// delete the permission
	dRes := c.GetGorm().Where("slug = ?", permSlug).Delete(models.Permission{})
	if dRes.Error != nil {
		return dRes.Error
	}

	return nil
}
