package user

import (
	"context"
	"database/sql"
	"time"

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

const contentBlacklistEntryColumns = `
	b.user_id,
	CASE
	  WHEN u.current_display_user_id_kind <> 'default'
	   AND COALESCE(u.current_display_user_id_expires_at_ms, 0) > 0
	   AND u.current_display_user_id_expires_at_ms <= ?
	  THEN u.default_display_user_id
	  ELSE u.current_display_user_id
	END,
	u.default_display_user_id,
	COALESCE((
		SELECT pdi.pretty_id
		FROM pretty_display_user_id_leases pdl
		LEFT JOIN pretty_display_ids pdi
		  ON pdi.app_code = pdl.app_code
		 AND pdi.assigned_lease_id = pdl.lease_id
		 AND pdi.assigned_user_id = pdl.user_id
		WHERE pdl.app_code = u.app_code
		  AND pdl.user_id = u.user_id
		  AND pdl.status = 'active'
		  AND (pdl.expires_at_ms = 0 OR pdl.expires_at_ms > ?)
		ORDER BY pdl.starts_at_ms DESC
		LIMIT 1
	), ''),
	COALESCE((
		SELECT pdl.display_user_id
		FROM pretty_display_user_id_leases pdl
		WHERE pdl.app_code = u.app_code
		  AND pdl.user_id = u.user_id
		  AND pdl.status = 'active'
		  AND (pdl.expires_at_ms = 0 OR pdl.expires_at_ms > ?)
		ORDER BY pdl.starts_at_ms DESC
		LIMIT 1
	), ''),
	COALESCE(u.username, ''),
	COALESCE(u.avatar, ''),
	u.status,
	b.operator_admin_id,
	b.reason,
	b.created_at_ms,
	b.updated_at_ms`

// IsUserContentBlacklisted 使用租户和用户主键做单点判断。
// 该查询位于 GetUser 链路，必须保持 O(1)，不能为运行时裁决扫描黑名单列表。
func (r *Repository) IsUserContentBlacklisted(ctx context.Context, userID int64) (bool, error) {
	var blacklisted bool
	err := r.db.QueryRowContext(ctx, `
		SELECT EXISTS (
			SELECT 1
			FROM user_content_blacklist
			WHERE app_code = ? AND user_id = ? AND status = 'active'
		)`,
		appcode.FromContext(ctx), userID,
	).Scan(&blacklisted)
	return blacklisted, err
}

// ListUserContentBlacklist 只列出 active 当前态；用户展示资料实时关联 users，避免后台看到过期昵称或头像快照。
func (r *Repository) ListUserContentBlacklist(ctx context.Context, page int32, pageSize int32) ([]userdomain.ContentBlacklistEntry, int64, error) {
	appCode := appcode.FromContext(ctx)
	var total int64
	if err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM user_content_blacklist FORCE INDEX (idx_user_content_blacklist_status_time)
		WHERE app_code = ? AND status = 'active'`,
		appCode,
	).Scan(&total); err != nil {
		return nil, 0, err
	}

	nowMS := userserviceNowMillis()
	rows, err := r.db.QueryContext(ctx, `
		SELECT `+contentBlacklistEntryColumns+`
		FROM user_content_blacklist b FORCE INDEX (idx_user_content_blacklist_status_time)
		INNER JOIN users u ON u.user_id = b.user_id AND u.app_code = b.app_code
		WHERE b.app_code = ? AND b.status = 'active'
		ORDER BY b.created_at_ms DESC, b.user_id DESC
		LIMIT ? OFFSET ?`,
		nowMS, nowMS, nowMS, appCode, pageSize, int64(page-1)*int64(pageSize),
	)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items := make([]userdomain.ContentBlacklistEntry, 0, pageSize)
	for rows.Next() {
		item, err := scanContentBlacklistEntry(rows)
		if err != nil {
			return nil, 0, err
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

// AddUserContentBlacklist 以 (app_code,user_id) 保存一行当前态。
// 从 removed 恢复时重置加入时间；重复 active 添加只更新操作者、原因和更新时间。
func (r *Repository) AddUserContentBlacklist(ctx context.Context, command userservice.UserContentBlacklistCommand) (userdomain.ContentBlacklistEntry, error) {
	appCode := appcode.FromContext(ctx)
	if err := r.requireContentBlacklistUser(ctx, appCode, command.UserID); err != nil {
		return userdomain.ContentBlacklistEntry{}, err
	}
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO user_content_blacklist (
			app_code, user_id, status, operator_admin_id, reason, created_at_ms, updated_at_ms
		) VALUES (?, ?, 'active', ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE
			created_at_ms = IF(status = 'active', created_at_ms, VALUES(created_at_ms)),
			status = 'active',
			operator_admin_id = VALUES(operator_admin_id),
			reason = VALUES(reason),
			updated_at_ms = VALUES(updated_at_ms)`,
		appCode, command.UserID, command.OperatorAdminID, command.Reason, command.NowMs, command.NowMs,
	)
	if err != nil {
		return userdomain.ContentBlacklistEntry{}, err
	}
	return r.getUserContentBlacklistEntry(ctx, appCode, command.UserID, command.NowMs)
}

// RemoveUserContentBlacklist 软移除当前态，保留最后一次操作元数据供数据库排障。
func (r *Repository) RemoveUserContentBlacklist(ctx context.Context, command userservice.UserContentBlacklistCommand) (bool, error) {
	result, err := r.db.ExecContext(ctx, `
		UPDATE user_content_blacklist
		SET status = 'removed',
		    operator_admin_id = ?,
		    reason = ?,
		    updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND status = 'active'`,
		command.OperatorAdminID, command.Reason, command.NowMs, appcode.FromContext(ctx), command.UserID,
	)
	if err != nil {
		return false, err
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return false, err
	}
	return affected > 0, nil
}

func (r *Repository) requireContentBlacklistUser(ctx context.Context, appCode string, userID int64) error {
	var exists int
	err := r.db.QueryRowContext(ctx, `
		SELECT 1
		FROM users FORCE INDEX (PRIMARY)
		WHERE user_id = ? AND app_code = ?`,
		userID, appCode,
	).Scan(&exists)
	if err == sql.ErrNoRows {
		return xerr.New(xerr.NotFound, "user not found")
	}
	return err
}

func (r *Repository) getUserContentBlacklistEntry(ctx context.Context, appCode string, userID int64, nowMS int64) (userdomain.ContentBlacklistEntry, error) {
	row := r.db.QueryRowContext(ctx, `
		SELECT `+contentBlacklistEntryColumns+`
		FROM user_content_blacklist b
		INNER JOIN users u ON u.user_id = b.user_id AND u.app_code = b.app_code
		WHERE b.app_code = ? AND b.user_id = ? AND b.status = 'active'`,
		nowMS, nowMS, nowMS, appCode, userID,
	)
	item, err := scanContentBlacklistEntry(row)
	if err == sql.ErrNoRows {
		return userdomain.ContentBlacklistEntry{}, xerr.New(xerr.NotFound, "content blacklist entry not found")
	}
	return item, err
}

func scanContentBlacklistEntry(scanner interface {
	Scan(dest ...any) error
}) (userdomain.ContentBlacklistEntry, error) {
	var item userdomain.ContentBlacklistEntry
	var status string
	err := scanner.Scan(
		&item.UserID,
		&item.CurrentDisplayUserID,
		&item.DefaultDisplayUserID,
		&item.PrettyID,
		&item.PrettyDisplayUserID,
		&item.Username,
		&item.Avatar,
		&status,
		&item.OperatorAdminID,
		&item.Reason,
		&item.CreatedAtMs,
		&item.UpdatedAtMs,
	)
	item.UserStatus = userdomain.Status(status)
	return item, err
}

func userserviceNowMillis() int64 {
	// 列表只用该时间判断展示号租约，不产生持久事实；UTC epoch 不受进程本地时区影响。
	return time.Now().UTC().UnixMilli()
}
