package system

import (
	"context"
	"encoding/json"
	"errors"

	"kra/internal/biz/system"
	"kra/internal/data/model"
	"kra/internal/data/query"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type userRepo struct {
	db *gorm.DB
}

// NewUserRepo 创建用户仓储
func NewUserRepo(db *gorm.DB) system.UserRepo {
	query.SetDefault(db)
	return &userRepo{db: db}
}

func (r *userRepo) Create(ctx context.Context, user *system.User) error {
	m := toBizUser(user)
	return query.SysUser.WithContext(ctx).Create(m)
}

func (r *userRepo) Update(ctx context.Context, user *system.User) error {
	_, err := query.SysUser.WithContext(ctx).Where(query.SysUser.ID.Eq(int64(user.ID))).Updates(map[string]any{
		"nick_name":  user.NickName,
		"header_img": user.HeaderImg,
		"phone":      user.Phone,
		"email":      user.Email,
		"enable":     user.Enable,
	})
	return err
}

func (r *userRepo) UpdateSelf(ctx context.Context, user *system.User) error {
	updates := map[string]any{}
	if user.NickName != "" {
		updates["nick_name"] = user.NickName
	}
	if user.HeaderImg != "" {
		updates["header_img"] = user.HeaderImg
	}
	if user.Phone != "" {
		updates["phone"] = user.Phone
	}
	if user.Email != "" {
		updates["email"] = user.Email
	}
	if user.SideMode != "" {
		updates["side_mode"] = user.SideMode
	}
	if user.BaseColor != "" {
		updates["base_color"] = user.BaseColor
	}
	if len(updates) == 0 {
		return nil
	}
	_, err := query.SysUser.WithContext(ctx).Where(query.SysUser.ID.Eq(int64(user.ID))).Updates(updates)
	return err
}

func (r *userRepo) UpdateSelfSetting(ctx context.Context, id uint, setting json.RawMessage) error {
	_, err := query.SysUser.WithContext(ctx).Where(query.SysUser.ID.Eq(int64(id))).Update(query.SysUser.OriginSetting, string(setting))
	return err
}

func (r *userRepo) Delete(ctx context.Context, id uint) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("id = ?", id).Delete(&model.SysUser{}).Error; err != nil {
			return err
		}
		if err := tx.Where("sys_user_id = ?", id).Delete(&model.SysUserAuthority{}).Error; err != nil {
			return err
		}
		return nil
	})
}

func (r *userRepo) FindByID(ctx context.Context, id uint) (*system.User, error) {
	m, err := query.SysUser.WithContext(ctx).Where(query.SysUser.ID.Eq(int64(id))).First()
	if err != nil {
		return nil, err
	}
	return toModelUser(m), nil
}

func (r *userRepo) FindByIDWithAuthorities(ctx context.Context, id uint) (*system.User, error) {
	m, err := query.SysUser.WithContext(ctx).
		Preload(query.SysUser.Authority).
		Preload(query.SysUser.Authorities).
		Where(query.SysUser.ID.Eq(int64(id))).
		First()
	if err != nil {
		return nil, err
	}
	return toModelUserWithAuthorities(m), nil
}

func (r *userRepo) FindByUUID(ctx context.Context, uuidStr string) (*system.User, error) {
	m, err := query.SysUser.WithContext(ctx).Where(query.SysUser.UUID.Eq(uuidStr)).First()
	if err != nil {
		return nil, err
	}
	return toModelUser(m), nil
}

func (r *userRepo) FindByUUIDWithAuthorities(ctx context.Context, uuidStr string) (*system.User, error) {
	m, err := query.SysUser.WithContext(ctx).
		Preload(query.SysUser.Authority).
		Preload(query.SysUser.Authorities).
		Where(query.SysUser.UUID.Eq(uuidStr)).
		First()
	if err != nil {
		return nil, err
	}
	return toModelUserWithAuthorities(m), nil
}

func (r *userRepo) FindByUsername(ctx context.Context, username string) (*system.User, error) {
	m, err := query.SysUser.WithContext(ctx).Where(query.SysUser.Username.Eq(username)).First()
	if err != nil {
		return nil, err
	}
	return toModelUser(m), nil
}

func (r *userRepo) FindByUsernameWithAuthorities(ctx context.Context, username string) (*system.User, error) {
	m, err := query.SysUser.WithContext(ctx).
		Preload(query.SysUser.Authority).
		Preload(query.SysUser.Authorities).
		Where(query.SysUser.Username.Eq(username)).
		First()
	if err != nil {
		return nil, err
	}
	return toModelUserWithAuthorities(m), nil
}

func (r *userRepo) List(ctx context.Context, page, pageSize int, filters map[string]string) ([]*system.User, int64, error) {
	u := query.SysUser
	q := u.WithContext(ctx)

	if v, ok := filters["username"]; ok && v != "" {
		q = q.Where(u.Username.Like("%" + v + "%"))
	}
	if v, ok := filters["nick_name"]; ok && v != "" {
		q = q.Where(u.NickName.Like("%" + v + "%"))
	}
	if v, ok := filters["phone"]; ok && v != "" {
		q = q.Where(u.Phone.Like("%" + v + "%"))
	}
	if v, ok := filters["email"]; ok && v != "" {
		q = q.Where(u.Email.Like("%" + v + "%"))
	}

	total, err := q.Count()
	if err != nil {
		return nil, 0, err
	}

	offset := (page - 1) * pageSize
	list, err := q.Preload(u.Authority).Preload(u.Authorities).Offset(offset).Limit(pageSize).Find()
	if err != nil {
		return nil, 0, err
	}

	users := make([]*system.User, len(list))
	for i := range list {
		users[i] = toModelUserWithAuthorities(list[i])
	}
	return users, total, nil
}

func (r *userRepo) UpdatePassword(ctx context.Context, id uint, password string) error {
	return r.db.WithContext(ctx).Model(&model.SysUser{}).Where("id = ?", id).Update("password", password).Error
}

func (r *userRepo) UpdateAuthorityID(ctx context.Context, id uint, authorityId uint) error {
	return r.db.WithContext(ctx).Model(&model.SysUser{}).Where("id = ?", id).Update("authority_id", authorityId).Error
}

func (r *userRepo) SetUserAuthorities(ctx context.Context, adminAuthorityID, userId uint, authorityIds []uint) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var user model.SysUser
		if err := tx.Where("id = ?", userId).First(&user).Error; err != nil {
			return errors.New("查询用户数据失败")
		}

		if err := tx.Where("sys_user_id = ?", userId).Delete(&model.SysUserAuthority{}).Error; err != nil {
			return err
		}

		for _, aid := range authorityIds {
			if err := checkAuthorityIDAuth(tx, adminAuthorityID, aid); err != nil {
				return err
			}
			ua := &model.SysUserAuthority{
				SysUserID:               int64(userId),
				SysAuthorityAuthorityID: int64(aid),
			}
			if err := tx.Create(ua).Error; err != nil {
				return err
			}
		}

		if len(authorityIds) > 0 {
			if err := tx.Model(&model.SysUser{}).Where("id = ?", userId).Update("authority_id", authorityIds[0]).Error; err != nil {
				return err
			}
		}
		return nil
	})
}

func checkAuthorityIDAuth(tx *gorm.DB, adminAuthorityID, targetAuthorityID uint) error {
	// 超级管理员(888)直接通过
	if adminAuthorityID == 888 {
		return nil
	}

	// 获取管理员角色的所有子角色ID（递归）
	authIDs, err := getStructAuthorityList(tx, adminAuthorityID)
	if err != nil {
		return err
	}

	// 检查目标角色是否在权限列表中
	for _, id := range authIDs {
		if id == targetAuthorityID {
			return nil
		}
	}
	return errors.New("您没有权限分配该角色")
}

// getStructAuthorityList 递归获取角色ID列表（包含子角色）
func getStructAuthorityList(tx *gorm.DB, authorityID uint) ([]uint, error) {
	var auth model.SysAuthority
	if err := tx.First(&auth, "authority_id = ?", authorityID).Error; err != nil {
		return nil, err
	}

	var list []uint
	var children []model.SysAuthority
	if err := tx.Where("parent_id = ?", authorityID).Find(&children).Error; err != nil {
		return nil, err
	}

	for _, child := range children {
		list = append(list, uint(child.AuthorityID))
		childList, err := getStructAuthorityList(tx, uint(child.AuthorityID))
		if err == nil {
			list = append(list, childList...)
		}
	}

	// 如果是顶级角色，包含自己
	if auth.ParentID == 0 {
		list = append(list, authorityID)
	}

	return list, nil
}

func (r *userRepo) CheckUserHasAuthority(ctx context.Context, userId, authorityId uint) (bool, error) {
	var count int64
	err := r.db.WithContext(ctx).Model(&model.SysUserAuthority{}).
		Where("sys_user_id = ? AND sys_authority_authority_id = ?", userId, authorityId).
		Count(&count).Error
	return count > 0, err
}

func (r *userRepo) GetAuthorityMenuRouters(ctx context.Context, authorityId uint) ([]string, error) {
	var authorityMenus []model.SysAuthorityMenu
	if err := r.db.WithContext(ctx).Where("sys_authority_authority_id = ?", authorityId).Find(&authorityMenus).Error; err != nil {
		return nil, err
	}

	if len(authorityMenus) == 0 {
		return []string{}, nil
	}

	menuIDs := make([]int64, len(authorityMenus))
	for i, am := range authorityMenus {
		menuIDs[i] = am.SysBaseMenuID
	}

	var menus []model.SysBaseMenu
	if err := r.db.WithContext(ctx).Where("id IN ?", menuIDs).Find(&menus).Error; err != nil {
		return nil, err
	}

	routers := make([]string, len(menus))
	for i, m := range menus {
		routers[i] = m.Name
	}
	return routers, nil
}

func (r *userRepo) GetAuthorityDefaultRouter(ctx context.Context, authorityId uint) (string, error) {
	var authority model.SysAuthority
	if err := r.db.WithContext(ctx).Where("authority_id = ?", authorityId).First(&authority).Error; err != nil {
		return "", err
	}
	return authority.DefaultRouter, nil
}

// 转换函数
func toBizUser(u *system.User) *model.SysUser {
	return &model.SysUser{
		ID:          int64(u.ID),
		UUID:        u.UUID.String(),
		Username:    u.Username,
		Password:    u.Password,
		NickName:    u.NickName,
		HeaderImg:   u.HeaderImg,
		AuthorityID: int64(u.AuthorityId),
		Phone:       u.Phone,
		Email:       u.Email,
		Enable:      int64(u.Enable),
	}
}

func toModelUser(m *model.SysUser) *system.User {
	u := &system.User{
		ID:            uint(m.ID),
		Username:      m.Username,
		Password:      m.Password,
		NickName:      m.NickName,
		HeaderImg:     m.HeaderImg,
		AuthorityId:   uint(m.AuthorityID),
		Phone:         m.Phone,
		Email:         m.Email,
		Enable:        int(m.Enable),
		OriginSetting: json.RawMessage(m.OriginSetting),
		CreatedAt:     m.CreatedAt,
		UpdatedAt:     m.UpdatedAt,
	}
	u.UUID, _ = uuid.Parse(m.UUID)
	return u
}

func toModelUserWithAuthorities(m *model.SysUser) *system.User {
	u := toModelUser(m)

	// 转换Authority
	u.Authority = &system.Authority{
		AuthorityId:   uint(m.Authority.AuthorityID),
		AuthorityName: m.Authority.AuthorityName,
		DefaultRouter: m.Authority.DefaultRouter,
	}
	if m.Authority.ParentID != 0 {
		pid := uint(m.Authority.ParentID)
		u.Authority.ParentId = &pid
	}

	// 转换Authorities
	if len(m.Authorities) > 0 {
		u.Authorities = make([]*system.Authority, len(m.Authorities))
		for i, a := range m.Authorities {
			u.Authorities[i] = &system.Authority{
				AuthorityId:   uint(a.AuthorityID),
				AuthorityName: a.AuthorityName,
				DefaultRouter: a.DefaultRouter,
			}
			if a.ParentID != 0 {
				pid := uint(a.ParentID)
				u.Authorities[i].ParentId = &pid
			}
		}
	}

	return u
}
