package repository

import (
	"errors"

	"hyapp-admin-server/internal/model"

	"gorm.io/gorm"
)

func (s *Store) ListPermissions() ([]model.Permission, error) {
	var permissions []model.Permission
	err := s.db.Order("kind ASC, code ASC").Find(&permissions).Error
	return permissions, err
}

func (s *Store) CreatePermission(permission *model.Permission) error {
	normalized, err := normalizePermission(*permission)
	if err != nil {
		return err
	}
	*permission = normalized
	return s.db.Create(permission).Error
}

func (s *Store) UpdatePermission(id uint, patch model.Permission) (*model.Permission, error) {
	normalized, err := normalizePermission(patch)
	if err != nil {
		return nil, err
	}

	var permission model.Permission
	if err := s.db.First(&permission, id).Error; err != nil {
		return nil, err
	}

	// 权限码会写入 JWT 和前端按钮判断，更新时必须整体校验后一次性落库。
	updates := map[string]any{
		"name":        normalized.Name,
		"code":        normalized.Code,
		"kind":        normalized.Kind,
		"description": normalized.Description,
	}
	if err := s.db.Model(&permission).Updates(updates).Error; err != nil {
		return nil, err
	}
	if err := s.db.First(&permission, id).Error; err != nil {
		return nil, err
	}
	return &permission, nil
}

func (s *Store) DeletePermission(id uint) error {
	var roleCount int64
	if err := s.db.Table("admin_role_permissions").Where("permission_id = ?", id).Count(&roleCount).Error; err != nil {
		return err
	}
	if roleCount > 0 {
		return errors.New("permission is bound to roles")
	}

	// Menus reference permission_code by string, so deletion must check the code before removing the definition.
	var menuCount int64
	if err := s.db.Model(&model.Menu{}).Where("permission_code = (SELECT code FROM admin_permissions WHERE id = ?)", id).Count(&menuCount).Error; err != nil {
		return err
	}
	if menuCount > 0 {
		return errors.New("permission is bound to menus")
	}

	return s.db.Delete(&model.Permission{}, id).Error
}

func (s *Store) SyncDefaultPermissions() error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		if err := s.syncDefaultPermissions(tx); err != nil {
			return err
		}
		return s.syncManagedDefaultRolePermissions(tx)
	})
}

func (s *Store) syncManagedDefaultRolePermissions(tx *gorm.DB) error {
	var allPermissions []model.Permission
	if err := tx.Find(&allPermissions).Error; err != nil {
		return err
	}

	for _, definition := range managedDefaultRoles {
		var role model.Role
		result := tx.Where("code = ?", definition.Code).Limit(1).Find(&role)
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			role = definition
			if err := tx.Create(&role).Error; err != nil {
				return err
			}
		} else if err := tx.Model(&role).Updates(map[string]any{
			"name":        definition.Name,
			"description": definition.Description,
		}).Error; err != nil {
			return err
		}

		permissions := allPermissions
		if definition.Code != roleCodePlatformAdmin {
			permissions = nil
			codes := defaultRolePermissionCodes(definition.Code)
			if len(codes) > 0 {
				if err := tx.Where("code IN ?", codes).Find(&permissions).Error; err != nil {
					return err
				}
			}
		}
		// “同步权限”是恢复固定岗位模板的显式管理动作，必须删除历史跨模块授权，不能只追加缺失项。
		if err := tx.Model(&role).Association("Permissions").Replace(permissions); err != nil {
			return err
		}
	}
	return nil
}

func (s *Store) ListRoles() ([]model.Role, error) {
	var roles []model.Role
	err := s.db.Preload("Permissions").Order("id ASC").Find(&roles).Error
	return roles, err
}

func (s *Store) CreateRole(role *model.Role, permissionIDs []uint) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(role).Error; err != nil {
			return err
		}
		if len(permissionIDs) == 0 {
			return nil
		}
		var permissions []model.Permission
		if err := tx.Where("id IN ?", permissionIDs).Find(&permissions).Error; err != nil {
			return err
		}
		return tx.Model(role).Association("Permissions").Replace(permissions)
	})
}

func (s *Store) UpdateRole(roleID uint, patch model.Role, permissionIDs *[]uint) (*model.Role, error) {
	var role model.Role
	if err := s.db.First(&role, roleID).Error; err != nil {
		return nil, err
	}

	err := s.db.Transaction(func(tx *gorm.DB) error {
		updates := map[string]any{
			"name":        patch.Name,
			"code":        patch.Code,
			"description": patch.Description,
		}
		if err := tx.Model(&role).Updates(updates).Error; err != nil {
			return err
		}
		if permissionIDs != nil {
			var permissions []model.Permission
			if len(*permissionIDs) > 0 {
				if err := tx.Where("id IN ?", *permissionIDs).Find(&permissions).Error; err != nil {
					return err
				}
			}
			if err := tx.Model(&role).Association("Permissions").Replace(permissions); err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	if err := s.db.Preload("Permissions").First(&role, roleID).Error; err != nil {
		return nil, err
	}
	return &role, nil
}

func (s *Store) DeleteRole(roleID uint) error {
	var users int64
	if err := s.db.Table("admin_user_roles").Where("role_id = ?", roleID).Count(&users).Error; err != nil {
		return err
	}
	if users > 0 {
		return errors.New("role is bound to users")
	}
	return s.db.Delete(&model.Role{}, roleID).Error
}

func (s *Store) ReplaceRolePermissions(roleID uint, permissionIDs []uint) error {
	var role model.Role
	if err := s.db.First(&role, roleID).Error; err != nil {
		return err
	}
	var permissions []model.Permission
	if len(permissionIDs) > 0 {
		if err := s.db.Where("id IN ?", permissionIDs).Find(&permissions).Error; err != nil {
			return err
		}
	}
	return s.db.Model(&role).Association("Permissions").Replace(permissions)
}
