package system

import (
	"context"
	"errors"
	"kra/internal/biz/system"

	"gorm.io/gorm"
)

var errInvalidDataScope = errors.New("数据权限范围不合法")

type authorityAccessRepo struct{ data Provider }

func NewAuthorityAccessRepo(data Provider) system.AuthorityAccessRepo {
	return &authorityAccessRepo{data: data}
}

func (r *authorityAccessRepo) strictAuthorityIDs(ctx context.Context, actorID uint) (map[uint]bool, error) {
	allowed := make(map[uint]bool)
	if actorID == 0 {
		return allowed, nil
	}
	var actor authorityPO
	if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actorID).First(&actor).Error; err != nil {
		return nil, err
	}
	var authorities []authorityPO
	if err := r.data.DB().WithContext(ctx).Find(&authorities).Error; err != nil {
		return nil, err
	}
	children := make(map[uint][]uint)
	for _, authority := range authorities {
		if authority.ParentID != nil {
			children[*authority.ParentID] = append(children[*authority.ParentID], authority.AuthorityID)
		}
	}
	var walk func(uint)
	walk = func(id uint) {
		for _, child := range children[id] {
			if !allowed[child] {
				allowed[child] = true
				walk(child)
			}
		}
	}
	if actor.ParentID == nil || *actor.ParentID == 0 {
		allowed[actorID] = true
	}
	walk(actorID)
	if actor.ParentID != nil && *actor.ParentID != 0 {
		// A non-root authority manages descendants, never itself. Removing the
		// actor explicitly also keeps malformed cyclic trees from reopening the
		// self-management path.
		delete(allowed, actorID)
	}
	return allowed, nil
}

func (r *authorityAccessRepo) strictAuthorityAccess(ctx context.Context) (system.Actor, map[uint]bool, bool, error) {
	config := r.data.Runtime().Admin()
	if config == nil || config.System == nil || !config.System.UseStrictAuth {
		return system.Actor{}, nil, false, nil
	}
	actor, ok := system.ActorFromContext(ctx)
	if !ok {
		return system.Actor{}, nil, true, errors.New("您提交的角色ID不合法")
	}
	allowed, err := r.strictAuthorityIDs(ctx, actor.AuthorityID)
	if err != nil {
		return system.Actor{}, nil, true, err
	}
	return actor, allowed, true, nil
}

func (r *authorityAccessRepo) checkAuthorityIDsAuth(ctx context.Context, targetIDs []uint) error {
	_, allowed, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil || !strict {
		return err
	}
	for _, targetID := range targetIDs {
		if !allowed[targetID] {
			return errors.New("您提交的角色ID不合法")
		}
	}
	return nil
}

func (r *authorityAccessRepo) checkAuthorityIDAuth(ctx context.Context, targetID uint) error {
	return r.checkAuthorityIDsAuth(ctx, []uint{targetID})
}

func managedAuthorityParent(actor system.Actor, allowed map[uint]bool, targetID uint, parentID *uint, creating bool) (*uint, error) {
	if creating && (parentID == nil || *parentID == 0) {
		value := actor.AuthorityID
		return &value, nil
	}
	if !creating && targetID == actor.AuthorityID && allowed[actor.AuthorityID] && (parentID == nil || *parentID == 0) {
		return parentID, nil
	}
	if parentID == nil || *parentID == 0 || *parentID == targetID || (*parentID != actor.AuthorityID && !allowed[*parentID]) {
		return nil, errors.New("您提交的角色ID不合法")
	}
	return parentID, nil
}

func (r *authorityAccessRepo) ensureAuthorityParentAcyclic(ctx context.Context, targetID uint, parentID *uint) error {
	if parentID == nil || *parentID == 0 {
		return nil
	}
	current := *parentID
	visited := make(map[uint]struct{})
	for current != 0 {
		if current == targetID {
			return errors.New("角色父级不能形成循环")
		}
		if _, seen := visited[current]; seen {
			return errors.New("角色层级存在循环")
		}
		visited[current] = struct{}{}
		var authority authorityPO
		if err := r.data.DB().WithContext(ctx).Select("authority_id", "parent_id").Where("authority_id = ?", current).First(&authority).Error; err != nil {
			return err
		}
		if authority.ParentID == nil {
			return nil
		}
		current = *authority.ParentID
	}
	return nil
}

func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *system.Authority) error {
	if value.DataScope == 0 {
		value.DataScope = 1
	} else if value.DataScope < 1 || value.DataScope > 5 {
		return errInvalidDataScope
	}
	if err := r.checkDataScopeGrant(ctx, value.DataScope, nil); err != nil {
		return err
	}
	actor, allowed, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil {
		return err
	}
	if strict {
		value.ParentID, err = managedAuthorityParent(actor, allowed, value.AuthorityID, value.ParentID, true)
		if err != nil {
			return err
		}
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// The administration contract checks existence with First and treats every result other than
		// ErrRecordNotFound as a duplicate-role error. Keep that precedence so
		// duplicate IDs win over strict-tree validation and the API message stays
		// compatible.
		var existing authorityPO
		if err := tx.Where("authority_id = ?", value.AuthorityID).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("存在相同角色id")
		}
		if value.DefaultRouter == "" {
			value.DefaultRouter = "dashboard"
		}
		po := authorityPO{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
		if err := tx.Create(&po).Error; err != nil {
			return err
		}
		value.CreatedAt, value.UpdatedAt = po.CreatedAt, po.UpdatedAt
		value.DataScope, value.DefaultRouter = po.DataScope, po.DefaultRouter
		value.Menus = []*system.Menu{{ID: 1, Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Sort: 1, Title: "仪表盘", Icon: "setting"}}
		var dashboard menuPO
		if err := tx.Where("name = ?", "dashboard").First(&dashboard).Error; err == nil {
			grantDashboard := true
			if strict && !allowed[actor.AuthorityID] {
				var count int64
				if err = tx.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", actor.AuthorityID, dashboard.ID).Count(&count).Error; err != nil {
					return err
				}
				grantDashboard = count > 0
			}
			if grantDashboard {
				if err = tx.Create(&authorityMenuPO{SysAuthorityAuthorityID: value.AuthorityID, SysBaseMenuID: dashboard.ID}).Error; err != nil {
					return err
				}
			} else {
				value.Menus = nil
			}
		}
		defaults := []struct{ path, method string }{{"/menu/getMenu", "POST"}, {"/jwt/jsonInBlacklist", "POST"}, {"/base/login", "POST"}, {"/user/changePassword", "POST"}, {"/user/setUserAuthority", "POST"}, {"/user/getUserInfo", "GET"}, {"/user/setSelfInfo", "PUT"}, {"/fileUploadAndDownload/upload", "POST"}, {"/sysDictionary/findSysDictionary", "GET"}}
		rules := make([]casbinRulePO, 0, len(defaults))
		for _, item := range defaults {
			if strict {
				permitted, policyErr := r.copyPolicyAllowed(ctx, tx, actor.AuthorityID, item.path, item.method)
				if policyErr != nil {
					return policyErr
				}
				if !permitted {
					continue
				}
			}
			rules = append(rules, newPolicyRule(value.AuthorityID, item.path, item.method))
		}
		if len(rules) > 0 {
			if err := tx.Create(&rules).Error; err != nil {
				return err
			}
		}
		return nil
	})
}
func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *system.Authority) error {
	if value.DataScope == 0 {
		value.DataScope = 1
	} else if value.DataScope < 1 || value.DataScope > 5 {
		return errInvalidDataScope
	}
	if err := r.checkDataScopeGrant(ctx, value.DataScope, nil); err != nil {
		return err
	}
	actor, allowed, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil {
		return err
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// Reject a duplicate target ID before performing the
		// hierarchy/Casbin checks that happen later in UpdateCasbin.
		var existing authorityPO
		if err := tx.Where("authority_id = ?", value.AuthorityID).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("存在相同角色id")
		}

		// UpdateCasbin checks that the newly-created role is in the actor's
		// strict-authority subtree. Before inserting it, the equivalent check is
		// that its requested parent is the actor or one of the actor's allowed
		// descendants. This preserves the compatible error and prevents a transient role
		// from being visible when the check is guaranteed to fail.
		if strict {
			if sourceID != actor.AuthorityID && !allowed[sourceID] {
				return errors.New("您提交的角色ID不合法")
			}
			value.ParentID, err = managedAuthorityParent(actor, allowed, value.AuthorityID, value.ParentID, true)
			if err != nil {
				return err
			}
		}
		po := authorityPO{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
		if err := tx.Create(&po).Error; err != nil {
			return err
		}
		value.CreatedAt, value.UpdatedAt = po.CreatedAt, po.UpdatedAt
		value.DataScope, value.DefaultRouter = po.DataScope, po.DefaultRouter
		copyLinks := func(table string, destination any, columns map[string]any) error {
			return tx.Table(table).Where(columns).Find(destination).Error
		}
		var menus []authorityMenuPO
		if err := copyLinks("sys_authority_menus", &menus, map[string]any{"sys_authority_authority_id": sourceID}); err != nil {
			return err
		}
		if strict && len(menus) > 0 {
			menuIDs := make([]uint, 0, len(menus))
			for _, menu := range menus {
				menuIDs = append(menuIDs, menu.SysBaseMenuID)
			}
			if err := checkMenuAssignment(tx.WithContext(ctx), actor.AuthorityID, allowed[actor.AuthorityID], menuIDs); err != nil {
				return err
			}
		}
		for i := range menus {
			menus[i].SysAuthorityAuthorityID = value.AuthorityID
		}
		if len(menus) > 0 {
			if err := tx.Create(&menus).Error; err != nil {
				return err
			}
		}
		if len(menus) > 0 {
			menuIDs := make([]uint, 0, len(menus))
			for _, menu := range menus {
				menuIDs = append(menuIDs, menu.SysBaseMenuID)
			}
			var copiedMenus []menuPO
			if err := tx.Where("id IN ?", menuIDs).Order("sort").Find(&copiedMenus).Error; err != nil {
				return err
			}
			value.Menus = make([]*system.Menu, 0, len(copiedMenus))
			for _, menu := range copiedMenus {
				value.Menus = append(value.Menus, menuFromPO(menu))
			}
		}
		apis, err := policyRowsForAuthority(tx, sourceID)
		if err != nil {
			return err
		}
		copiedRules := make([]casbinRulePO, 0, len(apis))
		seenPolicies := make(map[string]struct{}, len(apis))
		for _, api := range apis {
			if strict {
				allowed, err := r.copyPolicyAllowed(ctx, tx, actor.AuthorityID, api.V1, api.V2)
				if err != nil {
					return err
				}
				if !allowed {
					return errors.New("存在api不在权限列表中")
				}
			}
			key := api.V1 + "\x00" + api.V2
			if _, ok := seenPolicies[key]; ok {
				continue
			}
			seenPolicies[key] = struct{}{}
			copiedRules = append(copiedRules, newPolicyRule(value.AuthorityID, api.V1, api.V2))
		}
		if len(copiedRules) > 0 {
			if err := tx.Create(&copiedRules).Error; err != nil {
				return err
			}
		}
		var buttons []authorityButtonPO
		if err := copyLinks("sys_authority_btns", &buttons, map[string]any{"authority_id": sourceID}); err != nil {
			return err
		}
		if strict && len(buttons) > 0 {
			requested := make(map[uint][]uint)
			for _, button := range buttons {
				requested[button.MenuID] = append(requested[button.MenuID], button.ButtonID)
			}
			if err := checkButtonAssignment(tx.WithContext(ctx), actor.AuthorityID, allowed[actor.AuthorityID], requested); err != nil {
				return err
			}
		}
		for i := range buttons {
			buttons[i].AuthorityID = value.AuthorityID
		}
		if len(buttons) > 0 {
			if err := tx.Create(&buttons).Error; err != nil {
				return err
			}
		}
		return nil
	})
}

// copyPolicyAllowed mirrors the administration API-list and policy validation.
// A policy must refer to a currently registered API. Root roles
// may copy any registered API, while non-root roles may copy only APIs present
// in their own Casbin policy set.
func (r *authorityAccessRepo) copyPolicyAllowed(ctx context.Context, tx *gorm.DB, actorID uint, path, method string) (bool, error) {
	var actor authorityPO
	if err := tx.WithContext(ctx).Where("authority_id = ?", actorID).First(&actor).Error; err != nil {
		return false, err
	}
	var apiCount int64
	if err := tx.WithContext(ctx).Model(&apiPO{}).Where("path = ? AND method = ?", path, method).Count(&apiCount).Error; err != nil {
		return false, err
	}
	if apiCount == 0 {
		return false, nil
	}
	if actor.ParentID == nil || *actor.ParentID == 0 {
		return true, nil
	}
	return policyExists(tx.WithContext(ctx), actorID, path, method)
}
func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *system.Authority) error {
	if err := r.checkAuthorityIDAuth(ctx, value.AuthorityID); err != nil {
		return err
	}
	if value.DataScope < 0 || value.DataScope > 5 {
		return errInvalidDataScope
	}
	if value.DataScope != 0 {
		var departmentIDs []uint
		if value.DataScope == 5 {
			var err error
			departmentIDs, err = r.DataScopeDepartmentIDs(ctx, value.AuthorityID)
			if err != nil {
				return err
			}
		}
		if err := r.checkDataScopeGrant(ctx, value.DataScope, departmentIDs); err != nil {
			return err
		}
	}
	actor, allowed, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil {
		return err
	}
	if strict {
		value.ParentID, err = managedAuthorityParent(actor, allowed, value.AuthorityID, value.ParentID, false)
		if err != nil {
			return err
		}
	}
	if err = r.ensureAuthorityParentAcyclic(ctx, value.AuthorityID, value.ParentID); err != nil {
		return err
	}
	db := r.data.DB().WithContext(ctx)
	var current authorityPO
	if err := db.Where("authority_id = ?", value.AuthorityID).First(&current).Error; err != nil {
		return errors.New("查询角色数据失败")
	}
	updates := &authorityPO{AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
	return db.Model(&current).Updates(updates).Error
}
func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) error {
	if err := r.checkAuthorityIDAuth(ctx, id); err != nil {
		return err
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var authority authorityPO
		if err := tx.Where("authority_id = ?", id).First(&authority).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.New("该角色不存在")
			}
			return err
		}
		var users, children int64
		// Associated-user checks only block on users that still
		// exist (and are not soft-deleted). Counting raw join rows would make an
		// orphaned association impossible to clean up, which is observably
		// different from the administration contract.
		var linkedUserIDs []uint
		if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &linkedUserIDs).Error; err != nil {
			return err
		}
		if len(linkedUserIDs) > 0 {
			if err := tx.Model(&userPO{}).Where("id IN ?", linkedUserIDs).Count(&users).Error; err != nil {
				return err
			}
			if users > 0 {
				return errors.New("此角色有用户正在使用禁止删除")
			}
		}
		if err := tx.Model(&userPO{}).Where("authority_id = ?", id).Count(&users).Error; err != nil {
			return err
		}
		if users > 0 {
			return errors.New("此角色有用户正在使用禁止删除")
		}
		if err := tx.Model(&authorityPO{}).Where("parent_id = ?", id).Count(&children).Error; err != nil {
			return err
		}
		if children > 0 {
			return errors.New("此角色存在子角色不允许删除")
		}
		if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
			return err
		}
		if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil {
			return err
		}
		if err := deletePoliciesForAuthority(tx, id); err != nil {
			return err
		}
		if err := tx.Where("authority_id = ?", id).Delete(&authorityAPIPO{}).Error; err != nil {
			return err
		}
		if err := tx.Where("authority_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil {
			return err
		}
		// The compatible delete removes the role row permanently with Unscoped().Delete,
		// so reusing an authority ID after deletion behaves the same way.
		return tx.Unscoped().Delete(&authorityPO{}, "authority_id = ?", id).Error
	})
}
func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*system.Authority, error) {
	db := r.data.DB().WithContext(ctx)
	var allowed map[uint]bool
	config := r.data.Runtime().Admin()
	if actor, ok := system.ActorFromContext(ctx); ok {
		// The current authority is loaded even when strict mode is disabled;
		// an invalid token authority therefore fails the list request instead of
		// exposing the complete role tree.
		var current authorityPO
		if err := db.Where("authority_id = ?", actor.AuthorityID).First(&current).Error; err != nil {
			return nil, err
		}
		if config != nil && config.System != nil && config.System.UseStrictAuth {
			var err error
			allowed, err = r.strictAuthorityIDs(ctx, actor.AuthorityID)
			if err != nil {
				return nil, err
			}
		}
	}
	var pos []authorityPO
	if err := db.Find(&pos).Error; err != nil {
		return nil, err
	}
	out := make([]*system.Authority, 0, len(pos))
	for _, po := range pos {
		if allowed != nil && !allowed[po.AuthorityID] {
			continue
		}
		v := toBizAuthority(po)
		out = append(out, &v)
	}
	return out, nil
}
func (r *authorityAccessRepo) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
	_, allowed, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil {
		return err
	}
	if strict && !allowed[id] {
		return errors.New("您提交的角色ID不合法")
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var oldIDs []uint
		if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &oldIDs).Error; err != nil {
			return err
		}
		if strict {
			if err := checkManagedUserIDs(tx, ids, allowed, true); err != nil {
				return err
			}
			if err := checkManagedUserIDs(tx, oldIDs, allowed, false); err != nil {
				return err
			}
		}
		if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
			return err
		}
		selected := map[uint]bool{}
		for _, uid := range ids {
			selected[uid] = true
		}
		for _, uid := range oldIDs {
			if selected[uid] {
				continue
			}
			var user userPO
			if err := tx.First(&user, uid).Error; err == nil && user.AuthorityID == id {
				var another userAuthorityPO
				if err = tx.Where("sys_user_id = ?", uid).First(&another).Error; err != nil {
					// Missing users and association lookup errors are ignored
					// while choosing a replacement role.
					continue
				}
				if err = tx.Model(&user).Update("authority_id", another.SysAuthorityAuthorityID).Error; err != nil {
					return err
				}
			}
		}
		links := make([]userAuthorityPO, 0, len(ids))
		for _, uid := range ids {
			links = append(links, userAuthorityPO{SysUserID: uid, SysAuthorityAuthorityID: id})
		}
		if len(links) > 0 {
			if err := tx.Create(&links).Error; err != nil {
				return err
			}
		}
		return nil
	})
}

func checkManagedUserIDs(tx *gorm.DB, ids []uint, allowedAuthorities map[uint]bool, requireAll bool) error {
	unique := make(map[uint]struct{}, len(ids))
	for _, id := range ids {
		if id == 0 {
			if requireAll {
				return errors.New("您提交的用户ID不合法")
			}
			continue
		}
		unique[id] = struct{}{}
	}
	if len(unique) == 0 {
		return nil
	}
	userIDs := make([]uint, 0, len(unique))
	for id := range unique {
		userIDs = append(userIDs, id)
	}
	var users []userPO
	if err := tx.Select("id", "authority_id").Where("id IN ?", userIDs).Find(&users).Error; err != nil {
		return err
	}
	if requireAll && len(users) != len(unique) {
		return errors.New("您提交的用户ID不合法")
	}
	managedUsers := make(map[uint]bool, len(users))
	for _, user := range users {
		if !allowedAuthorities[user.AuthorityID] {
			return errors.New("您提交的用户ID不合法")
		}
		managedUsers[user.ID] = true
	}
	if len(managedUsers) == 0 {
		return nil
	}
	var links []userAuthorityPO
	if err := tx.Where("sys_user_id IN ?", userIDs).Find(&links).Error; err != nil {
		return err
	}
	for _, link := range links {
		if managedUsers[link.SysUserID] && !allowedAuthorities[link.SysAuthorityAuthorityID] {
			return errors.New("您提交的用户ID不合法")
		}
	}
	return nil
}

func (r *authorityAccessRepo) checkUserIDAuth(ctx context.Context, id uint, allowSelf bool) error {
	actor, allowed, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil || !strict {
		return err
	}
	if allowSelf && actor.UserID != 0 && actor.UserID == id {
		return nil
	}
	return checkManagedUserIDs(r.data.DB().WithContext(ctx), []uint{id}, allowed, true)
}

func (r *authorityAccessRepo) checkDepartmentIDsAuth(ctx context.Context, ids []uint) error {
	actor, allowedAuthorities, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil || !strict || len(ids) == 0 {
		return err
	}
	if allowedAuthorities[actor.AuthorityID] {
		return nil
	}
	var authority authorityPO
	if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
		return err
	}
	if authority.DataScope == 1 {
		return nil
	}
	scope, err := r.ResolveDataScope(ctx, actor.AuthorityID, actor.UserID)
	if err != nil {
		return err
	}
	visible := make(map[uint]bool, len(scope.DepartmentIDs))
	for _, id := range scope.DepartmentIDs {
		visible[id] = true
	}
	for _, id := range ids {
		if id == 0 || !visible[id] {
			return errors.New("您提交的部门ID不合法")
		}
	}
	return nil
}

func (r *authorityAccessRepo) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
	var ids []uint
	err := r.data.DB().WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &ids).Error
	return ids, err
}
func (r *authorityAccessRepo) SetDataScope(ctx context.Context, id uint, scope int, deptIDs []uint) error {
	if err := r.checkAuthorityIDAuth(ctx, id); err != nil {
		return err
	}
	if scope < 1 || scope > 5 {
		return errInvalidDataScope
	}
	if err := r.checkDataScopeGrant(ctx, scope, deptIDs); err != nil {
		return err
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Model(&authorityPO{}).Where("authority_id = ?", id).Update("data_scope", scope).Error; err != nil {
			return err
		}
		if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil {
			return err
		}
		if scope == 5 {
			links := make([]authorityDepartmentPO, 0, len(deptIDs))
			for _, did := range deptIDs {
				links = append(links, authorityDepartmentPO{AuthorityID: id, DepartmentID: did})
			}
			if len(links) > 0 {
				return tx.Create(&links).Error
			}
		}
		return nil
	})
}

func (r *authorityAccessRepo) checkDataScopeGrant(ctx context.Context, requestedScope int, departmentIDs []uint) error {
	actor, allowedAuthorities, strict, err := r.strictAuthorityAccess(ctx)
	if err != nil || !strict {
		return err
	}
	requestedDepartments := make(map[uint]struct{}, len(departmentIDs))
	if requestedScope == 5 {
		for _, departmentID := range departmentIDs {
			if departmentID == 0 {
				return errors.New("您提交的部门ID不合法")
			}
			requestedDepartments[departmentID] = struct{}{}
		}
		if len(requestedDepartments) > 0 {
			var count int64
			if err := r.data.DB().WithContext(ctx).Model(&departmentPO{}).Where("id IN ?", departmentIDs).Count(&count).Error; err != nil {
				return err
			}
			if count != int64(len(requestedDepartments)) {
				return errors.New("您提交的部门ID不合法")
			}
		}
	}
	if allowedAuthorities[actor.AuthorityID] {
		return nil
	}
	var actorAuthority authorityPO
	if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&actorAuthority).Error; err != nil {
		return err
	}
	grantable := false
	switch actorAuthority.DataScope {
	case 1:
		grantable = true
	case 2:
		grantable = requestedScope == 2 || requestedScope == 3 || requestedScope == 4 || requestedScope == 5
	case 3:
		grantable = requestedScope == 3 || requestedScope == 4 || requestedScope == 5
	case 4:
		grantable = requestedScope == 4
	case 5:
		grantable = requestedScope == 4 || requestedScope == 5
	default:
		return errInvalidDataScope
	}
	if !grantable {
		return errInvalidDataScope
	}
	if requestedScope != 5 || len(requestedDepartments) == 0 || actorAuthority.DataScope == 1 {
		return nil
	}
	actorScope, err := r.ResolveDataScope(ctx, actor.AuthorityID, actor.UserID)
	if err != nil {
		return err
	}
	allowedDepartments := make(map[uint]bool, len(actorScope.DepartmentIDs))
	for _, departmentID := range actorScope.DepartmentIDs {
		allowedDepartments[departmentID] = true
	}
	for departmentID := range requestedDepartments {
		if !allowedDepartments[departmentID] {
			return errors.New("您提交的部门ID不合法")
		}
	}
	return nil
}

func (r *authorityAccessRepo) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
	var ids []uint
	err := r.data.DB().WithContext(ctx).Model(&authorityDepartmentPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_department_id", &ids).Error
	return ids, err
}
func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (system.DataScope, error) {
	identity := system.DataScope{UserID: userID, AuthorityID: authorityID}
	var user userPO
	if err := r.data.DB().WithContext(ctx).Select("id", "dept_id").First(&user, userID).Error; err != nil {
		return identity, err
	}
	identity.PrimaryDeptID = user.DeptID
	var authority authorityPO
	if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").First(&authority, "authority_id = ?", authorityID).Error; err != nil {
		return identity, err
	}
	identity.Scope = authority.DataScope
	if identity.Scope < 1 || identity.Scope > 5 {
		return identity, errInvalidDataScope
	}
	identity.All = identity.Scope == 1
	if identity.Scope == 4 {
		identity.OwnerUserID = userID
	}
	var ids []uint
	selected := make(map[uint]bool)
	if identity.Scope == 2 || identity.Scope == 3 {
		if err := r.data.DB().WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_user_id = ?", userID).Pluck("sys_department_id", &ids).Error; err != nil {
			return identity, err
		}
		selected = make(map[uint]bool, len(ids)+1)
		for _, id := range ids {
			selected[id] = true
		}
		if user.DeptID != 0 {
			selected[user.DeptID] = true
		}
		ids = ids[:0]
		for id := range selected {
			ids = append(ids, id)
		}
	}
	if identity.Scope == 2 && len(ids) > 0 {
		// Match the administration subtree-union semantics: derive the visible set from
		// parent_id edges at read time, rather than trusting the denormalized
		// ancestors string (which may be stale after a department move).
		var departments []departmentPO
		if err := r.data.DB().WithContext(ctx).Select("id", "parent_id").Find(&departments).Error; err != nil {
			return identity, err
		}
		children := make(map[uint][]uint, len(departments))
		for _, department := range departments {
			children[department.ParentID] = append(children[department.ParentID], department.ID)
		}
		queue := make([]uint, 0, len(selected))
		for id := range selected {
			queue = append(queue, id)
		}
		visited := make(map[uint]struct{}, len(selected))
		for len(queue) > 0 {
			current := queue[0]
			queue = queue[1:]
			if _, ok := visited[current]; ok {
				continue
			}
			visited[current] = struct{}{}
			for _, child := range children[current] {
				if _, ok := visited[child]; !ok {
					queue = append(queue, child)
				}
			}
		}
		ids = ids[:0]
		for id := range visited {
			ids = append(ids, id)
		}
	} else if identity.Scope == 5 {
		var err error
		ids, err = r.DataScopeDepartmentIDs(ctx, authorityID)
		if err != nil {
			return identity, err
		}
	}
	identity.DepartmentIDs = ids
	return identity, nil
}
