package system

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

	"gorm.io/gorm"
)

type permissionRepo struct{ data Provider }

func NewPermissionRepo(data Provider) system.PermissionRepo { return &permissionRepo{data: data} }

type menuButtonPO struct {
	ID          uint `gorm:"primaryKey"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   gorm.DeletedAt `gorm:"index"`
	Name        string
	Description string `gorm:"column:desc"`
	MenuID      uint   `gorm:"column:sys_base_menu_id"`
}

func (menuButtonPO) TableName() string { return "sys_base_menu_btns" }

type authorityButtonPO struct {
	AuthorityID uint `gorm:"column:authority_id"`
	MenuID      uint `gorm:"column:sys_menu_id"`
	ButtonID    uint `gorm:"column:sys_base_menu_btn_id"`
}

func (authorityButtonPO) TableName() string { return "sys_authority_btns" }

func (r *permissionRepo) Buttons(ctx context.Context, menuID uint) ([]*system.MenuButton, error) {
	var pos []menuButtonPO
	if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id = ?", menuID).Find(&pos).Error; err != nil {
		return nil, err
	}
	out := make([]*system.MenuButton, 0, len(pos))
	for _, po := range pos {
		out = append(out, menuButtonFromPO(po))
	}
	return out, nil
}
func (r *permissionRepo) SetAuthorityButtons(ctx context.Context, aid uint, buttons map[uint][]uint) error {
	if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, aid); err != nil {
		return err
	}
	if err := r.checkButtonAssignmentAuth(ctx, buttons); err != nil {
		return err
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("authority_id = ?", aid).Delete(&authorityButtonPO{}).Error; err != nil {
			return err
		}
		var links []authorityButtonPO
		for menuID, ids := range buttons {
			for _, id := range ids {
				links = append(links, authorityButtonPO{AuthorityID: aid, MenuID: menuID, ButtonID: id})
			}
		}
		if len(links) > 0 {
			return tx.Create(&links).Error
		}
		return nil
	})
}
func (r *permissionRepo) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
	var rows []authorityButtonPO
	if err := r.data.DB().WithContext(ctx).Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Find(&rows).Error; err != nil {
		return nil, err
	}
	var ids []uint
	for _, row := range rows {
		ids = append(ids, row.ButtonID)
	}
	return ids, nil
}
func (r *permissionRepo) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
	if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, aid); err != nil {
		return err
	}
	if err := r.checkButtonAssignmentAuth(ctx, map[uint][]uint{menuID: ids}); err != nil {
		return err
	}
	return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Delete(&authorityButtonPO{}).Error; err != nil {
			return err
		}
		links := make([]authorityButtonPO, 0, len(ids))
		for _, id := range ids {
			links = append(links, authorityButtonPO{AuthorityID: aid, MenuID: menuID, ButtonID: id})
		}
		if len(links) > 0 {
			return tx.Create(&links).Error
		}
		return nil
	})
}
func (r *permissionRepo) CanRemoveButton(ctx context.Context, id string) (bool, error) {
	var row authorityButtonPO
	err := r.data.DB().WithContext(ctx).Where("sys_base_menu_btn_id = ?", id).First(&row).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return true, nil
	}
	if err != nil {
		return false, errors.New("此按钮正在被使用无法删除")
	}
	return false, nil
}
func (r *permissionRepo) AuthorityButtonIDs(ctx context.Context, aid uint) ([]uint, error) {
	var ids []uint
	err := r.data.DB().WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ?", aid).Pluck("sys_base_menu_btn_id", &ids).Error
	return ids, err
}

func (r *permissionRepo) checkButtonAssignmentAuth(ctx context.Context, buttons map[uint][]uint) error {
	actor, allowedAuthorities, strict, err := (&authorityAccessRepo{data: r.data}).strictAuthorityAccess(ctx)
	if err != nil || !strict {
		return err
	}
	return checkButtonAssignment(r.data.DB().WithContext(ctx), actor.AuthorityID, allowedAuthorities[actor.AuthorityID], buttons)
}

func checkButtonAssignment(db *gorm.DB, actorID uint, root bool, buttons map[uint][]uint) error {
	requested := make(map[[2]uint]struct{})
	buttonIDs := make([]uint, 0)
	for menuID, ids := range buttons {
		for _, buttonID := range ids {
			key := [2]uint{menuID, buttonID}
			if _, exists := requested[key]; exists {
				continue
			}
			requested[key] = struct{}{}
			buttonIDs = append(buttonIDs, buttonID)
		}
	}
	if len(requested) == 0 {
		return nil
	}
	allowed := make(map[[2]uint]bool, len(requested))
	if root {
		var rows []menuButtonPO
		if err := db.Where("id IN ?", buttonIDs).Find(&rows).Error; err != nil {
			return err
		}
		for _, row := range rows {
			allowed[[2]uint{row.MenuID, row.ID}] = true
		}
	} else {
		var rows []authorityButtonPO
		if err := db.Where("authority_id = ?", actorID).Find(&rows).Error; err != nil {
			return err
		}
		for _, row := range rows {
			allowed[[2]uint{row.MenuID, row.ButtonID}] = true
		}
	}
	for key := range requested {
		if !allowed[key] {
			return errors.New("添加失败,请勿跨级操作")
		}
	}
	return nil
}
