package user

import (
	"context"
	"fmt"
	"strings"

	"hyapp/pkg/appcode"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

// BatchGetUserAdminProfiles 聚合 user-service 同库内的用户、登录、角色和封禁事实。
// 该查询不读取 wallet/activity 等 owner 数据；admin-server 会分别批量读取并组装最终 DTO。
func (r *Repository) BatchGetUserAdminProfiles(ctx context.Context, userIDs []int64, _ int64) (map[int64]userdomain.AdminProfile, error) {
	users, err := r.BatchGetUsers(ctx, userIDs)
	if err != nil {
		return nil, err
	}
	profiles := make(map[int64]userdomain.AdminProfile, len(users))
	for userID, item := range users {
		profiles[userID] = userdomain.AdminProfile{
			User:           item,
			BirthDate:      item.BirthDate,
			RegisterDevice: item.RegisterDevice,
			Roles:          make([]string, 0, 6),
		}
	}
	if len(profiles) == 0 {
		return profiles, nil
	}

	placeholders, idArgs := adminProfileIDArgs(userIDs)
	if err := r.hydrateAdminLastSuccessLogin(ctx, profiles, placeholders, idArgs); err != nil {
		return nil, err
	}
	if err := r.hydrateAdminRoles(ctx, profiles, placeholders, idArgs); err != nil {
		return nil, err
	}
	if err := r.hydrateAdminLastOperations(ctx, profiles, placeholders, idArgs); err != nil {
		return nil, err
	}
	if err := r.hydrateAdminBanSummaries(ctx, profiles, placeholders, idArgs); err != nil {
		return nil, err
	}
	return profiles, nil
}

func (r *Repository) hydrateAdminLastSuccessLogin(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error {
	args := append([]any{appcode.FromContext(ctx)}, idArgs...)
	rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
		SELECT user_id, MAX(created_at_ms)
		FROM login_audit
		WHERE app_code = ? AND result = 'success' AND user_id IN (%s)
		GROUP BY user_id`, placeholders), args...)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var userID, loggedInAtMs int64
		if err := rows.Scan(&userID, &loggedInAtMs); err != nil {
			return err
		}
		profile, ok := profiles[userID]
		if ok {
			profile.LastSuccessLoginAtMs = loggedInAtMs
			profiles[userID] = profile
		}
	}
	return rows.Err()
}

func (r *Repository) hydrateAdminRoles(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error {
	args := append([]any{appcode.FromContext(ctx)}, idArgs...)
	rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
		SELECT u.user_id,
		       EXISTS(SELECT 1 FROM host_profiles h WHERE h.app_code = u.app_code AND h.user_id = u.user_id AND h.status = 'active'),
		       EXISTS(SELECT 1 FROM agencies a WHERE a.app_code = u.app_code AND a.active_owner_user_id = u.user_id),
		       EXISTS(SELECT 1 FROM bd_profiles b WHERE b.app_code = u.app_code AND b.user_id = u.user_id AND b.status = 'active'),
		       EXISTS(SELECT 1 FROM bd_leader_profiles bl WHERE bl.app_code = u.app_code AND bl.user_id = u.user_id AND bl.status = 'active'),
		       EXISTS(SELECT 1 FROM coin_seller_profiles c WHERE c.app_code = u.app_code AND c.user_id = u.user_id AND c.status = 'active'),
		       EXISTS(SELECT 1 FROM manager_profiles m WHERE m.app_code = u.app_code AND m.user_id = u.user_id AND m.status = 'active')
		FROM users u
		WHERE u.app_code = ? AND u.user_id IN (%s)`, placeholders), args...)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var userID int64
		var host, agency, bd, bdLeader, coinSeller, manager bool
		if err := rows.Scan(&userID, &host, &agency, &bd, &bdLeader, &coinSeller, &manager); err != nil {
			return err
		}
		profile, ok := profiles[userID]
		if !ok {
			continue
		}
		// 固定顺序让 CSV、列表和详情在相同事实下始终产生一致展示，避免 map/set 顺序漂移。
		profile.Roles = appendAdminRole(profile.Roles, host, userdomain.AdminRoleHost)
		profile.Roles = appendAdminRole(profile.Roles, agency, userdomain.AdminRoleAgency)
		profile.Roles = appendAdminRole(profile.Roles, bd, userdomain.AdminRoleBD)
		profile.Roles = appendAdminRole(profile.Roles, bdLeader, userdomain.AdminRoleBDLeader)
		profile.Roles = appendAdminRole(profile.Roles, coinSeller, userdomain.AdminRoleCoinSeller)
		profile.Roles = appendAdminRole(profile.Roles, manager, userdomain.AdminRoleManager)
		profiles[userID] = profile
	}
	return rows.Err()
}

func (r *Repository) hydrateAdminLastOperations(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error {
	args := make([]any, 0, len(idArgs)+2)
	args = append(args, appcode.FromContext(ctx))
	args = append(args, idArgs...)
	args = append(args, appcode.FromContext(ctx))
	rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
		SELECT log.target_user_id, log.operator_type, log.operator_user_id, log.reason, log.created_at_ms
		FROM user_status_change_logs log
		INNER JOIN (
			SELECT target_user_id, MAX(id) AS latest_id
			FROM user_status_change_logs
			WHERE app_code = ? AND target_user_id IN (%s)
			GROUP BY target_user_id
		) latest ON latest.latest_id = log.id
		WHERE log.app_code = ?`, placeholders), args...)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var userID, operatorUserID, operatedAtMs int64
		var operatorType, reason string
		if err := rows.Scan(&userID, &operatorType, &operatorUserID, &reason, &operatedAtMs); err != nil {
			return err
		}
		profile, ok := profiles[userID]
		if ok {
			profile.LastOperatorType = operatorType
			profile.LastOperatorUserID = operatorUserID
			profile.LastOperationReason = reason
			profile.LastOperationAtMs = operatedAtMs
			profiles[userID] = profile
		}
	}
	return rows.Err()
}

func (r *Repository) hydrateAdminBanSummaries(ctx context.Context, profiles map[int64]userdomain.AdminProfile, placeholders string, idArgs []any) error {
	args := make([]any, 0, len(idArgs)*2+2)
	args = append(args, appcode.FromContext(ctx))
	args = append(args, idArgs...)
	args = append(args, appcode.FromContext(ctx))
	args = append(args, idArgs...)
	rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
		SELECT 'admin', ban_id, target_user_id, expires_at_ms, operator_admin_id, reason, created_at_ms
		FROM admin_user_bans
		WHERE app_code = ? AND status = 'active' AND target_user_id IN (%s)
		UNION ALL
		SELECT 'manager', block_id, target_user_id, blocked_until_ms, manager_user_id, reason, created_at_ms
		FROM manager_user_blocks
		WHERE app_code = ? AND status = 'active' AND target_user_id IN (%s)`, placeholders, placeholders), args...)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var source, banID, reason string
		var targetUserID, expiresAtMs, operatorUserID, createdAtMs int64
		if err := rows.Scan(&source, &banID, &targetUserID, &expiresAtMs, &operatorUserID, &reason, &createdAtMs); err != nil {
			return err
		}
		profile, ok := profiles[targetUserID]
		if !ok {
			continue
		}
		profile.Ban = mergeActiveBan(profile.Ban, userdomain.ActiveBanSummary{
			Active:         true,
			Source:         source,
			BanID:          banID,
			Permanent:      expiresAtMs == 0,
			ExpiresAtMs:    expiresAtMs,
			UserStatus:     profile.User.Status,
			OperatorType:   source,
			OperatorUserID: operatorUserID,
			Reason:         reason,
			CreatedAtMs:    createdAtMs,
			ActiveBanCount: 1,
		})
		profiles[targetUserID] = profile
	}
	if err := rows.Err(); err != nil {
		return err
	}

	for userID, profile := range profiles {
		if profile.Ban.Active || (profile.User.Status != userdomain.StatusBanned && profile.User.Status != userdomain.StatusDisabled) {
			continue
		}
		// 历史 banned/disabled 没有新事实表记录时一律视为永久；cron 绝不能仅凭时间猜测并恢复这些账号。
		profile.Ban = userdomain.ActiveBanSummary{
			Active:         true,
			Source:         userdomain.BanSourceLegacy,
			Permanent:      true,
			UserStatus:     profile.User.Status,
			OperatorType:   profile.LastOperatorType,
			OperatorUserID: profile.LastOperatorUserID,
			Reason:         profile.LastOperationReason,
			CreatedAtMs:    profile.LastOperationAtMs,
			ActiveBanCount: 1,
		}
		profiles[userID] = profile
	}
	return nil
}

func adminProfileIDArgs(userIDs []int64) (string, []any) {
	placeholders := make([]string, 0, len(userIDs))
	args := make([]any, 0, len(userIDs))
	for _, userID := range userIDs {
		placeholders = append(placeholders, "?")
		args = append(args, userID)
	}
	return strings.Join(placeholders, ","), args
}

func appendAdminRole(roles []string, active bool, role string) []string {
	if active {
		return append(roles, role)
	}
	return roles
}

func mergeActiveBan(current userdomain.ActiveBanSummary, candidate userdomain.ActiveBanSummary) userdomain.ActiveBanSummary {
	if !current.Active {
		return candidate
	}
	current.ActiveBanCount += candidate.ActiveBanCount
	// 任一永久封禁都会让聚合视图永久；多个永久事实取最新一条作为可操作 ban_id 和审计展示。
	if candidate.Permanent && (!current.Permanent || candidate.CreatedAtMs > current.CreatedAtMs) {
		candidate.ActiveBanCount = current.ActiveBanCount
		return candidate
	}
	if current.Permanent {
		return current
	}
	// 全部是限时事实时，账号只有在最晚一条也到期后才能恢复，因此摘要展示最晚截止时间。
	if candidate.ExpiresAtMs > current.ExpiresAtMs || (candidate.ExpiresAtMs == current.ExpiresAtMs && candidate.CreatedAtMs > current.CreatedAtMs) {
		candidate.ActiveBanCount = current.ActiveBanCount
		return candidate
	}
	return current
}
