<?php

declare(strict_types=1);

/**
 * 推薦技師：獎勵設定（JSON）與發放帳本（fate_reward_ledger）。
 *
 * 設定檔：fate/data/fate_masseur_rewards.json
 *  - feature_enabled  功能總開關
 *  - reward_create    建檔（推薦）一則給多少 N 幣
 *  - reward_review    文字評論一則給多少 N 幣
 *  - reward_like      點讚一次給多少 N 幣
 *  - reward_tag       技師標籤點選一次給多少 N 幣
 *  - tag_reward_max_per_masseur  同會員對同一技師可獲標籤獎勵次數上限（0＝不限制；不影響標籤點選）
 *  - tag_reward_max_masseurs_per_day  同會員每日可獲標籤獎勵的不同技師數上限（0＝不限制）
 *  - reviews_enabled  是否開放技師文字評論（關閉後前台不顯示、不可發表；既有評論仍可在後台管理）
 *  - store_reviews_enabled  是否開放店家文字評論（可獨立於推薦技師模組）
 *  - reviews_formal_only  （已廢止，互動改以信箱驗證為準）
 *  - tags_formal_only     技師標籤僅正式會員可見／可點（站方人員豁免）
 *  - profile_stats_formal_only  年齡／身材僅正式會員可見（站方人員豁免）
 *  - revert_on_delete 管理員刪除時是否扣回已發獎勵
 *  - review_paid_enabled           技師評論付費文開關
 *  - review_paid_max_price         單篇評論售價上限（N 幣）
 *  - review_paid_commission_pct    平台抽成百分比
 *  - review_paid_min_preview_chars 免費預讀字數下限
 *  - review_paid_max_preview_chars 免費預讀字數上限
 *  - review_paid_default_preview_chars 預設預讀字數
 *  - review_paid_min_body_chars    啟用付費所需正文最少字數
 *  - profile_stats_enabled         技師年齡／三圍／罩杯欄位（可選無資料）
 *
 * 帳本以 (action, ref_id, member_id) 唯一，避免重複發放；刪除時可標記 reverted 並扣回。
 */

require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_ncoin_credit.php';

function fate_masseur_rewards_path(): string
{
    return dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'fate_masseur_rewards.json';
}

/** @return array{feature_enabled:bool,reviews_enabled:bool,reviews_formal_only:bool,tags_formal_only:bool,reward_create:int,reward_review:int,reward_like:int,reward_tag:int,review_per_week:int,revert_on_delete:bool} */
function fate_masseur_rewards_default(): array
{
    return [
        'feature_enabled' => true,
        'reviews_enabled' => true,
        'store_reviews_enabled' => true,
        'reward_create' => 10,
        'reward_review' => 5,
        'reward_store_review' => 5,
        'reward_like' => 1,
        'reward_tag' => 1,
        'tag_reward_max_per_masseur' => 5,
        'tag_reward_max_masseurs_per_day' => 10,
        'reward_crowd_store_closed' => 1,
        'reward_crowd_store_renamed' => 1,
        'reward_crowd_masseur_left_store' => 1,
        'reward_store_recommend' => 10,
        'reward_store_mobile' => 10,
        'review_per_week' => 1,
        'weekly_recommend_enabled' => true,
        'weekly_recommend_max_per_week' => 10,
        'weekly_rank_limit' => 10,
        'reward_weekly_recommend' => 0,
        'revert_on_delete' => true,
        'reviews_formal_only' => true,
        'tags_formal_only' => false,
        'profile_stats_formal_only' => false,
        'default_avatar_female' => '',
        'default_avatar_male' => '',
        'default_avatar_other' => '',
        'review_paid_enabled' => false,
        'review_paid_max_price' => 500,
        'review_paid_commission_pct' => 20,
        'review_paid_min_preview_chars' => 50,
        'review_paid_max_preview_chars' => 2000,
        'review_paid_default_preview_chars' => 200,
        'review_paid_min_body_chars' => 500,
        'profile_stats_enabled' => false,
    ];
}

/** 推薦技師：年齡／三圍／罩杯進階欄位是否啟用。 */
function fate_masseur_profile_stats_enabled(): bool
{
    $s = fate_masseur_rewards_settings();

    return $s['feature_enabled'] && $s['profile_stats_enabled'];
}

/** 推薦技師總開關開啟時，是否允許前台文字評論。 */
function fate_masseur_reviews_enabled(): bool
{
    $s = fate_masseur_rewards_settings();

    return $s['feature_enabled'] && $s['reviews_enabled'];
}

function fate_masseur_reviews_formal_only(): bool
{
    return false;
}

function fate_masseur_tags_formal_only(): bool
{
    $s = fate_masseur_rewards_settings();

    return $s['feature_enabled'] && $s['tags_formal_only'];
}

function fate_masseur_profile_stats_formal_only(): bool
{
    $s = fate_masseur_rewards_settings();

    return $s['feature_enabled'] && $s['profile_stats_enabled'] && $s['profile_stats_formal_only'];
}

/** 推薦技師總開關（建檔／列表／互動）。 */
function fate_masseur_feature_enabled(): bool
{
    return !empty(fate_masseur_rewards_settings()['feature_enabled']);
}

/** 本週推薦／週推榜（須推薦技師總開關一併啟用）。 */
function fate_masseur_weekly_recommend_enabled(): bool
{
    $s = fate_masseur_rewards_settings();

    return !empty($s['feature_enabled']) && !empty($s['weekly_recommend_enabled']);
}

/** @param array<string,mixed> $settings */
function fate_masseur_rewards_normalize(array $settings): array
{
    if (empty($settings['feature_enabled'])) {
        $settings['weekly_recommend_enabled'] = false;
    }

    return $settings;
}

/** @return array{feature_enabled:bool,reviews_enabled:bool,reviews_formal_only:bool,tags_formal_only:bool,reward_create:int,reward_review:int,reward_like:int,reward_tag:int,review_per_week:int,revert_on_delete:bool} */
function fate_masseur_rewards_settings(): array
{
    $d = fate_masseur_rewards_default();
    $p = fate_masseur_rewards_path();
    if (!is_file($p)) {
        return fate_masseur_rewards_normalize($d);
    }
    $raw = (string) file_get_contents($p);
    $j = json_decode($raw, true);
    if (!is_array($j)) {
        return fate_masseur_rewards_normalize($d);
    }

    return fate_masseur_rewards_normalize([
        'feature_enabled' => array_key_exists('feature_enabled', $j) ? (bool) $j['feature_enabled'] : $d['feature_enabled'],
        'reviews_enabled' => array_key_exists('reviews_enabled', $j) ? (bool) $j['reviews_enabled'] : $d['reviews_enabled'],
        'store_reviews_enabled' => array_key_exists('store_reviews_enabled', $j) ? (bool) $j['store_reviews_enabled'] : $d['store_reviews_enabled'],
        'reviews_formal_only' => array_key_exists('reviews_formal_only', $j) ? (bool) $j['reviews_formal_only'] : $d['reviews_formal_only'],
        'tags_formal_only' => array_key_exists('tags_formal_only', $j) ? (bool) $j['tags_formal_only'] : $d['tags_formal_only'],
        'profile_stats_formal_only' => array_key_exists('profile_stats_formal_only', $j) ? (bool) $j['profile_stats_formal_only'] : $d['profile_stats_formal_only'],
        'reward_create' => isset($j['reward_create']) ? max(0, (int) $j['reward_create']) : $d['reward_create'],
        'reward_review' => isset($j['reward_review']) ? max(0, (int) $j['reward_review']) : $d['reward_review'],
        'reward_store_review' => isset($j['reward_store_review']) ? max(0, (int) $j['reward_store_review']) : $d['reward_store_review'],
        'reward_like' => isset($j['reward_like']) ? max(0, (int) $j['reward_like']) : $d['reward_like'],
        'reward_tag' => isset($j['reward_tag']) ? max(0, (int) $j['reward_tag']) : $d['reward_tag'],
        'tag_reward_max_per_masseur' => isset($j['tag_reward_max_per_masseur'])
            ? max(0, min(50, (int) $j['tag_reward_max_per_masseur']))
            : $d['tag_reward_max_per_masseur'],
        'tag_reward_max_masseurs_per_day' => isset($j['tag_reward_max_masseurs_per_day'])
            ? max(0, min(100, (int) $j['tag_reward_max_masseurs_per_day']))
            : $d['tag_reward_max_masseurs_per_day'],
        'reward_crowd_store_closed' => isset($j['reward_crowd_store_closed']) ? max(0, (int) $j['reward_crowd_store_closed']) : $d['reward_crowd_store_closed'],
        'reward_crowd_store_renamed' => isset($j['reward_crowd_store_renamed']) ? max(0, (int) $j['reward_crowd_store_renamed']) : $d['reward_crowd_store_renamed'],
        'reward_crowd_masseur_left_store' => isset($j['reward_crowd_masseur_left_store']) ? max(0, (int) $j['reward_crowd_masseur_left_store']) : $d['reward_crowd_masseur_left_store'],
        'reward_store_recommend' => isset($j['reward_store_recommend']) ? max(0, (int) $j['reward_store_recommend']) : $d['reward_store_recommend'],
        'reward_store_mobile' => isset($j['reward_store_mobile']) ? max(0, (int) $j['reward_store_mobile']) : $d['reward_store_mobile'],
        'review_per_week' => isset($j['review_per_week']) ? max(1, min(50, (int) $j['review_per_week'])) : $d['review_per_week'],
        'weekly_recommend_enabled' => array_key_exists('weekly_recommend_enabled', $j) ? (bool) $j['weekly_recommend_enabled'] : $d['weekly_recommend_enabled'],
        'weekly_recommend_max_per_week' => isset($j['weekly_recommend_max_per_week'])
            ? max(1, min(50, (int) $j['weekly_recommend_max_per_week']))
            : $d['weekly_recommend_max_per_week'],
        'weekly_rank_limit' => isset($j['weekly_rank_limit'])
            ? max(1, min(50, (int) $j['weekly_rank_limit']))
            : $d['weekly_rank_limit'],
        'reward_weekly_recommend' => isset($j['reward_weekly_recommend']) ? max(0, (int) $j['reward_weekly_recommend']) : $d['reward_weekly_recommend'],
        'revert_on_delete' => array_key_exists('revert_on_delete', $j) ? (bool) $j['revert_on_delete'] : $d['revert_on_delete'],
        'default_avatar_female' => fate_masseur_sanitize_avatar_rel(isset($j['default_avatar_female']) ? (string) $j['default_avatar_female'] : ''),
        'default_avatar_male' => fate_masseur_sanitize_avatar_rel(isset($j['default_avatar_male']) ? (string) $j['default_avatar_male'] : ''),
        'default_avatar_other' => fate_masseur_sanitize_avatar_rel(isset($j['default_avatar_other']) ? (string) $j['default_avatar_other'] : ''),
        'review_paid_enabled' => array_key_exists('review_paid_enabled', $j) ? (bool) $j['review_paid_enabled'] : $d['review_paid_enabled'],
        'review_paid_max_price' => isset($j['review_paid_max_price']) ? max(1, min(100000, (int) $j['review_paid_max_price'])) : $d['review_paid_max_price'],
        'review_paid_commission_pct' => isset($j['review_paid_commission_pct']) ? max(0, min(100, (int) $j['review_paid_commission_pct'])) : $d['review_paid_commission_pct'],
        'review_paid_min_preview_chars' => isset($j['review_paid_min_preview_chars']) ? max(0, min(5000, (int) $j['review_paid_min_preview_chars'])) : $d['review_paid_min_preview_chars'],
        'review_paid_max_preview_chars' => isset($j['review_paid_max_preview_chars']) ? max(50, min(10000, (int) $j['review_paid_max_preview_chars'])) : $d['review_paid_max_preview_chars'],
        'review_paid_default_preview_chars' => isset($j['review_paid_default_preview_chars']) ? max(0, min(5000, (int) $j['review_paid_default_preview_chars'])) : $d['review_paid_default_preview_chars'],
        'review_paid_min_body_chars' => isset($j['review_paid_min_body_chars']) ? max(0, min(50000, (int) $j['review_paid_min_body_chars'])) : $d['review_paid_min_body_chars'],
        'profile_stats_enabled' => array_key_exists('profile_stats_enabled', $j) ? (bool) $j['profile_stats_enabled'] : $d['profile_stats_enabled'],
    ]);
}

function fate_masseur_sanitize_avatar_rel(string $raw): string
{
    $r = str_replace('\\', '/', trim($raw));
    if ($r === '' || str_contains($r, '..') || !str_starts_with($r, 'uploads/masseurs/')) {
        return '';
    }

    return $r;
}

/**
 * @param array<string,mixed> $patch
 * @return array{ok:bool,error:?string}
 */
function fate_masseur_rewards_save(array $patch): array
{
    $cur = fate_masseur_rewards_settings();
    foreach (['feature_enabled', 'reviews_enabled', 'store_reviews_enabled', 'reviews_formal_only', 'tags_formal_only', 'profile_stats_formal_only', 'revert_on_delete', 'review_paid_enabled', 'profile_stats_enabled', 'weekly_recommend_enabled'] as $bk) {
        if (array_key_exists($bk, $patch)) {
            $cur[$bk] = (bool) $patch[$bk];
        }
    }
    foreach (['reward_create', 'reward_review', 'reward_store_review', 'reward_like', 'reward_tag', 'reward_crowd_store_closed', 'reward_crowd_store_renamed', 'reward_crowd_masseur_left_store', 'reward_store_recommend', 'reward_store_mobile', 'reward_weekly_recommend'] as $nk) {
        if (array_key_exists($nk, $patch)) {
            $cur[$nk] = max(0, (int) $patch[$nk]);
        }
    }
    if (array_key_exists('review_per_week', $patch)) {
        $cur['review_per_week'] = max(1, min(50, (int) $patch['review_per_week']));
    }
    if (array_key_exists('weekly_recommend_max_per_week', $patch)) {
        $cur['weekly_recommend_max_per_week'] = max(1, min(50, (int) $patch['weekly_recommend_max_per_week']));
    }
    if (array_key_exists('weekly_rank_limit', $patch)) {
        $cur['weekly_rank_limit'] = max(1, min(50, (int) $patch['weekly_rank_limit']));
    }
    if (array_key_exists('tag_reward_max_per_masseur', $patch)) {
        $cur['tag_reward_max_per_masseur'] = max(0, min(50, (int) $patch['tag_reward_max_per_masseur']));
    }
    if (array_key_exists('tag_reward_max_masseurs_per_day', $patch)) {
        $cur['tag_reward_max_masseurs_per_day'] = max(0, min(100, (int) $patch['tag_reward_max_masseurs_per_day']));
    }
    foreach (['default_avatar_female', 'default_avatar_male', 'default_avatar_other'] as $ak) {
        if (array_key_exists($ak, $patch)) {
            $cur[$ak] = fate_masseur_sanitize_avatar_rel(is_string($patch[$ak]) ? $patch[$ak] : '');
        }
    }
    if (array_key_exists('review_paid_max_price', $patch)) {
        $cur['review_paid_max_price'] = max(1, min(100000, (int) $patch['review_paid_max_price']));
    }
    if (array_key_exists('review_paid_commission_pct', $patch)) {
        $cur['review_paid_commission_pct'] = max(0, min(100, (int) $patch['review_paid_commission_pct']));
    }
    if (array_key_exists('review_paid_min_preview_chars', $patch)) {
        $cur['review_paid_min_preview_chars'] = max(0, min(5000, (int) $patch['review_paid_min_preview_chars']));
    }
    if (array_key_exists('review_paid_max_preview_chars', $patch)) {
        $cur['review_paid_max_preview_chars'] = max(50, min(10000, (int) $patch['review_paid_max_preview_chars']));
    }
    if (array_key_exists('review_paid_default_preview_chars', $patch)) {
        $cur['review_paid_default_preview_chars'] = max(0, min(5000, (int) $patch['review_paid_default_preview_chars']));
    }
    if (array_key_exists('review_paid_min_body_chars', $patch)) {
        $cur['review_paid_min_body_chars'] = max(0, min(50000, (int) $patch['review_paid_min_body_chars']));
    }
    $cur = fate_masseur_rewards_normalize($cur);

    $dir = dirname(fate_masseur_rewards_path());
    if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) {
        return ['ok' => false, 'error' => '無法建立 data 目錄。'];
    }
    $payload = json_encode($cur, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    if ($payload === false) {
        return ['ok' => false, 'error' => '設定序列化失敗。'];
    }
    if (file_put_contents(fate_masseur_rewards_path(), $payload . "\n", LOCK_EX) === false) {
        return ['ok' => false, 'error' => '無法寫入設定檔（請確認 fate/data 可寫入）。'];
    }

    return ['ok' => true, 'error' => null];
}

function fate_reward_ledger_ddl(): string
{
    return <<<'SQL'
CREATE TABLE IF NOT EXISTS `fate_reward_ledger` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `member_id` INT UNSIGNED NOT NULL,
  `action` VARCHAR(32) NOT NULL,
  `ref_id` INT UNSIGNED NOT NULL DEFAULT 0,
  `amount` INT NOT NULL DEFAULT 0,
  `applied_local` TINYINT NOT NULL DEFAULT 0,
  `applied_spa` TINYINT NOT NULL DEFAULT 0,
  `reverted` TINYINT NOT NULL DEFAULT 0,
  `reverted_at` DATETIME NULL DEFAULT NULL,
  `note` VARCHAR(255) NOT NULL DEFAULT '',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_action_ref_member` (`action`, `ref_id`, `member_id`),
  KEY `idx_member` (`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL;
}

function fate_reward_ledger_ensure_table(PDO $pdo): void
{
    static $done = false;
    if ($done) {
        return;
    }
    try {
        $pdo->exec(fate_reward_ledger_ddl());
        $done = true;
    } catch (Throwable) {
        /* 無建表權限時略過 */
    }
}

/**
 * 發放獎勵（具去重）：首次記錄 (action, ref_id, member_id) 時才實際加幣。
 *
 * @param 'masseur_create'|'review'|'vote_like'|'tag_pick'|'weekly_recommend'|'crowd_store_closed'|'crowd_store_renamed'|'crowd_masseur_left_store' $action
 * @return int 實際發放金額（0 表示未發放：金額為 0、重複、或功能關閉）
 */
function fate_masseur_reward_grant(PDO $pdo, int $memberId, string $action, int $refId, string $username = '', string $note = ''): int
{
    if ($memberId <= 0) {
        return 0;
    }
    $cfg = fate_masseur_rewards_settings();
    $isCrowd = str_starts_with($action, 'crowd_');
    if (!$isCrowd && !$cfg['feature_enabled']) {
        return 0;
    }
    $amount = match ($action) {
        'masseur_create' => $cfg['reward_create'],
        'review' => $cfg['reward_review'],
        'vote_like' => $cfg['reward_like'],
        'tag_pick' => $cfg['reward_tag'],
        'weekly_recommend' => $cfg['reward_weekly_recommend'],
        'crowd_store_closed' => $cfg['reward_crowd_store_closed'],
        'crowd_store_renamed' => $cfg['reward_crowd_store_renamed'],
        'crowd_masseur_left_store' => $cfg['reward_crowd_masseur_left_store'],
        default => 0,
    };
    if ($amount <= 0) {
        return 0;
    }

    fate_reward_ledger_ensure_table($pdo);

    try {
        $st = $pdo->prepare(
            'INSERT IGNORE INTO `fate_reward_ledger` (`member_id`, `action`, `ref_id`, `amount`, `note`) VALUES (?, ?, ?, ?, ?)'
        );
        $st->execute([$memberId, $action, $refId, $amount, mb_substr($note, 0, 255)]);
        if ($st->rowCount() < 1) {
            return 0; // 已存在 → 不重複發放
        }
        $ledgerId = (int) $pdo->lastInsertId();
    } catch (Throwable) {
        return 0;
    }

    $applied = fate_ncoin_credit_member($pdo, $memberId, $amount, $username);
    try {
        $pdo->prepare('UPDATE `fate_reward_ledger` SET `applied_local` = ?, `applied_spa` = ? WHERE `id` = ?')
            ->execute([$applied['local'] ? 1 : 0, $applied['spa'] ? 1 : 0, $ledgerId]);
    } catch (Throwable) {
        /* 略過更新失敗 */
    }

    return $amount;
}

/**
 * 扣回某筆獎勵（管理員刪除時）：依設定決定是否實際扣幣。
 *
 * @param 'masseur_create'|'review'|'vote_like'|'tag_pick' $action
 * @return int 扣回金額（0＝無需扣回或設定關閉）
 */
function fate_masseur_reward_revert(PDO $pdo, string $action, int $refId, string $username = ''): int
{
    $cfg = fate_masseur_rewards_settings();
    if (!$cfg['revert_on_delete']) {
        return 0;
    }
    fate_reward_ledger_ensure_table($pdo);

    try {
        $st = $pdo->prepare(
            'SELECT `id`, `member_id`, `amount` FROM `fate_reward_ledger` WHERE `action` = ? AND `ref_id` = ? AND `reverted` = 0'
        );
        $st->execute([$action, $refId]);
        $rows = $st->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable) {
        return 0;
    }

    $total = 0;
    foreach ($rows as $r) {
        $mid = (int) ($r['member_id'] ?? 0);
        $amt = (int) ($r['amount'] ?? 0);
        if ($mid <= 0 || $amt <= 0) {
            continue;
        }
        fate_ncoin_debit_member($pdo, $mid, $amt, $username);
        try {
            $pdo->prepare('UPDATE `fate_reward_ledger` SET `reverted` = 1, `reverted_at` = NOW() WHERE `id` = ?')
                ->execute([(int) $r['id']]);
            $total += $amt;
        } catch (Throwable) {
            /* 略過 */
        }
    }

    return $total;
}

/**
 * 推薦店家核准後發放 N 幣（依 listing_kind 分開設定）。
 *
 * @return int 實際發放金額
 */
function fate_store_recommend_reward_grant(PDO $pdo, int $memberId, int $storeId, string $listingKind, string $username = ''): int
{
    if ($memberId <= 0 || $storeId <= 0) {
        return 0;
    }
    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_listing.php';
    $kind = fate_store_listing_normalize_kind($listingKind);
    $cfg = fate_masseur_rewards_settings();
    $action = $kind === FATE_STORE_LISTING_MOBILE ? 'store_recommend_mobile' : 'store_recommend_fixed';
    $amount = $kind === FATE_STORE_LISTING_MOBILE
        ? (int) ($cfg['reward_store_mobile'] ?? 0)
        : (int) ($cfg['reward_store_recommend'] ?? 0);
    if ($amount <= 0) {
        return 0;
    }

    fate_reward_ledger_ensure_table($pdo);
    $note = ($kind === FATE_STORE_LISTING_MOBILE ? '非定點服務 #' : '定點店家 #') . $storeId;

    try {
        $st = $pdo->prepare(
            'INSERT IGNORE INTO `fate_reward_ledger` (`member_id`, `action`, `ref_id`, `amount`, `note`) VALUES (?, ?, ?, ?, ?)'
        );
        $st->execute([$memberId, $action, $storeId, $amount, mb_substr($note, 0, 255)]);
        if ($st->rowCount() < 1) {
            return 0;
        }
        $ledgerId = (int) $pdo->lastInsertId();
    } catch (Throwable) {
        return 0;
    }

    $applied = fate_ncoin_credit_member($pdo, $memberId, $amount, $username);
    try {
        $pdo->prepare('UPDATE `fate_reward_ledger` SET `applied_local` = ?, `applied_spa` = ? WHERE `id` = ?')
            ->execute([$applied['local'] ? 1 : 0, $applied['spa'] ? 1 : 0, $ledgerId]);
    } catch (Throwable) {
    }

    return $amount;
}

/** 刪除已核准推薦店家時扣回獎勵（若設定啟用）。 */
function fate_store_recommend_reward_revert(PDO $pdo, int $storeId, string $listingKind, string $username = ''): int
{
    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_listing.php';
    $kind = fate_store_listing_normalize_kind($listingKind);
    $action = $kind === FATE_STORE_LISTING_MOBILE ? 'store_recommend_mobile' : 'store_recommend_fixed';

    return fate_masseur_reward_revert($pdo, $action, $storeId, $username);
}
