package services

import (
	"context"
	"errors"
	"strconv"
	"strings"
	"time"

	"quyun/v2/app/errorx"
	super_dto "quyun/v2/app/http/super/v1/dto"
	v1_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/app/requests"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"
	jwt_provider "quyun/v2/providers/jwt"

	"github.com/google/uuid"
	"github.com/spf13/cast"
	"go.ipao.vip/gen/field"
	"go.ipao.vip/gen/types"
	"gorm.io/gorm"
)

// @provider
type super struct {
	jwt *jwt_provider.JWT
}

func (s *super) Login(ctx context.Context, form *super_dto.LoginForm) (*super_dto.LoginResponse, error) {
	if form == nil {
		return nil, errorx.ErrInvalidParameter.WithMsg("登录参数不能为空")
	}
	username := strings.TrimSpace(form.Username)
	password := strings.TrimSpace(form.Password)
	if username == "" || password == "" {
		return nil, errorx.ErrInvalidParameter.WithMsg("账号或密码不能为空")
	}

	// 校验账号与权限。
	tbl, q := models.UserQuery.QueryContext(ctx)
	u, err := q.Where(tbl.Username.Eq(username)).First()
	if err != nil {
		return nil, errorx.ErrInvalidCredentials.WithCause(err).WithMsg("账号或密码错误")
	}
	if u.Password != password {
		return nil, errorx.ErrInvalidCredentials.WithMsg("账号或密码错误")
	}
	if u.Status == consts.UserStatusBanned {
		return nil, errorx.ErrAccountDisabled
	}
	if !hasRole(u.Roles, consts.RoleSuperAdmin) {
		return nil, errorx.ErrForbidden.WithMsg("无权限访问")
	}

	// 生成登录令牌。
	token, err := s.jwt.CreateToken(s.jwt.CreateClaims(jwt_provider.BaseClaims{
		UserID: u.ID,
	}))
	if err != nil {
		return nil, errorx.ErrInternalError.WithCause(err).WithMsg("生成令牌失败")
	}

	return &super_dto.LoginResponse{
		Token: token,
		User:  s.toSuperUserDTO(u),
	}, nil
}

func (s *super) CheckToken(ctx context.Context, token string) (*super_dto.LoginResponse, error) {
	if token == "" {
		return nil, errorx.ErrUnauthorized.WithMsg("Missing token")
	}

	claims, err := s.jwt.Parse(token)
	if err != nil {
		return nil, errorx.ErrUnauthorized.WithCause(err)
	}

	tbl, q := models.UserQuery.QueryContext(ctx)
	u, err := q.Where(tbl.ID.Eq(claims.UserID)).First()
	if err != nil {
		return nil, errorx.ErrUnauthorized.WithCause(err).WithMsg("UserNotFound")
	}
	if u.Status == consts.UserStatusBanned {
		return nil, errorx.ErrAccountDisabled
	}
	if !hasRole(u.Roles, consts.RoleSuperAdmin) {
		return nil, errorx.ErrForbidden.WithMsg("无权限访问")
	}

	newToken, err := s.jwt.CreateTokenByOldToken(token, s.jwt.CreateClaims(jwt_provider.BaseClaims{
		UserID: u.ID,
	}))
	if err != nil {
		return nil, errorx.ErrInternalError.WithCause(err).WithMsg("生成令牌失败")
	}

	return &super_dto.LoginResponse{
		Token: newToken,
		User:  s.toSuperUserDTO(u),
	}, nil
}

func (s *super) ListUsers(ctx context.Context, filter *super_dto.UserListFilter) (*requests.Pager, error) {
	tbl, q := models.UserQuery.QueryContext(ctx)
	if filter.Username != nil && strings.TrimSpace(*filter.Username) != "" {
		keyword := "%" + strings.TrimSpace(*filter.Username) + "%"
		q = q.Where(field.Or(tbl.Username.Like(keyword), tbl.Nickname.Like(keyword)))
	}
	if filter.ID != nil && *filter.ID > 0 {
		q = q.Where(tbl.ID.Eq(*filter.ID))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.Role != nil && *filter.Role != "" {
		q = q.Where(tbl.Roles.Contains(types.Array[consts.Role]{*filter.Role}))
	}
	if filter.TenantID != nil && *filter.TenantID > 0 {
		// 按租户成员过滤用户，需要先定位租户成员关系。
		tblTu, qTu := models.TenantUserQuery.QueryContext(ctx)
		userIDs, err := qTu.Where(tblTu.TenantID.Eq(*filter.TenantID)).Select(tblTu.UserID).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		ids := make([]int64, 0, len(userIDs))
		for _, row := range userIDs {
			ids = append(ids, row.UserID)
		}
		if len(ids) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.ID.In(ids...))
		}
	}

	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}
	if filter.VerifiedAtFrom != nil {
		from, err := s.parseFilterTime(filter.VerifiedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.VerifiedAt.Gte(*from))
		}
	}
	if filter.VerifiedAtTo != nil {
		to, err := s.parseFilterTime(filter.VerifiedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.VerifiedAt.Lte(*to))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "id":
			q = q.Order(tbl.ID.Desc())
		case "username":
			q = q.Order(tbl.Username.Desc())
		case "status":
			q = q.Order(tbl.Status.Desc())
		case "verified_at":
			q = q.Order(tbl.VerifiedAt.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		case "updated_at":
			q = q.Order(tbl.UpdatedAt.Desc())
		case "balance":
			q = q.Order(tbl.Balance.Desc())
		case "balance_frozen":
			q = q.Order(tbl.BalanceFrozen.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "id":
			q = q.Order(tbl.ID)
		case "username":
			q = q.Order(tbl.Username)
		case "status":
			q = q.Order(tbl.Status)
		case "verified_at":
			q = q.Order(tbl.VerifiedAt)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		case "updated_at":
			q = q.Order(tbl.UpdatedAt)
		case "balance":
			q = q.Order(tbl.Balance)
		case "balance_frozen":
			q = q.Order(tbl.BalanceFrozen)
		}
		orderApplied = true
	}
	if !orderApplied {
		q = q.Order(tbl.ID.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.ID.Desc()).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	userIDs := make([]int64, 0, len(list))
	for _, u := range list {
		userIDs = append(userIDs, u.ID)
	}

	ownedCountMap, err := s.userOwnedTenantCount(ctx, userIDs)
	if err != nil {
		return nil, err
	}
	joinedCountMap, err := s.userJoinedTenantCount(ctx, userIDs)
	if err != nil {
		return nil, err
	}

	var data []super_dto.UserItem
	for _, u := range list {
		data = append(data, super_dto.UserItem{
			SuperUserLite: super_dto.SuperUserLite{
				ID:                u.ID,
				Username:          u.Username,
				Roles:             u.Roles,
				Status:            u.Status,
				StatusDescription: u.Status.Description(),
				VerifiedAt:        s.formatTime(u.VerifiedAt),
				CreatedAt:         s.formatTime(u.CreatedAt),
				UpdatedAt:         s.formatTime(u.UpdatedAt),
			},
			Balance:           u.Balance,
			BalanceFrozen:     u.BalanceFrozen,
			OwnedTenantCount:  ownedCountMap[u.ID],
			JoinedTenantCount: joinedCountMap[u.ID],
		})
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      data,
	}, nil
}

func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, error) {
	tbl, q := models.UserQuery.QueryContext(ctx)
	u, err := q.Where(tbl.ID.Eq(id)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	return &super_dto.UserItem{
		SuperUserLite: super_dto.SuperUserLite{
			ID:                u.ID,
			Username:          u.Username,
			Roles:             u.Roles,
			Status:            u.Status,
			StatusDescription: u.Status.Description(),
			CreatedAt:         u.CreatedAt.Format(time.RFC3339),
			UpdatedAt:         u.UpdatedAt.Format(time.RFC3339),
		},
		Balance:       u.Balance,
		BalanceFrozen: u.BalanceFrozen,
	}, nil
}

func (s *super) UpdateUserStatus(ctx context.Context, id int64, form *super_dto.UserStatusUpdateForm) error {
	tbl, q := models.UserQuery.QueryContext(ctx)
	_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Status, consts.UserStatus(form.Status))
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *super) UpdateUserRoles(ctx context.Context, id int64, form *super_dto.UserRolesUpdateForm) error {
	var roles types.Array[consts.Role]
	for _, r := range form.Roles {
		roles = append(roles, r)
	}
	tbl, q := models.UserQuery.QueryContext(ctx)
	_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Roles, roles)
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *super) ListTenants(ctx context.Context, filter *super_dto.TenantListFilter) (*requests.Pager, error) {
	if filter == nil {
		filter = &super_dto.TenantListFilter{}
	}
	tbl, q := models.TenantQuery.QueryContext(ctx)
	if filter.ID != nil && *filter.ID > 0 {
		q = q.Where(tbl.ID.Eq(*filter.ID))
	}
	if filter.UserID != nil && *filter.UserID > 0 {
		q = q.Where(tbl.UserID.Eq(*filter.UserID))
	}
	if filter.Name != nil && strings.TrimSpace(*filter.Name) != "" {
		q = q.Where(tbl.Name.Like("%" + strings.TrimSpace(*filter.Name) + "%"))
	}
	if filter.Code != nil && strings.TrimSpace(*filter.Code) != "" {
		q = q.Where(tbl.Code.Like("%" + strings.TrimSpace(*filter.Code) + "%"))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.ExpiredAtFrom != nil {
		from, err := s.parseFilterTime(filter.ExpiredAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.ExpiredAt.Gte(*from))
		}
	}
	if filter.ExpiredAtTo != nil {
		to, err := s.parseFilterTime(filter.ExpiredAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.ExpiredAt.Lte(*to))
		}
	}
	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "id":
			q = q.Order(tbl.ID.Desc())
		case "name":
			q = q.Order(tbl.Name.Desc())
		case "code":
			q = q.Order(tbl.Code.Desc())
		case "status":
			q = q.Order(tbl.Status.Desc())
		case "expired_at":
			q = q.Order(tbl.ExpiredAt.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		case "updated_at":
			q = q.Order(tbl.UpdatedAt.Desc())
		case "user_id":
			q = q.Order(tbl.UserID.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "id":
			q = q.Order(tbl.ID)
		case "name":
			q = q.Order(tbl.Name)
		case "code":
			q = q.Order(tbl.Code)
		case "status":
			q = q.Order(tbl.Status)
		case "expired_at":
			q = q.Order(tbl.ExpiredAt)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		case "updated_at":
			q = q.Order(tbl.UpdatedAt)
		case "user_id":
			q = q.Order(tbl.UserID)
		}
		orderApplied = true
	}
	if !orderApplied {
		q = q.Order(tbl.ID.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	data, err := s.buildTenantItems(ctx, list)
	if err != nil {
		return nil, err
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      data,
	}, nil
}

func (s *super) TenantHealth(ctx context.Context, filter *super_dto.TenantListFilter) (*requests.Pager, error) {
	if filter == nil {
		filter = &super_dto.TenantListFilter{}
	}
	tbl, q := models.TenantQuery.QueryContext(ctx)
	if filter.ID != nil && *filter.ID > 0 {
		q = q.Where(tbl.ID.Eq(*filter.ID))
	}
	if filter.UserID != nil && *filter.UserID > 0 {
		q = q.Where(tbl.UserID.Eq(*filter.UserID))
	}
	if filter.Name != nil && strings.TrimSpace(*filter.Name) != "" {
		q = q.Where(tbl.Name.Like("%" + strings.TrimSpace(*filter.Name) + "%"))
	}
	if filter.Code != nil && strings.TrimSpace(*filter.Code) != "" {
		q = q.Where(tbl.Code.Like("%" + strings.TrimSpace(*filter.Code) + "%"))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.ExpiredAtFrom != nil {
		from, err := s.parseFilterTime(filter.ExpiredAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.ExpiredAt.Gte(*from))
		}
	}
	if filter.ExpiredAtTo != nil {
		to, err := s.parseFilterTime(filter.ExpiredAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.ExpiredAt.Lte(*to))
		}
	}
	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "id":
			q = q.Order(tbl.ID.Desc())
		case "name":
			q = q.Order(tbl.Name.Desc())
		case "code":
			q = q.Order(tbl.Code.Desc())
		case "status":
			q = q.Order(tbl.Status.Desc())
		case "expired_at":
			q = q.Order(tbl.ExpiredAt.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		case "updated_at":
			q = q.Order(tbl.UpdatedAt.Desc())
		case "user_id":
			q = q.Order(tbl.UserID.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "id":
			q = q.Order(tbl.ID)
		case "name":
			q = q.Order(tbl.Name)
		case "code":
			q = q.Order(tbl.Code)
		case "status":
			q = q.Order(tbl.Status)
		case "expired_at":
			q = q.Order(tbl.ExpiredAt)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		case "updated_at":
			q = q.Order(tbl.UpdatedAt)
		case "user_id":
			q = q.Order(tbl.UserID)
		}
		orderApplied = true
	}
	if !orderApplied {
		q = q.Order(tbl.ID.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	items, err := s.buildTenantHealthItems(ctx, list)
	if err != nil {
		return nil, err
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
	uid := form.AdminUserID
	if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First(); err != nil {
		return errorx.ErrRecordNotFound.WithMsg("用户不存在")
	}

	t := &models.Tenant{
		UserID: uid,
		Name:   form.Name,
		Code:   form.Code,
		UUID:   types.UUID(uuid.New()),
		Status: consts.TenantStatusVerified,
	}
	if err := models.TenantQuery.WithContext(ctx).Create(t); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *super) GetTenant(ctx context.Context, id int64) (*super_dto.TenantItem, error) {
	tbl, q := models.TenantQuery.QueryContext(ctx)
	t, err := q.Where(tbl.ID.Eq(id)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	items, err := s.buildTenantItems(ctx, []*models.Tenant{t})
	if err != nil {
		return nil, err
	}
	if len(items) == 0 {
		return nil, errorx.ErrRecordNotFound
	}
	return &items[0], nil
}

func (s *super) UpdateTenantStatus(ctx context.Context, id int64, form *super_dto.TenantStatusUpdateForm) error {
	tbl, q := models.TenantQuery.QueryContext(ctx)
	_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Status, consts.TenantStatus(form.Status))
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *super) UpdateTenantExpire(ctx context.Context, id int64, form *super_dto.TenantExpireUpdateForm) error {
	expire := time.Now().AddDate(0, 0, form.Duration)
	tbl, q := models.TenantQuery.QueryContext(ctx)
	_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.ExpiredAt, expire)
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *super) ListTenantUsers(ctx context.Context, tenantID int64, filter *super_dto.SuperTenantUserListFilter) (*requests.Pager, error) {
	tbl, q := models.TenantUserQuery.QueryContext(ctx)
	q = q.Where(tbl.TenantID.Eq(tenantID))
	if filter.UserID != nil && *filter.UserID > 0 {
		q = q.Where(tbl.UserID.Eq(*filter.UserID))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.Role != nil && *filter.Role != "" {
		q = q.Where(tbl.Role.Contains(types.Array[consts.TenantUserRole]{*filter.Role}))
	}

	userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
	if err != nil {
		return nil, err
	}
	if userFilter {
		if len(userIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.UserID.In(userIDs...))
		}
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.CreatedAt.Desc()).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	userMap, err := s.userMapByTenantUsers(ctx, list)
	if err != nil {
		return nil, err
	}

	items := make([]super_dto.SuperTenantUserItem, 0, len(list))
	for _, tu := range list {
		items = append(items, super_dto.SuperTenantUserItem{
			User:       s.toSuperUserLite(userMap[tu.UserID]),
			TenantUser: s.toSuperTenantUserDTO(tu),
		})
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

func (s *super) ListUserTenants(ctx context.Context, userID int64, filter *super_dto.SuperUserTenantListFilter) (*requests.Pager, error) {
	tbl, q := models.TenantUserQuery.QueryContext(ctx)
	q = q.Where(tbl.UserID.Eq(userID))

	if filter.TenantID != nil && *filter.TenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.Role != nil && *filter.Role != "" {
		q = q.Where(tbl.Role.Contains(types.Array[consts.TenantUserRole]{*filter.Role}))
	}

	tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.Code, filter.Name)
	if err != nil {
		return nil, err
	}
	if tenantFilter {
		if len(tenantIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.TenantID.In(tenantIDs...))
		}
	}

	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "tenant_id":
			q = q.Order(tbl.TenantID.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "tenant_id":
			q = q.Order(tbl.TenantID)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		}
		orderApplied = true
	}
	if !orderApplied {
		q = q.Order(tbl.CreatedAt.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	tenantMap, ownerMap, err := s.tenantMapsForTenantUsers(ctx, list)
	if err != nil {
		return nil, err
	}

	items := make([]super_dto.UserTenantItem, 0, len(list))
	for _, tu := range list {
		tenant := tenantMap[tu.TenantID]
		owner := ownerMap[tu.TenantID]
		item := super_dto.UserTenantItem{
			TenantID:                tu.TenantID,
			Role:                    tu.Role,
			MemberStatus:            tu.Status,
			MemberStatusDescription: tu.Status.Description(),
			JoinedAt:                s.formatTime(tu.CreatedAt),
		}
		if tenant != nil {
			item.TenantStatus = tenant.Status
			item.TenantStatusDescription = tenant.Status.Description()
			item.Name = tenant.Name
			item.Code = tenant.Code
			item.ExpiredAt = s.formatTime(tenant.ExpiredAt)
		}
		if owner != nil {
			item.Owner = &super_dto.TenantOwnerUserLite{
				ID:       owner.ID,
				Username: owner.Username,
			}
		}
		items = append(items, item)
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

func (s *super) ListContents(ctx context.Context, filter *super_dto.SuperContentListFilter) (*requests.Pager, error) {
	tbl, q := models.ContentQuery.QueryContext(ctx)

	if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
		keyword := "%" + strings.TrimSpace(*filter.Keyword) + "%"
		q = q.Where(field.Or(
			tbl.Title.Like(keyword),
			tbl.Description.Like(keyword),
			tbl.Summary.Like(keyword),
		))
	}
	if filter.ID != nil && *filter.ID > 0 {
		q = q.Where(tbl.ID.Eq(*filter.ID))
	}
	if filter.TenantID != nil && *filter.TenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
	}
	if filter.UserID != nil && *filter.UserID > 0 {
		q = q.Where(tbl.UserID.Eq(*filter.UserID))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.Visibility != nil && *filter.Visibility != "" {
		q = q.Where(tbl.Visibility.Eq(*filter.Visibility))
	}

	tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
	if err != nil {
		return nil, err
	}
	if tenantFilter {
		if len(tenantIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.TenantID.In(tenantIDs...))
		}
	}

	userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
	if err != nil {
		return nil, err
	}
	if userFilter {
		if len(userIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.UserID.In(userIDs...))
		}
	}

	if filter.PublishedAtFrom != nil {
		from, err := s.parseFilterTime(filter.PublishedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.PublishedAt.Gte(*from))
		}
	}
	if filter.PublishedAtTo != nil {
		to, err := s.parseFilterTime(filter.PublishedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.PublishedAt.Lte(*to))
		}
	}
	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}

	if filter.PriceAmountMin != nil || filter.PriceAmountMax != nil {
		pTbl, pQ := models.ContentPriceQuery.QueryContext(ctx)
		pq := pQ
		if filter.PriceAmountMin != nil {
			pq = pq.Where(pTbl.PriceAmount.Gte(*filter.PriceAmountMin))
		}
		if filter.PriceAmountMax != nil {
			pq = pq.Where(pTbl.PriceAmount.Lte(*filter.PriceAmountMax))
		}
		prices, err := pq.Select(pTbl.ContentID).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		ids := make([]int64, 0, len(prices))
		for _, price := range prices {
			ids = append(ids, price.ContentID)
		}
		if len(ids) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.ID.In(ids...))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "id":
			q = q.Order(tbl.ID.Desc())
		case "title":
			q = q.Order(tbl.Title.Desc())
		case "tenant_id":
			q = q.Order(tbl.TenantID.Desc())
		case "user_id":
			q = q.Order(tbl.UserID.Desc())
		case "status":
			q = q.Order(tbl.Status.Desc())
		case "visibility":
			q = q.Order(tbl.Visibility.Desc())
		case "published_at":
			q = q.Order(tbl.PublishedAt.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "id":
			q = q.Order(tbl.ID)
		case "title":
			q = q.Order(tbl.Title)
		case "tenant_id":
			q = q.Order(tbl.TenantID)
		case "user_id":
			q = q.Order(tbl.UserID)
		case "status":
			q = q.Order(tbl.Status)
		case "visibility":
			q = q.Order(tbl.Visibility)
		case "published_at":
			q = q.Order(tbl.PublishedAt)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		}
		orderApplied = true
	}

	if !orderApplied {
		q = q.Order(tbl.ID.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	var list []*models.Content
	err = q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).
		UnderlyingDB().
		Preload("Author").
		Preload("ContentAssets.Asset").
		Find(&list).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	priceMap, err := s.contentPriceMap(ctx, list)
	if err != nil {
		return nil, err
	}

	tenantMap, err := s.contentTenantMap(ctx, list)
	if err != nil {
		return nil, err
	}

	data := make([]super_dto.AdminContentItem, 0, len(list))
	for _, c := range list {
		data = append(data, s.toSuperContentItem(c, priceMap[c.ID], tenantMap[c.TenantID]))
	}
	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      data,
	}, nil
}

func (s *super) UpdateContentStatus(ctx context.Context, tenantID, contentID int64, form *super_dto.SuperTenantContentStatusUpdateForm) error {
	tbl, q := models.ContentQuery.QueryContext(ctx)
	_, err := q.Where(tbl.ID.Eq(contentID), tbl.TenantID.Eq(tenantID)).Update(tbl.Status, consts.ContentStatus(form.Status))
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *super) ReviewContent(ctx context.Context, operatorID, contentID int64, form *super_dto.SuperContentReviewForm) error {
	if operatorID == 0 {
		return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
	}
	if form == nil {
		return errorx.ErrBadRequest.WithMsg("审核参数不能为空")
	}

	action := strings.ToLower(strings.TrimSpace(form.Action))
	if action != "approve" && action != "reject" {
		return errorx.ErrBadRequest.WithMsg("审核动作非法")
	}

	tbl, q := models.ContentQuery.QueryContext(ctx)
	content, err := q.Where(tbl.ID.Eq(contentID)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errorx.ErrRecordNotFound
		}
		return errorx.ErrDatabaseError.WithCause(err)
	}
	if content.Status != consts.ContentStatusReviewing {
		return errorx.ErrStatusConflict.WithMsg("内容未处于审核中状态")
	}

	// 审核动作映射为内容状态。
	nextStatus := consts.ContentStatusBlocked
	if action == "approve" {
		nextStatus = consts.ContentStatusPublished
	}

	updates := &models.Content{
		Status:    nextStatus,
		UpdatedAt: time.Now(),
	}
	if nextStatus == consts.ContentStatusPublished {
		updates.PublishedAt = time.Now()
	}
	_, err = q.Where(tbl.ID.Eq(contentID)).Updates(updates)
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}

	// 审核完成后通知作者并记录审计日志。
	title := "内容审核结果"
	detail := "内容审核通过"
	if action == "reject" {
		detail = "内容审核驳回"
		if strings.TrimSpace(form.Reason) != "" {
			detail += "，原因：" + strings.TrimSpace(form.Reason)
		}
	}
	if Notification != nil {
		_ = Notification.Send(ctx, content.TenantID, content.UserID, string(consts.NotificationTypeAudit), title, detail)
	}
	if Audit != nil {
		Audit.Log(ctx, operatorID, "review_content", cast.ToString(contentID), detail)
	}
	return nil
}

func (s *super) ListOrders(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) {
	tbl, q := models.OrderQuery.QueryContext(ctx)

	if filter.ID != nil && *filter.ID > 0 {
		q = q.Where(tbl.ID.Eq(*filter.ID))
	}
	if filter.TenantID != nil && *filter.TenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
	}
	if filter.UserID != nil && *filter.UserID > 0 {
		q = q.Where(tbl.UserID.Eq(*filter.UserID))
	}
	if filter.Type != nil && *filter.Type != "" {
		q = q.Where(tbl.Type.Eq(*filter.Type))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.AmountPaidMin != nil {
		q = q.Where(tbl.AmountPaid.Gte(*filter.AmountPaidMin))
	}
	if filter.AmountPaidMax != nil {
		q = q.Where(tbl.AmountPaid.Lte(*filter.AmountPaidMax))
	}

	tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
	if err != nil {
		return nil, err
	}
	if tenantFilter {
		if len(tenantIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.TenantID.In(tenantIDs...))
		}
	}

	userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
	if err != nil {
		return nil, err
	}
	if userFilter {
		if len(userIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.UserID.In(userIDs...))
		}
	}

	orderIDs, contentFilter, err := s.lookupOrderIDsByContent(ctx, filter.ContentID, filter.ContentTitle)
	if err != nil {
		return nil, err
	}
	if contentFilter {
		if len(orderIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.ID.In(orderIDs...))
		}
	}

	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}
	if filter.PaidAtFrom != nil {
		from, err := s.parseFilterTime(filter.PaidAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.PaidAt.Gte(*from))
		}
	}
	if filter.PaidAtTo != nil {
		to, err := s.parseFilterTime(filter.PaidAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.PaidAt.Lte(*to))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "id":
			q = q.Order(tbl.ID.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		case "paid_at":
			q = q.Order(tbl.PaidAt.Desc())
		case "amount_paid":
			q = q.Order(tbl.AmountPaid.Desc())
		case "amount_original":
			q = q.Order(tbl.AmountOriginal.Desc())
		case "amount_discount":
			q = q.Order(tbl.AmountDiscount.Desc())
		case "status":
			q = q.Order(tbl.Status.Desc())
		case "type":
			q = q.Order(tbl.Type.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "id":
			q = q.Order(tbl.ID)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		case "paid_at":
			q = q.Order(tbl.PaidAt)
		case "amount_paid":
			q = q.Order(tbl.AmountPaid)
		case "amount_original":
			q = q.Order(tbl.AmountOriginal)
		case "amount_discount":
			q = q.Order(tbl.AmountDiscount)
		case "status":
			q = q.Order(tbl.Status)
		case "type":
			q = q.Order(tbl.Type)
		}
		orderApplied = true
	}
	if !orderApplied {
		q = q.Order(tbl.ID.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	items, err := s.buildSuperOrderItems(ctx, list)
	if err != nil {
		return nil, err
	}
	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

func (s *super) GetOrder(ctx context.Context, id int64) (*super_dto.SuperOrderDetail, error) {
	o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var tenant *models.Tenant
	if t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(o.TenantID)).First(); err == nil {
		tenant = t
	}
	var buyer *models.User
	if u, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(o.UserID)).First(); err == nil {
		buyer = u
	}

	itemTbl, itemQ := models.OrderItemQuery.QueryContext(ctx)
	orderItems, err := itemQ.Where(itemTbl.OrderID.Eq(o.ID)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	items := make([]super_dto.SuperOrderItemLine, 0, len(orderItems))
	for _, it := range orderItems {
		items = append(items, s.toSuperOrderItemLine(it))
	}

	item := s.toSuperOrderItem(o, tenant, buyer)
	item.Snapshot = o.Snapshot.Data()
	item.Items = items
	return &super_dto.SuperOrderDetail{
		Order:  &item,
		Tenant: item.Tenant,
		Buyer:  item.Buyer,
	}, nil
}

func (s *super) RefundOrder(ctx context.Context, id int64, form *super_dto.SuperOrderRefundForm) error {
	o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errorx.ErrRecordNotFound
		}
		return errorx.ErrDatabaseError.WithCause(err)
	}

	if o.Status != consts.OrderStatusRefunding {
		if !form.Force {
			return errorx.ErrStatusConflict.WithMsg("订单状态不是退款中")
		}
		_, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).Updates(&models.Order{
			Status:       consts.OrderStatusRefunding,
			RefundReason: form.Reason,
			UpdatedAt:    time.Now(),
		})
		if err != nil {
			return errorx.ErrDatabaseError.WithCause(err)
		}
	}

	t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(o.TenantID)).First()
	if err != nil {
		return errorx.ErrRecordNotFound.WithMsg("租户不存在")
	}

	return Creator.ProcessRefund(ctx, t.ID, t.UserID, id, &v1_dto.RefundForm{
		Action: "accept",
		Reason: form.Reason,
	})
}

func (s *super) OrderStatistics(ctx context.Context) (*super_dto.OrderStatisticsResponse, error) {
	var totals struct {
		TotalCount         int64 `gorm:"column:total_count"`
		TotalAmountPaidSum int64 `gorm:"column:total_amount_paid_sum"`
	}
	err := models.OrderQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Order{}).
		Select("count(*) as total_count, coalesce(sum(amount_paid), 0) as total_amount_paid_sum").
		Scan(&totals).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var rows []struct {
		Status        consts.OrderStatus `gorm:"column:status"`
		Count         int64              `gorm:"column:count"`
		AmountPaidSum int64              `gorm:"column:amount_paid_sum"`
	}
	err = models.OrderQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Order{}).
		Select("status, count(*) as count, coalesce(sum(amount_paid), 0) as amount_paid_sum").
		Group("status").
		Scan(&rows).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	stats := make([]super_dto.OrderStatisticsRow, 0, len(rows))
	for _, row := range rows {
		stats = append(stats, super_dto.OrderStatisticsRow{
			Status:            row.Status,
			StatusDescription: row.Status.Description(),
			Count:             row.Count,
			AmountPaidSum:     row.AmountPaidSum,
		})
	}

	return &super_dto.OrderStatisticsResponse{
		TotalCount:         totals.TotalCount,
		TotalAmountPaidSum: totals.TotalAmountPaidSum,
		ByStatus:           stats,
	}, nil
}

func (s *super) UserStatistics(ctx context.Context) ([]super_dto.UserStatistics, error) {
	var rows []struct {
		Status consts.UserStatus `gorm:"column:status"`
		Count  int64             `gorm:"column:count"`
	}
	err := models.UserQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.User{}).
		Select("status, count(*) as count").
		Group("status").
		Scan(&rows).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	stats := make([]super_dto.UserStatistics, 0, len(rows))
	for _, row := range rows {
		stats = append(stats, super_dto.UserStatistics{
			Status:            row.Status,
			StatusDescription: row.Status.Description(),
			Count:             row.Count,
		})
	}
	return stats, nil
}

func (s *super) UserStatuses(ctx context.Context) ([]requests.KV, error) {
	return consts.UserStatusItems(), nil
}

func (s *super) TenantStatuses(ctx context.Context) ([]requests.KV, error) {
	return consts.TenantStatusItems(), nil
}

func (s *super) toSuperUserDTO(u *models.User) *super_dto.User {
	return &super_dto.User{
		ID:                 u.ID,
		Phone:              u.Phone,
		Nickname:           u.Nickname,
		Avatar:             u.Avatar,
		Gender:             u.Gender,
		Bio:                u.Bio,
		Balance:            float64(u.Balance) / 100.0,
		Points:             u.Points,
		IsRealNameVerified: u.IsRealNameVerified,
	}
}

func (s *super) toSuperUserLite(u *models.User) *super_dto.SuperUserLite {
	if u == nil {
		return nil
	}
	return &super_dto.SuperUserLite{
		ID:                u.ID,
		Username:          u.Username,
		Roles:             u.Roles,
		Status:            u.Status,
		StatusDescription: u.Status.Description(),
		VerifiedAt:        s.formatTime(u.VerifiedAt),
		CreatedAt:         s.formatTime(u.CreatedAt),
		UpdatedAt:         s.formatTime(u.UpdatedAt),
	}
}

func hasRole(roles types.Array[consts.Role], role consts.Role) bool {
	for _, r := range roles {
		if r == role {
			return true
		}
	}
	return false
}

func hasTenantRole(roles types.Array[consts.TenantUserRole], role consts.TenantUserRole) bool {
	for _, r := range roles {
		if r == role {
			return true
		}
	}
	return false
}

func (s *super) buildSuperOrderItems(ctx context.Context, orders []*models.Order) ([]super_dto.SuperOrderItem, error) {
	if len(orders) == 0 {
		return []super_dto.SuperOrderItem{}, nil
	}

	tenantIDs := make([]int64, 0, len(orders))
	userIDs := make([]int64, 0, len(orders))
	tenantSet := make(map[int64]struct{})
	userSet := make(map[int64]struct{})
	for _, o := range orders {
		if _, ok := tenantSet[o.TenantID]; !ok {
			tenantSet[o.TenantID] = struct{}{}
			tenantIDs = append(tenantIDs, o.TenantID)
		}
		if _, ok := userSet[o.UserID]; !ok {
			userSet[o.UserID] = struct{}{}
			userIDs = append(userIDs, o.UserID)
		}
	}

	tenantMap := make(map[int64]*models.Tenant, len(tenantIDs))
	if len(tenantIDs) > 0 {
		tbl, q := models.TenantQuery.QueryContext(ctx)
		tenants, err := q.Where(tbl.ID.In(tenantIDs...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, t := range tenants {
			tenantMap[t.ID] = t
		}
	}

	userMap := make(map[int64]*models.User, len(userIDs))
	if len(userIDs) > 0 {
		tbl, q := models.UserQuery.QueryContext(ctx)
		users, err := q.Where(tbl.ID.In(userIDs...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, u := range users {
			userMap[u.ID] = u
		}
	}

	items := make([]super_dto.SuperOrderItem, 0, len(orders))
	for _, o := range orders {
		items = append(items, s.toSuperOrderItem(o, tenantMap[o.TenantID], userMap[o.UserID]))
	}
	return items, nil
}

func (s *super) toSuperOrderItem(o *models.Order, tenant *models.Tenant, buyer *models.User) super_dto.SuperOrderItem {
	item := super_dto.SuperOrderItem{
		ID:                o.ID,
		Type:              o.Type,
		Status:            o.Status,
		StatusDescription: o.Status.Description(),
		Currency:          o.Currency,
		AmountOriginal:    o.AmountOriginal,
		AmountDiscount:    o.AmountDiscount,
		AmountPaid:        o.AmountPaid,
		CreatedAt:         o.CreatedAt.Format(time.RFC3339),
		UpdatedAt:         o.UpdatedAt.Format(time.RFC3339),
	}

	if !o.PaidAt.IsZero() {
		item.PaidAt = o.PaidAt.Format(time.RFC3339)
	}
	if !o.RefundedAt.IsZero() {
		item.RefundedAt = o.RefundedAt.Format(time.RFC3339)
	}

	if tenant != nil {
		item.Tenant = &super_dto.OrderTenantLite{
			ID:   tenant.ID,
			Code: tenant.Code,
			Name: tenant.Name,
		}
	}
	if buyer != nil {
		item.Buyer = &super_dto.OrderBuyerLite{
			ID:       buyer.ID,
			Username: buyer.Username,
		}
	}

	return item
}

func (s *super) toSuperOrderItemLine(item *models.OrderItem) super_dto.SuperOrderItemLine {
	return super_dto.SuperOrderItemLine{
		ID:         item.ID,
		ContentID:  item.ContentID,
		AmountPaid: item.AmountPaid,
		Snapshot:   item.Snapshot.Data(),
	}
}

func (s *super) parseFilterTime(value *string) (*time.Time, error) {
	if value == nil {
		return nil, nil
	}
	text := strings.TrimSpace(*value)
	if text == "" {
		return nil, nil
	}
	if t, err := time.Parse(time.RFC3339, text); err == nil {
		return &t, nil
	}
	t, err := time.Parse("2006-01-02", text)
	if err != nil {
		return nil, errorx.ErrInvalidFormat.WithCause(err)
	}
	return &t, nil
}

func (s *super) lookupTenantIDs(ctx context.Context, code, name *string) ([]int64, bool, error) {
	codeText := ""
	if code != nil {
		codeText = strings.TrimSpace(*code)
	}
	nameText := ""
	if name != nil {
		nameText = strings.TrimSpace(*name)
	}
	if codeText == "" && nameText == "" {
		return nil, false, nil
	}

	tbl, q := models.TenantQuery.QueryContext(ctx)
	if codeText != "" {
		q = q.Where(tbl.Code.Like("%" + codeText + "%"))
	}
	if nameText != "" {
		q = q.Where(tbl.Name.Like("%" + nameText + "%"))
	}
	tenants, err := q.Select(tbl.ID).Find()
	if err != nil {
		return nil, true, errorx.ErrDatabaseError.WithCause(err)
	}

	ids := make([]int64, 0, len(tenants))
	for _, tenant := range tenants {
		ids = append(ids, tenant.ID)
	}
	return ids, true, nil
}

func (s *super) lookupUserIDs(ctx context.Context, username *string) ([]int64, bool, error) {
	text := ""
	if username != nil {
		text = strings.TrimSpace(*username)
	}
	if text == "" {
		return nil, false, nil
	}

	tbl, q := models.UserQuery.QueryContext(ctx)
	keyword := "%" + text + "%"
	q = q.Where(field.Or(tbl.Username.Like(keyword), tbl.Nickname.Like(keyword)))
	users, err := q.Select(tbl.ID).Find()
	if err != nil {
		return nil, true, errorx.ErrDatabaseError.WithCause(err)
	}

	ids := make([]int64, 0, len(users))
	for _, user := range users {
		ids = append(ids, user.ID)
	}
	return ids, true, nil
}

func (s *super) lookupOrderIDsByContent(ctx context.Context, contentID *int64, contentTitle *string) ([]int64, bool, error) {
	var id int64
	if contentID != nil {
		id = *contentID
	}
	title := ""
	if contentTitle != nil {
		title = strings.TrimSpace(*contentTitle)
	}
	if id <= 0 && title == "" {
		return nil, false, nil
	}

	tbl, q := models.OrderItemQuery.QueryContext(ctx)
	if id > 0 {
		q = q.Where(tbl.ContentID.Eq(id))
	}

	var items []*models.OrderItem
	if title != "" {
		// JSONB 字段需要使用 UnderlyingDB 做模糊查询。
		keyword := "%" + title + "%"
		err := q.UnderlyingDB().
			Where("snapshot ->> 'content_title' ILIKE ?", keyword).
			Select("order_id").
			Find(&items).Error
		if err != nil {
			return nil, true, errorx.ErrDatabaseError.WithCause(err)
		}
	} else {
		list, err := q.Select(tbl.OrderID).Find()
		if err != nil {
			return nil, true, errorx.ErrDatabaseError.WithCause(err)
		}
		items = list
	}

	idMap := make(map[int64]struct{}, len(items))
	for _, item := range items {
		idMap[item.OrderID] = struct{}{}
	}
	ids := make([]int64, 0, len(idMap))
	for orderID := range idMap {
		ids = append(ids, orderID)
	}
	return ids, true, nil
}

func (s *super) contentPriceMap(ctx context.Context, list []*models.Content) (map[int64]*models.ContentPrice, error) {
	if len(list) == 0 {
		return map[int64]*models.ContentPrice{}, nil
	}

	ids := make([]int64, 0, len(list))
	for _, item := range list {
		ids = append(ids, item.ID)
	}

	tbl, q := models.ContentPriceQuery.QueryContext(ctx)
	prices, err := q.Where(tbl.ContentID.In(ids...)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	priceMap := make(map[int64]*models.ContentPrice, len(prices))
	for _, price := range prices {
		priceMap[price.ContentID] = price
	}
	return priceMap, nil
}

func (s *super) contentTenantMap(ctx context.Context, list []*models.Content) (map[int64]*models.Tenant, error) {
	if len(list) == 0 {
		return map[int64]*models.Tenant{}, nil
	}

	ids := make([]int64, 0, len(list))
	seen := make(map[int64]struct{}, len(list))
	for _, item := range list {
		if _, ok := seen[item.TenantID]; ok {
			continue
		}
		seen[item.TenantID] = struct{}{}
		ids = append(ids, item.TenantID)
	}

	tbl, q := models.TenantQuery.QueryContext(ctx)
	tenants, err := q.Where(tbl.ID.In(ids...)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	tenantMap := make(map[int64]*models.Tenant, len(tenants))
	for _, tenant := range tenants {
		tenantMap[tenant.ID] = tenant
	}
	return tenantMap, nil
}

func (s *super) toSuperContentItem(item *models.Content, price *models.ContentPrice, tenant *models.Tenant) super_dto.AdminContentItem {
	return super_dto.AdminContentItem{
		Content:               s.toSuperContentDTO(item, price),
		Owner:                 s.toSuperContentOwner(item.Author),
		Price:                 s.toSuperContentPrice(price),
		StatusDescription:     item.Status.Description(),
		VisibilityDescription: item.Visibility.Description(),
		Tenant:                s.toSuperContentTenant(tenant),
	}
}

func (s *super) toSuperContentOwner(author *models.User) *super_dto.AdminContentOwnerLite {
	if author == nil {
		return nil
	}
	return &super_dto.AdminContentOwnerLite{
		ID:       author.ID,
		Username: author.Username,
		Roles:    author.Roles,
		Status:   author.Status,
	}
}

func (s *super) toSuperContentTenant(tenant *models.Tenant) *super_dto.SuperContentTenantLite {
	if tenant == nil {
		return nil
	}
	return &super_dto.SuperContentTenantLite{
		ID:   tenant.ID,
		Code: tenant.Code,
		Name: tenant.Name,
	}
}

func (s *super) toSuperContentDTO(item *models.Content, price *models.ContentPrice) *v1_dto.ContentItem {
	dto := &v1_dto.ContentItem{
		ID:          item.ID,
		TenantID:    item.TenantID,
		UserID:      item.UserID,
		Title:       item.Title,
		Genre:       item.Genre,
		Status:      string(item.Status),
		Visibility:  string(item.Visibility),
		AuthorID:    item.UserID,
		Views:       int(item.Views),
		Likes:       int(item.Likes),
		CreatedAt:   item.CreatedAt.Format("2006-01-02"),
		IsPurchased: false,
	}
	if !item.PublishedAt.IsZero() {
		dto.PublishedAt = item.PublishedAt.Format("2006-01-02")
	}
	if price != nil {
		dto.Price = float64(price.PriceAmount) / 100.0
	}
	if item.Author != nil {
		dto.AuthorName = item.Author.Nickname
		if dto.AuthorName == "" {
			dto.AuthorName = item.Author.Username
		}
		dto.AuthorAvatar = item.Author.Avatar
	}

	var hasVideo, hasAudio bool
	for _, asset := range item.ContentAssets {
		if asset.Asset == nil {
			continue
		}
		if asset.Role == consts.ContentAssetRoleCover {
			dto.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
		}
		switch asset.Asset.Type {
		case consts.MediaAssetTypeVideo:
			hasVideo = true
		case consts.MediaAssetTypeAudio:
			hasAudio = true
		}
	}

	if dto.Cover == "" && len(item.ContentAssets) > 0 {
		for _, asset := range item.ContentAssets {
			if asset.Asset != nil && asset.Asset.Type == consts.MediaAssetTypeImage {
				dto.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
				break
			}
		}
	}

	if hasVideo {
		dto.Type = "video"
	} else if hasAudio {
		dto.Type = "audio"
	} else {
		dto.Type = "article"
	}

	return dto
}

func (s *super) toSuperContentPrice(price *models.ContentPrice) *v1_dto.ContentPrice {
	if price == nil {
		return nil
	}
	dto := &v1_dto.ContentPrice{
		Currency:      string(price.Currency),
		PriceAmount:   float64(price.PriceAmount) / 100.0,
		DiscountType:  string(price.DiscountType),
		DiscountValue: s.toSuperDiscountValue(price),
	}
	if !price.DiscountStartAt.IsZero() {
		dto.DiscountStartAt = price.DiscountStartAt.Format(time.RFC3339)
	}
	if !price.DiscountEndAt.IsZero() {
		dto.DiscountEndAt = price.DiscountEndAt.Format(time.RFC3339)
	}
	return dto
}

func (s *super) toSuperDiscountValue(price *models.ContentPrice) float64 {
	if price == nil {
		return 0
	}
	if price.DiscountType == consts.DiscountTypeAmount {
		return float64(price.DiscountValue) / 100.0
	}
	return float64(price.DiscountValue)
}

func (s *super) buildTenantItems(ctx context.Context, list []*models.Tenant) ([]super_dto.TenantItem, error) {
	if len(list) == 0 {
		return []super_dto.TenantItem{}, nil
	}

	tenantIDs := make([]int64, 0, len(list))
	tenantOwnerIDs := make(map[int64]struct{}, len(list))
	for _, t := range list {
		tenantIDs = append(tenantIDs, t.ID)
		tenantOwnerIDs[t.UserID] = struct{}{}
	}

	// 统计租户成员数与管理员列表（基于 tenant_users）。
	userCountMap := make(map[int64]int64, len(list))
	adminSet := make(map[int64]map[int64]struct{}, len(list))
	tblTu, qTu := models.TenantUserQuery.QueryContext(ctx)
	tenantUsers, err := qTu.Where(tblTu.TenantID.In(tenantIDs...)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	for _, tu := range tenantUsers {
		userCountMap[tu.TenantID]++
		if hasTenantRole(tu.Role, consts.TenantUserRoleTenantAdmin) {
			if _, ok := adminSet[tu.TenantID]; !ok {
				adminSet[tu.TenantID] = make(map[int64]struct{})
			}
			adminSet[tu.TenantID][tu.UserID] = struct{}{}
		}
	}

	adminIDs := make(map[int64]struct{})
	for _, ids := range adminSet {
		for id := range ids {
			adminIDs[id] = struct{}{}
		}
	}

	userIDs := make([]int64, 0, len(adminIDs)+len(tenantOwnerIDs))
	seen := make(map[int64]struct{})
	for id := range adminIDs {
		seen[id] = struct{}{}
		userIDs = append(userIDs, id)
	}
	for id := range tenantOwnerIDs {
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		userIDs = append(userIDs, id)
	}

	userMap := make(map[int64]*models.User, len(userIDs))
	if len(userIDs) > 0 {
		tblUser, qUser := models.UserQuery.QueryContext(ctx)
		users, err := qUser.Where(tblUser.ID.In(userIDs...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, u := range users {
			userMap[u.ID] = u
		}
	}

	// 汇总租户收入（按已支付/退款中/已退款订单统计实付金额）。
	incomeMap := make(map[int64]int64, len(list))
	if len(tenantIDs) > 0 {
		var rows []struct {
			TenantID      int64 `gorm:"column:tenant_id"`
			AmountPaidSum int64 `gorm:"column:amount_paid_sum"`
		}
		err := models.OrderQuery.WithContext(ctx).
			UnderlyingDB().
			Model(&models.Order{}).
			Select("tenant_id, coalesce(sum(amount_paid), 0) as amount_paid_sum").
			Where("tenant_id IN ?", tenantIDs).
			Where("status IN ?", []consts.OrderStatus{
				consts.OrderStatusPaid,
				consts.OrderStatusRefunding,
				consts.OrderStatusRefunded,
			}).
			Group("tenant_id").
			Scan(&rows).Error
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, row := range rows {
			incomeMap[row.TenantID] = row.AmountPaidSum
		}
	}

	items := make([]super_dto.TenantItem, 0, len(list))
	for _, t := range list {
		item := super_dto.TenantItem{
			ID:                  t.ID,
			UUID:                t.UUID.String(),
			Name:                t.Name,
			Code:                t.Code,
			Status:              t.Status,
			StatusDescription:   t.Status.Description(),
			UserID:              t.UserID,
			UserCount:           userCountMap[t.ID],
			IncomeAmountPaidSum: incomeMap[t.ID],
			ExpiredAt:           s.formatTime(t.ExpiredAt),
			CreatedAt:           s.formatTime(t.CreatedAt),
			UpdatedAt:           s.formatTime(t.UpdatedAt),
		}

		if owner := userMap[t.UserID]; owner != nil {
			item.Owner = &super_dto.TenantOwnerUserLite{
				ID:       owner.ID,
				Username: owner.Username,
			}
		}

		if adminSet[t.ID] != nil {
			admins := make([]*super_dto.TenantAdminUserLite, 0, len(adminSet[t.ID]))
			for adminID := range adminSet[t.ID] {
				if u := userMap[adminID]; u != nil {
					admins = append(admins, &super_dto.TenantAdminUserLite{
						ID:       u.ID,
						Username: u.Username,
					})
				}
			}
			item.AdminUsers = admins
		}

		items = append(items, item)
	}

	return items, nil
}

type tenantHealthContentAgg struct {
	TenantID       int64 `gorm:"column:tenant_id"`
	ContentCount   int64 `gorm:"column:content_count"`
	PublishedCount int64 `gorm:"column:published_count"`
}

type tenantHealthOrderAgg struct {
	TenantID     int64     `gorm:"column:tenant_id"`
	PaidCount    int64     `gorm:"column:paid_count"`
	PaidAmount   int64     `gorm:"column:paid_amount"`
	RefundCount  int64     `gorm:"column:refund_count"`
	RefundAmount int64     `gorm:"column:refund_amount"`
	LastPaidAt   time.Time `gorm:"column:last_paid_at"`
}

type tenantHealthMetrics struct {
	MemberCount           int64
	ContentCount          int64
	PublishedContentCount int64
	PaidOrders            int64
	PaidAmount            int64
	RefundOrders          int64
	RefundAmount          int64
	RefundRate            float64
	LastPaidAt            time.Time
}

func (s *super) buildTenantHealthItems(ctx context.Context, list []*models.Tenant) ([]super_dto.TenantHealthItem, error) {
	if len(list) == 0 {
		return []super_dto.TenantHealthItem{}, nil
	}

	tenantIDs := make([]int64, 0, len(list))
	ownerIDs := make(map[int64]struct{}, len(list))
	for _, t := range list {
		tenantIDs = append(tenantIDs, t.ID)
		ownerIDs[t.UserID] = struct{}{}
	}

	// 查询租户所有者信息。
	ownerMap := make(map[int64]*models.User, len(ownerIDs))
	if len(ownerIDs) > 0 {
		ids := make([]int64, 0, len(ownerIDs))
		for id := range ownerIDs {
			ids = append(ids, id)
		}
		tblUser, qUser := models.UserQuery.QueryContext(ctx)
		users, err := qUser.Where(tblUser.ID.In(ids...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, u := range users {
			ownerMap[u.ID] = u
		}
	}

	// 汇总租户成员数。
	memberCountMap := make(map[int64]int64, len(list))
	var memberRows []struct {
		TenantID int64 `gorm:"column:tenant_id"`
		Count    int64 `gorm:"column:count"`
	}
	err := models.TenantUserQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.TenantUser{}).
		Select("tenant_id, count(*) as count").
		Where("tenant_id IN ?", tenantIDs).
		Group("tenant_id").
		Scan(&memberRows).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	for _, row := range memberRows {
		memberCountMap[row.TenantID] = row.Count
	}

	// 汇总内容总量与发布量。
	contentMap := make(map[int64]tenantHealthContentAgg, len(list))
	contentRows := make([]tenantHealthContentAgg, 0)
	err = models.ContentQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Content{}).
		Select(
			"tenant_id, count(*) as content_count, coalesce(sum(case when status = ? then 1 else 0 end), 0) as published_count",
			consts.ContentStatusPublished,
		).
		Where("tenant_id IN ?", tenantIDs).
		Group("tenant_id").
		Scan(&contentRows).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	for _, row := range contentRows {
		contentMap[row.TenantID] = row
	}

	// 汇总订单成交/退款指标。
	orderMap := make(map[int64]tenantHealthOrderAgg, len(list))
	orderRows := make([]tenantHealthOrderAgg, 0)
	err = models.OrderQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Order{}).
		Select(
			"tenant_id, "+
				"coalesce(sum(case when status = ? then 1 else 0 end), 0) as paid_count, "+
				"coalesce(sum(case when status = ? then amount_paid else 0 end), 0) as paid_amount, "+
				"coalesce(sum(case when status = ? then 1 else 0 end), 0) as refund_count, "+
				"coalesce(sum(case when status = ? then amount_paid else 0 end), 0) as refund_amount, "+
				"max(case when status = ? then paid_at else null end) as last_paid_at",
			consts.OrderStatusPaid,
			consts.OrderStatusPaid,
			consts.OrderStatusRefunded,
			consts.OrderStatusRefunded,
			consts.OrderStatusPaid,
		).
		Where("tenant_id IN ? AND type = ?", tenantIDs, consts.OrderTypeContentPurchase).
		Group("tenant_id").
		Scan(&orderRows).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	for _, row := range orderRows {
		orderMap[row.TenantID] = row
	}

	items := make([]super_dto.TenantHealthItem, 0, len(list))
	for _, t := range list {
		contentAgg := contentMap[t.ID]
		orderAgg := orderMap[t.ID]
		refundRate := 0.0
		if orderAgg.PaidCount > 0 {
			refundRate = float64(orderAgg.RefundCount) / float64(orderAgg.PaidCount)
		}

		metrics := tenantHealthMetrics{
			MemberCount:           memberCountMap[t.ID],
			ContentCount:          contentAgg.ContentCount,
			PublishedContentCount: contentAgg.PublishedCount,
			PaidOrders:            orderAgg.PaidCount,
			PaidAmount:            orderAgg.PaidAmount,
			RefundOrders:          orderAgg.RefundCount,
			RefundAmount:          orderAgg.RefundAmount,
			RefundRate:            refundRate,
			LastPaidAt:            orderAgg.LastPaidAt,
		}

		healthLevel, alerts := s.evaluateTenantHealth(t, metrics)
		item := super_dto.TenantHealthItem{
			TenantID:              t.ID,
			Code:                  t.Code,
			Name:                  t.Name,
			Status:                t.Status,
			StatusDescription:     t.Status.Description(),
			MemberCount:           metrics.MemberCount,
			ContentCount:          metrics.ContentCount,
			PublishedContentCount: metrics.PublishedContentCount,
			PaidOrders:            metrics.PaidOrders,
			PaidAmount:            metrics.PaidAmount,
			RefundOrders:          metrics.RefundOrders,
			RefundAmount:          metrics.RefundAmount,
			RefundRate:            metrics.RefundRate,
			LastPaidAt:            s.formatTime(metrics.LastPaidAt),
			HealthLevel:           healthLevel,
			Alerts:                alerts,
		}
		if owner := ownerMap[t.UserID]; owner != nil {
			item.Owner = &super_dto.TenantOwnerUserLite{
				ID:       owner.ID,
				Username: owner.Username,
			}
		}
		items = append(items, item)
	}

	return items, nil
}

func (s *super) evaluateTenantHealth(tenant *models.Tenant, metrics tenantHealthMetrics) (string, []string) {
	level := 0
	alerts := make([]string, 0)
	now := time.Now()

	// 根据租户状态与过期情况判断风险级别。
	if tenant.Status == consts.TenantStatusBanned {
		level = 2
		alerts = append(alerts, "租户已封禁")
	} else if tenant.Status == consts.TenantStatusPendingVerify {
		if level < 1 {
			level = 1
		}
		alerts = append(alerts, "租户待审核")
	}
	if !tenant.ExpiredAt.IsZero() && tenant.ExpiredAt.Before(now) {
		level = 2
		alerts = append(alerts, "租户已过期")
	}

	// 内容与成交基础判断。
	if metrics.PublishedContentCount == 0 {
		if level < 1 {
			level = 1
		}
		alerts = append(alerts, "无已发布内容")
	}
	if metrics.PaidOrders == 0 {
		if level < 1 {
			level = 1
		}
		alerts = append(alerts, "暂无成交")
	} else if !metrics.LastPaidAt.IsZero() {
		if metrics.LastPaidAt.Before(now.AddDate(0, 0, -90)) {
			level = 2
			alerts = append(alerts, "成交活跃度偏低")
		} else if metrics.LastPaidAt.Before(now.AddDate(0, 0, -30)) {
			if level < 1 {
				level = 1
			}
			alerts = append(alerts, "成交活跃度偏低")
		}
	}

	// 退款率异常判断。
	if metrics.RefundRate >= 0.2 {
		level = 2
		alerts = append(alerts, "退款率偏高")
	} else if metrics.RefundRate >= 0.1 {
		if level < 1 {
			level = 1
		}
		alerts = append(alerts, "退款率偏高")
	}

	switch level {
	case 1:
		return "warning", alerts
	case 2:
		return "risk", alerts
	default:
		return "healthy", alerts
	}
}

func (s *super) ListWithdrawals(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) {
	if filter == nil {
		filter = &super_dto.SuperOrderListFilter{}
	}

	tbl, q := models.OrderQuery.QueryContext(ctx)
	q = q.Where(tbl.Type.Eq(consts.OrderTypeWithdrawal))

	if filter.ID != nil && *filter.ID > 0 {
		q = q.Where(tbl.ID.Eq(*filter.ID))
	}
	if filter.TenantID != nil && *filter.TenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
	}
	if filter.UserID != nil && *filter.UserID > 0 {
		q = q.Where(tbl.UserID.Eq(*filter.UserID))
	}
	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(*filter.Status))
	}
	if filter.AmountPaidMin != nil {
		q = q.Where(tbl.AmountPaid.Gte(*filter.AmountPaidMin))
	}
	if filter.AmountPaidMax != nil {
		q = q.Where(tbl.AmountPaid.Lte(*filter.AmountPaidMax))
	}

	tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
	if err != nil {
		return nil, err
	}
	if tenantFilter {
		if len(tenantIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.TenantID.In(tenantIDs...))
		}
	}

	userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
	if err != nil {
		return nil, err
	}
	if userFilter {
		if len(userIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.UserID.In(userIDs...))
		}
	}

	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}
	if filter.PaidAtFrom != nil {
		from, err := s.parseFilterTime(filter.PaidAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.PaidAt.Gte(*from))
		}
	}
	if filter.PaidAtTo != nil {
		to, err := s.parseFilterTime(filter.PaidAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.PaidAt.Lte(*to))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "id":
			q = q.Order(tbl.ID.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		case "paid_at":
			q = q.Order(tbl.PaidAt.Desc())
		case "amount_paid":
			q = q.Order(tbl.AmountPaid.Desc())
		case "status":
			q = q.Order(tbl.Status.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "id":
			q = q.Order(tbl.ID)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		case "paid_at":
			q = q.Order(tbl.PaidAt)
		case "amount_paid":
			q = q.Order(tbl.AmountPaid)
		case "status":
			q = q.Order(tbl.Status)
		}
		orderApplied = true
	}
	if !orderApplied {
		q = q.Order(tbl.ID.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	items, err := s.buildSuperOrderItems(ctx, list)
	if err != nil {
		return nil, err
	}
	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

func (s *super) ListCoupons(ctx context.Context, filter *super_dto.SuperCouponListFilter) (*requests.Pager, error) {
	if filter == nil {
		filter = &super_dto.SuperCouponListFilter{}
	}

	tbl, q := models.CouponQuery.QueryContext(ctx)
	if filter.ID != nil && *filter.ID > 0 {
		q = q.Where(tbl.ID.Eq(*filter.ID))
	}
	if filter.TenantID != nil && *filter.TenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
	}

	tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
	if err != nil {
		return nil, err
	}
	if tenantFilter {
		if len(tenantIDs) == 0 {
			q = q.Where(tbl.ID.Eq(-1))
		} else {
			q = q.Where(tbl.TenantID.In(tenantIDs...))
		}
	}

	if filter.Type != nil && strings.TrimSpace(*filter.Type) != "" {
		parsed, err := consts.ParseCouponType(strings.TrimSpace(*filter.Type))
		if err != nil {
			return nil, errorx.ErrInvalidParameter.WithCause(err).WithMsg("优惠券类型无效")
		}
		q = q.Where(tbl.Type.Eq(parsed))
	}
	if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
		keyword := strings.TrimSpace(*filter.Keyword)
		q = q.Where(field.Or(
			tbl.Title.Like("%"+keyword+"%"),
			tbl.Description.Like("%"+keyword+"%"),
		))
	}
	if filter.Status != nil && strings.TrimSpace(*filter.Status) != "" {
		status := strings.ToLower(strings.TrimSpace(*filter.Status))
		now := time.Now()
		switch status {
		case "active":
			q = q.Where(field.Or(tbl.StartAt.Lte(now), tbl.StartAt.IsNull()))
			q = q.Where(field.Or(tbl.EndAt.Gte(now), tbl.EndAt.IsNull()))
		case "expired":
			q = q.Where(tbl.EndAt.IsNotNull(), tbl.EndAt.Lt(now))
		case "upcoming":
			q = q.Where(tbl.StartAt.IsNotNull(), tbl.StartAt.Gt(now))
		default:
			return nil, errorx.ErrInvalidParameter.WithMsg("状态参数无效")
		}
	}

	if filter.CreatedAtFrom != nil {
		from, err := s.parseFilterTime(filter.CreatedAtFrom)
		if err != nil {
			return nil, err
		}
		if from != nil {
			q = q.Where(tbl.CreatedAt.Gte(*from))
		}
	}
	if filter.CreatedAtTo != nil {
		to, err := s.parseFilterTime(filter.CreatedAtTo)
		if err != nil {
			return nil, err
		}
		if to != nil {
			q = q.Where(tbl.CreatedAt.Lte(*to))
		}
	}

	orderApplied := false
	if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
		switch strings.TrimSpace(*filter.Desc) {
		case "id":
			q = q.Order(tbl.ID.Desc())
		case "created_at":
			q = q.Order(tbl.CreatedAt.Desc())
		case "start_at":
			q = q.Order(tbl.StartAt.Desc())
		case "end_at":
			q = q.Order(tbl.EndAt.Desc())
		}
		orderApplied = true
	} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
		switch strings.TrimSpace(*filter.Asc) {
		case "id":
			q = q.Order(tbl.ID)
		case "created_at":
			q = q.Order(tbl.CreatedAt)
		case "start_at":
			q = q.Order(tbl.StartAt)
		case "end_at":
			q = q.Order(tbl.EndAt)
		}
		orderApplied = true
	}
	if !orderApplied {
		q = q.Order(tbl.CreatedAt.Desc())
	}

	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	items, err := s.buildSuperCouponItems(ctx, list)
	if err != nil {
		return nil, err
	}
	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      items,
	}, nil
}

func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperReportOverviewFilter) (*v1_dto.ReportOverviewResponse, error) {
	// 统一统计时间范围与粒度。
	rg, err := s.normalizeReportRange(filter)
	if err != nil {
		return nil, err
	}

	tenantID := int64(0)
	if filter != nil && filter.TenantID != nil {
		tenantID = *filter.TenantID
	}

	// 统计累计曝光（全量累计值，暂无按时间拆分的曝光记录）。
	var totalViews int64
	contentQuery := models.ContentQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Content{}).
		Select("coalesce(sum(views), 0)")
	if tenantID > 0 {
		contentQuery = contentQuery.Where("tenant_id = ?", tenantID)
	}
	if err := contentQuery.Scan(&totalViews).Error; err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	// 订单仅统计内容购买类型，并按状态划分已支付/已退款。
	paidCount, paidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
	if err != nil {
		return nil, err
	}
	refundCount, refundAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
	if err != nil {
		return nil, err
	}

	conversionRate := 0.0
	if totalViews > 0 {
		conversionRate = float64(paidCount) / float64(totalViews)
	}

	paidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
	if err != nil {
		return nil, err
	}
	refundSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
	if err != nil {
		return nil, err
	}

	items := make([]v1_dto.ReportOverviewItem, 0)
	for day := rg.startDay; !day.After(rg.endDay); day = day.AddDate(0, 0, 1) {
		key := day.Format("2006-01-02")
		paidItem := paidSeries[key]
		refundItem := refundSeries[key]
		items = append(items, v1_dto.ReportOverviewItem{
			Date:         key,
			PaidOrders:   paidItem.Count,
			PaidAmount:   float64(paidItem.Amount) / 100.0,
			RefundOrders: refundItem.Count,
			RefundAmount: float64(refundItem.Amount) / 100.0,
		})
	}

	return &v1_dto.ReportOverviewResponse{
		Summary: v1_dto.ReportSummary{
			TotalViews:     totalViews,
			PaidOrders:     paidCount,
			PaidAmount:     float64(paidAmount) / 100.0,
			RefundOrders:   refundCount,
			RefundAmount:   float64(refundAmount) / 100.0,
			ConversionRate: conversionRate,
		},
		Items: items,
	}, nil
}

func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExportForm) (*v1_dto.ReportExportResponse, error) {
	if form == nil {
		return nil, errorx.ErrBadRequest.WithMsg("导出参数不能为空")
	}
	format := strings.ToLower(strings.TrimSpace(form.Format))
	if format == "" {
		format = "csv"
	}
	if format != "csv" {
		return nil, errorx.ErrBadRequest.WithMsg("仅支持 CSV 导出")
	}

	overview, err := s.ReportOverview(ctx, &super_dto.SuperReportOverviewFilter{
		TenantID:    form.TenantID,
		StartAt:     form.StartAt,
		EndAt:       form.EndAt,
		Granularity: form.Granularity,
	})
	if err != nil {
		return nil, err
	}

	builder := &strings.Builder{}
	builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount\n")
	for _, item := range overview.Items {
		builder.WriteString(item.Date)
		builder.WriteString(",")
		builder.WriteString(strconv.FormatInt(item.PaidOrders, 10))
		builder.WriteString(",")
		builder.WriteString(formatAmount(item.PaidAmount))
		builder.WriteString(",")
		builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
		builder.WriteString(",")
		builder.WriteString(formatAmount(item.RefundAmount))
		builder.WriteString("\n")
	}

	filename := "report_overview_" + time.Now().Format("20060102_150405") + ".csv"
	return &v1_dto.ReportExportResponse{
		Filename: filename,
		MimeType: "text/csv",
		Content:  builder.String(),
	}, nil
}

func (s *super) reportOrderAggregate(
	ctx context.Context,
	tenantID int64,
	status consts.OrderStatus,
	timeField string,
	rg reportRange,
) (int64, int64, error) {
	var total struct {
		Count  int64 `gorm:"column:count"`
		Amount int64 `gorm:"column:amount"`
	}

	query := models.OrderQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Order{}).
		Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
		Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
			consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
	if tenantID > 0 {
		query = query.Where("tenant_id = ?", tenantID)
	}

	if err := query.Scan(&total).Error; err != nil {
		return 0, 0, errorx.ErrDatabaseError.WithCause(err)
	}
	return total.Count, total.Amount, nil
}

func (s *super) reportOrderSeries(
	ctx context.Context,
	tenantID int64,
	status consts.OrderStatus,
	timeField string,
	rg reportRange,
) (map[string]reportAggRow, error) {
	rows := make([]reportAggRow, 0)
	query := models.OrderQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Order{}).
		Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
		Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
			consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
	if tenantID > 0 {
		query = query.Where("tenant_id = ?", tenantID)
	}

	if err := query.Group("day").Scan(&rows).Error; err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	result := make(map[string]reportAggRow, len(rows))
	for _, row := range rows {
		key := row.Day.Format("2006-01-02")
		result[key] = row
	}
	return result, nil
}

func (s *super) normalizeReportRange(filter *super_dto.SuperReportOverviewFilter) (reportRange, error) {
	granularity := "day"
	if filter != nil && filter.Granularity != nil && strings.TrimSpace(*filter.Granularity) != "" {
		granularity = strings.ToLower(strings.TrimSpace(*filter.Granularity))
	}
	if granularity != "day" {
		return reportRange{}, errorx.ErrBadRequest.WithMsg("仅支持按天统计")
	}

	now := time.Now()
	endAt := now
	if filter != nil && filter.EndAt != nil && strings.TrimSpace(*filter.EndAt) != "" {
		parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(*filter.EndAt))
		if err != nil {
			return reportRange{}, errorx.ErrBadRequest.WithMsg("结束时间格式错误")
		}
		endAt = parsed
	}

	startAt := endAt.AddDate(0, 0, -6)
	if filter != nil && filter.StartAt != nil && strings.TrimSpace(*filter.StartAt) != "" {
		parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(*filter.StartAt))
		if err != nil {
			return reportRange{}, errorx.ErrBadRequest.WithMsg("开始时间格式错误")
		}
		startAt = parsed
	}

	startDay := time.Date(startAt.Year(), startAt.Month(), startAt.Day(), 0, 0, 0, 0, startAt.Location())
	endDay := time.Date(endAt.Year(), endAt.Month(), endAt.Day(), 0, 0, 0, 0, endAt.Location())
	if endDay.Before(startDay) {
		return reportRange{}, errorx.ErrBadRequest.WithMsg("结束时间不能早于开始时间")
	}

	endNext := endDay.AddDate(0, 0, 1)
	return reportRange{
		startDay: startDay,
		endDay:   endDay,
		endNext:  endNext,
	}, nil
}

func (s *super) buildSuperCouponItems(ctx context.Context, list []*models.Coupon) ([]super_dto.SuperCouponItem, error) {
	if len(list) == 0 {
		return []super_dto.SuperCouponItem{}, nil
	}

	tenantIDs := make([]int64, 0, len(list))
	seen := make(map[int64]struct{}, len(list))
	for _, c := range list {
		if c == nil {
			continue
		}
		if _, ok := seen[c.TenantID]; ok {
			continue
		}
		seen[c.TenantID] = struct{}{}
		tenantIDs = append(tenantIDs, c.TenantID)
	}

	tenantMap := make(map[int64]*models.Tenant, len(tenantIDs))
	if len(tenantIDs) > 0 {
		tbl, q := models.TenantQuery.QueryContext(ctx)
		tenants, err := q.Where(tbl.ID.In(tenantIDs...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, t := range tenants {
			tenantMap[t.ID] = t
		}
	}

	items := make([]super_dto.SuperCouponItem, 0, len(list))
	for _, c := range list {
		if c == nil {
			continue
		}
		items = append(items, s.toSuperCouponItem(c, tenantMap[c.TenantID]))
	}
	return items, nil
}

func (s *super) toSuperCouponItem(c *models.Coupon, tenant *models.Tenant) super_dto.SuperCouponItem {
	status, statusDescription := s.resolveCouponStatus(c)
	item := super_dto.SuperCouponItem{
		ID:                c.ID,
		TenantID:          c.TenantID,
		Title:             c.Title,
		Description:       c.Description,
		Type:              c.Type,
		TypeDescription:   c.Type.Description(),
		Value:             c.Value,
		MinOrderAmount:    c.MinOrderAmount,
		MaxDiscount:       c.MaxDiscount,
		TotalQuantity:     c.TotalQuantity,
		UsedQuantity:      c.UsedQuantity,
		Status:            status,
		StatusDescription: statusDescription,
		CreatedAt:         s.formatTime(c.CreatedAt),
		UpdatedAt:         s.formatTime(c.UpdatedAt),
	}
	if tenant != nil {
		item.TenantCode = tenant.Code
		item.TenantName = tenant.Name
	}
	if !c.StartAt.IsZero() {
		item.StartAt = s.formatTime(c.StartAt)
	}
	if !c.EndAt.IsZero() {
		item.EndAt = s.formatTime(c.EndAt)
	}
	return item
}

func (s *super) resolveCouponStatus(c *models.Coupon) (string, string) {
	now := time.Now()
	if !c.EndAt.IsZero() && c.EndAt.Before(now) {
		return "expired", "已过期"
	}
	if !c.StartAt.IsZero() && c.StartAt.After(now) {
		return "upcoming", "未开始"
	}
	return "active", "生效中"
}

func (s *super) ApproveWithdrawal(ctx context.Context, operatorID, id int64) error {
	if operatorID == 0 {
		return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
	}
	o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}
	if o.Status != consts.OrderStatusCreated {
		return errorx.ErrStatusConflict.WithMsg("订单状态不正确")
	}

	// Mark as Paid (Assumes external transfer done)
	_, err = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).Updates(&models.Order{
		Status:    consts.OrderStatusPaid,
		PaidAt:    time.Now(),
		UpdatedAt: time.Now(),
	})
	if err == nil && Audit != nil {
		Audit.Log(ctx, operatorID, "approve_withdrawal", cast.ToString(id), "Approved withdrawal")
	}
	return err
}

func (s *super) userOwnedTenantCount(ctx context.Context, userIDs []int64) (map[int64]int64, error) {
	result := make(map[int64]int64, len(userIDs))
	if len(userIDs) == 0 {
		return result, nil
	}
	var rows []struct {
		UserID int64 `gorm:"column:user_id"`
		Count  int64 `gorm:"column:count"`
	}
	err := models.TenantQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.Tenant{}).
		Select("user_id, count(*) as count").
		Where("user_id IN ?", userIDs).
		Group("user_id").
		Scan(&rows).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	for _, row := range rows {
		result[row.UserID] = row.Count
	}
	return result, nil
}

func (s *super) userJoinedTenantCount(ctx context.Context, userIDs []int64) (map[int64]int64, error) {
	result := make(map[int64]int64, len(userIDs))
	if len(userIDs) == 0 {
		return result, nil
	}
	var rows []struct {
		UserID int64 `gorm:"column:user_id"`
		Count  int64 `gorm:"column:count"`
	}
	err := models.TenantUserQuery.WithContext(ctx).
		UnderlyingDB().
		Model(&models.TenantUser{}).
		Select("user_id, count(*) as count").
		Where("user_id IN ?", userIDs).
		Group("user_id").
		Scan(&rows).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	for _, row := range rows {
		result[row.UserID] = row.Count
	}
	return result, nil
}

func (s *super) userMapByTenantUsers(ctx context.Context, list []*models.TenantUser) (map[int64]*models.User, error) {
	userIDs := make([]int64, 0, len(list))
	seen := make(map[int64]struct{}, len(list))
	for _, tu := range list {
		if _, ok := seen[tu.UserID]; ok {
			continue
		}
		seen[tu.UserID] = struct{}{}
		userIDs = append(userIDs, tu.UserID)
	}
	userMap := make(map[int64]*models.User, len(userIDs))
	if len(userIDs) == 0 {
		return userMap, nil
	}
	tblUser, qUser := models.UserQuery.QueryContext(ctx)
	users, err := qUser.Where(tblUser.ID.In(userIDs...)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	for _, u := range users {
		userMap[u.ID] = u
	}
	return userMap, nil
}

func (s *super) toSuperTenantUserDTO(tu *models.TenantUser) *super_dto.TenantUser {
	if tu == nil {
		return nil
	}
	return &super_dto.TenantUser{
		ID:        tu.ID,
		TenantID:  tu.TenantID,
		UserID:    tu.UserID,
		Role:      tu.Role,
		Status:    tu.Status,
		CreatedAt: s.formatTime(tu.CreatedAt),
		UpdatedAt: s.formatTime(tu.UpdatedAt),
	}
}

func (s *super) tenantMapsForTenantUsers(ctx context.Context, list []*models.TenantUser) (map[int64]*models.Tenant, map[int64]*models.User, error) {
	tenantIDs := make([]int64, 0, len(list))
	seen := make(map[int64]struct{}, len(list))
	for _, tu := range list {
		if _, ok := seen[tu.TenantID]; ok {
			continue
		}
		seen[tu.TenantID] = struct{}{}
		tenantIDs = append(tenantIDs, tu.TenantID)
	}

	tenantMap := make(map[int64]*models.Tenant, len(tenantIDs))
	ownerMap := make(map[int64]*models.User, len(tenantIDs))
	if len(tenantIDs) == 0 {
		return tenantMap, ownerMap, nil
	}

	tblTenant, qTenant := models.TenantQuery.QueryContext(ctx)
	tenants, err := qTenant.Where(tblTenant.ID.In(tenantIDs...)).Find()
	if err != nil {
		return nil, nil, errorx.ErrDatabaseError.WithCause(err)
	}

	ownerIDs := make([]int64, 0, len(tenants))
	ownerSeen := make(map[int64]struct{}, len(tenants))
	for _, t := range tenants {
		tenantMap[t.ID] = t
		if _, ok := ownerSeen[t.UserID]; ok {
			continue
		}
		ownerSeen[t.UserID] = struct{}{}
		ownerIDs = append(ownerIDs, t.UserID)
	}

	userMap := make(map[int64]*models.User, len(ownerIDs))
	if len(ownerIDs) > 0 {
		tblUser, qUser := models.UserQuery.QueryContext(ctx)
		users, err := qUser.Where(tblUser.ID.In(ownerIDs...)).Find()
		if err != nil {
			return nil, nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, u := range users {
			userMap[u.ID] = u
		}
	}

	for tenantID, tenant := range tenantMap {
		if tenant == nil {
			continue
		}
		if owner := userMap[tenant.UserID]; owner != nil {
			ownerMap[tenantID] = owner
		}
	}

	return tenantMap, ownerMap, nil
}

func (s *super) formatTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}
	return t.Format(time.RFC3339)
}

func (s *super) RejectWithdrawal(ctx context.Context, operatorID, id int64, reason string) error {
	if operatorID == 0 {
		return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
	}
	err := models.Q.Transaction(func(tx *models.Query) error {
		o, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).First()
		if err != nil {
			return errorx.ErrRecordNotFound
		}
		if o.Status != consts.OrderStatusCreated {
			return errorx.ErrStatusConflict.WithMsg("订单状态不正确")
		}

		// Refund User Balance
		_, err = tx.User.WithContext(ctx).Where(tx.User.ID.Eq(o.UserID)).Update(tx.User.Balance, gorm.Expr("balance + ?", o.AmountPaid))
		if err != nil {
			return err
		}

		// Update Order
		_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).Updates(&models.Order{
			Status:       consts.OrderStatusFailed, // or Canceled
			RefundReason: reason,
			UpdatedAt:    time.Now(),
		})
		if err != nil {
			return err
		}

		// Create Ledger (Adjustment/Unfreeze)
		ledger := &models.TenantLedger{
			TenantID:       o.TenantID,
			UserID:         o.UserID,
			OrderID:        o.ID,
			Type:           consts.TenantLedgerTypeAdjustment,
			Amount:         o.AmountPaid,
			Remark:         "提现拒绝返还: " + reason,
			OperatorUserID: operatorID,
			IdempotencyKey: uuid.NewString(),
		}
		if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
			return err
		}

		return nil
	})

	if err == nil && Audit != nil {
		Audit.Log(ctx, operatorID, "reject_withdrawal", cast.ToString(id), "Rejected: "+reason)
	}
	return err
}
