/*
* @desc:角色管理
* @company:云南奇讯科技有限公司
* @Author: yixiaohu<yxh669@qq.com>
* @Date:   2022/9/26 15:54
 */

package sysRole

import (
	"context"
	"errors"
	"github.com/gogf/gf/v2/container/garray"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/text/gstr"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/tiger1103/gfast/v3/api/v1/system"
	commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
	"github.com/tiger1103/gfast/v3/internal/app/system/consts"
	"github.com/tiger1103/gfast/v3/internal/app/system/dao"
	"github.com/tiger1103/gfast/v3/internal/app/system/model"
	"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
	"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
	"github.com/tiger1103/gfast/v3/internal/app/system/service"
	"github.com/tiger1103/gfast/v3/library/libWebsocket"
	"github.com/tiger1103/gfast/v3/library/liberr"
)

func init() {
	service.RegisterSysRole(New())
}

func New() service.ISysRole {
	return &sSysRole{}
}

type sSysRole struct {
}

func (s *sSysRole) GetRoleListSearch(ctx context.Context, req *system.RoleListReq) (res *system.RoleListRes, err error) {
	res = new(system.RoleListRes)
	err = g.Try(ctx, func(ctx context.Context) {
		model := dao.SysRole.Ctx(ctx)
		if req.RoleName != "" {
			model = model.Where("a.name like ?", "%"+req.RoleName+"%")
		}
		if req.Status != "" {
			model = model.Where("a.status", gconv.Int(req.Status))
		}
		userId := service.Context().GetUserId(ctx)
		//获取当前用户所属角色ids
		if !service.SysUser().IsSupperAdmin(ctx, userId) {
			var roleIds []uint
			roleIds, err = service.SysUser().GetAdminRoleIds(ctx, userId, true)
			liberr.ErrIsNil(ctx, err)
			if len(roleIds) == 0 {
				return
			}
			model = model.Where("a."+dao.SysRole.Columns().Id+" in(?) OR a.created_by = ?", roleIds, userId)
		}
		model = model.As("a")
		if service.ToolsGenTable().IsMysql() {
			model = model.LeftJoin("casbin_rule", "b", "b.v1  = a.id ")
			model = model.LeftJoin("sys_user", "u", "CONCAT('u_',u.id) = b.v0 ")
		} else {
			model = model.LeftJoin("casbin_rule", "b", "b.v1  = cast(a.id AS VARCHAR) ")
			model = model.LeftJoin("sys_user", "u", "CONCAT('u_',u.id)  =  b.v0")
		}
		model = model.Group("a.id")
		err = model.Order("id asc").Fields("a.*, count(u.id) user_cnt").Scan(&res.List)
		liberr.ErrIsNil(ctx, err, "获取数据失败")
	})
	return
}

// GetRoleList 获取角色列表
func (s *sSysRole) GetRoleList(ctx context.Context) (list []*entity.SysRole, err error) {
	cache := commonService.Cache()
	//从缓存获取
	iList := cache.GetOrSetFuncLock(ctx, consts.CacheSysRole, s.getRoleListFromDb, 0, consts.CacheSysAuthTag)
	if !iList.IsEmpty() {
		err = gconv.Struct(iList, &list)
	}
	return
}

// 从数据库获取所有角色
func (s *sSysRole) getRoleListFromDb(ctx context.Context) (value interface{}, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		var v []*entity.SysRole
		//从数据库获取
		err = dao.SysRole.Ctx(ctx).
			Order(dao.SysRole.Columns().ListOrder + " asc," + dao.SysRole.Columns().Id + " asc").
			Scan(&v)
		liberr.ErrIsNil(ctx, err, "获取角色数据失败")
		value = v
	})
	return
}

// AddRoleRule 添加角色权限
func (s *sSysRole) AddRoleRule(ctx context.Context, ruleIds []uint, roleId int64) (err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		enforcer, e := commonService.CasbinEnforcer(ctx)
		liberr.ErrIsNil(ctx, e)
		ruleIdsStr := gconv.Strings(ruleIds)
		rules := make([][]string, len(ruleIdsStr))
		for k, v := range ruleIdsStr {
			rules[k] = []string{gconv.String(roleId), v, "All"}
		}
		if len(rules) > 0 {
			_, err = enforcer.AddPolicies(rules)
			liberr.ErrIsNil(ctx, err)
		}
	})
	return
}

// DelRoleRule 删除角色权限
func (s *sSysRole) DelRoleRule(ctx context.Context, roleId int64) (err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		enforcer, e := commonService.CasbinEnforcer(ctx)
		liberr.ErrIsNil(ctx, e)
		_, err = enforcer.RemoveFilteredPolicy(0, gconv.String(roleId))
		liberr.ErrIsNil(ctx, e)
	})
	return
}

func (s *sSysRole) AddRole(ctx context.Context, req *system.RoleAddReq) (err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		err = g.Try(ctx, func(ctx context.Context) {
			req.CreatedBy = service.Context().GetUserId(ctx)
			roleId, e := dao.SysRole.Ctx(ctx).TX(tx).InsertAndGetId(do.SysRole{
				Pid:           req.Pid,
				Status:        req.Status,
				ListOrder:     req.ListOrder,
				Name:          req.Name,
				Remark:        req.Remark,
				CreatedBy:     req.CreatedBy,
				EffectiveTime: req.EffectiveTimeInfo,
			})
			liberr.ErrIsNil(ctx, e, "添加角色失败")
			//过滤ruleIds 把没有权限的过滤掉
			req.MenuIds, err = s.filterAccessRuleIds(ctx, req.MenuIds)
			liberr.ErrIsNil(ctx, err)
			//添加角色权限
			e = s.AddRoleRule(ctx, req.MenuIds, roleId)
			liberr.ErrIsNil(ctx, e)
			//清除缓存
			commonService.Cache().Remove(ctx, consts.CacheSysRole)
		})
		return err
	})
	return
}

func (s *sSysRole) Get(ctx context.Context, id uint) (res *model.RoleInfoRes, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		//判断是否具有此角色的权限
		if !s.hasManageAccess(ctx, id, true) {
			liberr.ErrIsNil(ctx, errors.New("没有查看这个角色的权限"))
		}
		res = new(model.RoleInfoRes)
		err = dao.SysRole.Ctx(ctx).WherePri(id).Scan(&res.SysRole)
		liberr.ErrIsNil(ctx, err, "获取角色信息失败")
		err = gconv.Struct(res.SysRole.EffectiveTime, &res.EffectiveTimeInfo)
		if err != nil {
			res.EffectiveTimeInfo = new(model.EffectiveTimeInfo)
		}
	})
	return
}

// GetFilteredNamedPolicy 获取角色关联的菜单规则
func (s *sSysRole) GetFilteredNamedPolicy(ctx context.Context, id uint) (gpSlice []int, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		enforcer, e := commonService.CasbinEnforcer(ctx)
		liberr.ErrIsNil(ctx, e)
		gp := enforcer.GetFilteredNamedPolicy("p", 0, gconv.String(id))
		gpSlice = make([]int, len(gp))
		for k, v := range gp {
			gpSlice[k] = gconv.Int(v[1])
		}
	})
	return
}

func (s *sSysRole) hasManageAccess(ctx context.Context, roleId uint, includeChildren ...bool) bool {
	currentUserId := service.Context().GetUserId(ctx)
	if !service.SysUser().IsSupperAdmin(ctx, currentUserId) {
		var (
			roleIds   []uint
			hasAccess bool
			err       error
			list      []*entity.SysRole
		)
		list, err = s.GetRoleList(ctx)
		if err != nil {
			g.Log().Error(ctx, err)
			return false
		}
		for _, v := range list {
			//判断是否当前用户所建角色
			if roleId == v.Id && v.CreatedBy == currentUserId {
				return true
			}
		}
		roleIds, err = service.SysUser().GetAdminRoleIds(ctx, service.Context().GetUserId(ctx), includeChildren...)
		if err != nil {
			g.Log().Error(ctx, err)
			return false
		}
		if len(roleIds) > 0 {
			for _, v := range roleIds {
				if v == roleId {
					hasAccess = true
					break
				}
			}
		}
		return hasAccess
	}
	return true
}

// EditRole 修改角色
func (s *sSysRole) EditRole(ctx context.Context, req *system.RoleEditReq) (err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		err = g.Try(ctx, func(ctx context.Context) {
			//判断是否具有修改此角色的权限
			if !s.hasManageAccess(ctx, gconv.Uint(req.Id), true) {
				liberr.ErrIsNil(ctx, errors.New("没有修改这个角色的权限"))
			}
			_, e := dao.SysRole.Ctx(ctx).TX(tx).WherePri(req.Id).Data(&do.SysRole{
				Pid:           req.Pid,
				Status:        req.Status,
				ListOrder:     req.ListOrder,
				Name:          req.Name,
				Remark:        req.Remark,
				EffectiveTime: req.EffectiveTimeInfo,
			}).Update()
			liberr.ErrIsNil(ctx, e, "修改角色失败")
			//过滤ruleIds 把没有权限的过滤掉
			req.MenuIds, err = s.filterAccessRuleIds(ctx, req.MenuIds)
			liberr.ErrIsNil(ctx, err)
			//删除角色权限
			e = s.DelRoleRule(ctx, req.Id)
			liberr.ErrIsNil(ctx, e)
			//添加角色权限
			e = s.AddRoleRule(ctx, req.MenuIds, req.Id)
			liberr.ErrIsNil(ctx, e)
			//清除缓存
			commonService.Cache().Remove(ctx, consts.CacheSysRole)
			//通知刷新token
			s.refreshToken(ctx, req.Id)
		})
		return err
	})
	return
}

// 从给定的menuIds中过滤掉用户没有操作权限的菜单id
func (s *sSysRole) filterAccessRuleIds(ctx context.Context, menuIds []uint) (newRuleIds []uint, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		//若不是超管，过滤ruleIds 把没有权限的过滤掉
		if !service.SysUser().IsSupperAdmin(ctx, service.Context().GetUserId(ctx)) {
			var (
				userRoleIds []uint
				accessMenus *garray.Array
			)
			userRoleIds, err = service.SysUser().GetAdminRoleIds(ctx, service.Context().GetUserId(ctx))
			liberr.ErrIsNil(ctx, err)
			accessMenus, err = service.SysUser().GetAdminMenusIdsByRoleIds(ctx, userRoleIds)
			for _, v := range menuIds {
				if accessMenus.Contains(v) {
					newRuleIds = append(newRuleIds, v)
				}
			}
		} else {
			newRuleIds = menuIds
		}
	})
	return
}

// DeleteByIds 删除角色
func (s *sSysRole) DeleteByIds(ctx context.Context, ids []int64) (err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		err = g.Try(ctx, func(ctx context.Context) {
			for _, id := range ids {
				//判断是否有删除该角色的权限
				if !s.hasManageAccess(ctx, gconv.Uint(id)) {
					liberr.ErrIsNil(ctx, errors.New("没有删除这个角色的权限"))
				}
			}
			_, err = dao.SysRole.Ctx(ctx).TX(tx).Where(dao.SysRole.Columns().Id+" in(?)", ids).Delete()
			liberr.ErrIsNil(ctx, err, "删除角色失败")
			//删除角色权限
			for _, v := range ids {
				err = s.DelRoleRule(ctx, v)
				liberr.ErrIsNil(ctx, err)
			}
			//清除缓存
			commonService.Cache().Remove(ctx, consts.CacheSysRole)
		})
		return err
	})
	return
}

func (s *sSysRole) RoleDeptTreeSelect(ctx context.Context) (res *system.RoleDeptTreeSelectRes, err error) {
	res = new(system.RoleDeptTreeSelectRes)
	err = g.Try(ctx, func(ctx context.Context) {
		list, err := service.SysDept().GetList(ctx, &system.DeptSearchReq{
			Status: "1",
		})
		liberr.ErrIsNil(ctx, err)
		dList := service.SysDept().GetListTree(0, list)
		res.Depts = dList
	})
	return
}

func (s *sSysRole) GetRoleDataScope(ctx context.Context, roleId uint) (data []*model.ScopeAuthData, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		err = dao.SysRoleScope.Ctx(ctx).Where("role_id", roleId).Scan(&data)
		liberr.ErrIsNil(ctx, err, "获取角色数据权限失败")
	})
	return
}

func (s *sSysRole) GetRoleMenuScope(ctx context.Context, roleIds []uint, menuId uint) (data []*model.ScopeAuthData, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		err = dao.SysRoleScope.Ctx(ctx).WhereIn("role_id", roleIds).
			Where("menu_id", menuId).
			Scan(&data)
		liberr.ErrIsNil(ctx, err, "获取角色数据权限失败")
	})
	return
}

// RoleDataScope 设置角色数据权限
func (s *sSysRole) RoleDataScope(ctx context.Context, req *system.DataScopeReq) error {
	err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		err := g.Try(ctx, func(ctx context.Context) {
			data := make([]do.SysRoleScope, 0, len(req.AuthData))
			for _, v := range req.AuthData {
				if v.MenuId != 0 && v.Scope != 0 {
					data = append(data, do.SysRoleScope{
						RoleId:    req.RoleId,
						MenuId:    v.MenuId,
						DataScope: v.Scope,
						DeptIds:   gconv.String(v.DeptIds),
					})
				}
			}
			//清除旧权限
			_, err := dao.SysRoleScope.Ctx(ctx).Where(dao.SysRoleScope.Columns().RoleId, req.RoleId).
				Delete()
			liberr.ErrIsNil(ctx, err, "清除旧权限信息失败")
			if len(data) > 0 {
				_, err = dao.SysRoleScope.Ctx(ctx).Data(data).Insert()
				liberr.ErrIsNil(ctx, err, "设置权限信息失败")
			}
			//通知刷新token
			s.refreshToken(ctx, gconv.Int64(req.RoleId))
		})
		return err
	})
	return err
}

func (s *sSysRole) FindSonByParentId(roleList []*entity.SysRole, id uint) []*entity.SysRole {
	children := make([]*entity.SysRole, 0, len(roleList))
	for _, v := range roleList {
		if v.Pid == id {
			children = append(children, v)
			fChildren := s.FindSonByParentId(roleList, v.Id)
			children = append(children, fChildren...)
		}
	}
	return children
}

func (s *sSysRole) FindSonIdsByParentId(roleList []*entity.SysRole, id uint) []uint {
	children := make([]uint, 0, len(roleList))
	for _, v := range roleList {
		if v.Pid == id {
			children = append(children, v.Id)
			fChildren := s.FindSonIdsByParentId(roleList, v.Id)
			children = append(children, fChildren...)
		}
	}
	return children
}

// 刷新角色下用户token
func (s *sSysRole) refreshToken(ctx context.Context, roleId int64) {
	_ = g.Try(ctx, func(ctx context.Context) {
		enforcer, e := commonService.CasbinEnforcer(ctx)
		liberr.ErrIsNil(ctx, e)
		userRoleIds := enforcer.GetFilteredGroupingPolicy(1, gconv.String(roleId))
		for _, v := range userRoleIds {
			userId := gstr.Split(v[0], "_")[1]
			//通知用户更新token
			libWebsocket.SendToUser(gconv.Uint64(userId), &libWebsocket.WResponse{
				Event: consts.WebsocketTypeTokenUpdated,
				Data:  nil,
			})
		}
	})
}
