<?php

declare(strict_types=1);

/**
 * 技師評論付費文：預讀、N 幣購買、抽成。
 */

require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_rich_body.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_masseur_rewards.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_ncoin_credit.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_account.php';

function fate_masseur_review_paid_ensure_schema(PDO $pdo): void
{
    static $done = false;
    if ($done) {
        return;
    }
    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_masseurs.php';
    fate_masseur_ensure_tables($pdo);
    try {
        $cols = [];
        foreach ($pdo->query('SHOW COLUMNS FROM `fate_masseur_reviews`')->fetchAll(PDO::FETCH_ASSOC) as $c) {
            $cols[(string) $c['Field']] = true;
        }
        if (!isset($cols['is_paid'])) {
            $pdo->exec('ALTER TABLE `fate_masseur_reviews` ADD COLUMN `is_paid` TINYINT NOT NULL DEFAULT 0 AFTER `body`');
        }
        if (!isset($cols['paid_price'])) {
            $pdo->exec('ALTER TABLE `fate_masseur_reviews` ADD COLUMN `paid_price` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `is_paid`');
        }
        if (!isset($cols['paid_preview_chars'])) {
            $pdo->exec('ALTER TABLE `fate_masseur_reviews` ADD COLUMN `paid_preview_chars` INT UNSIGNED NOT NULL DEFAULT 200 AFTER `paid_price`');
        }
    } catch (Throwable) {
        /* 無 ALTER 權限時略過 */
    }

    try {
        $pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `fate_masseur_review_purchases` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `review_id` INT UNSIGNED NOT NULL,
  `buyer_member_id` INT UNSIGNED NOT NULL,
  `price_paid` INT UNSIGNED NOT NULL DEFAULT 0,
  `author_payout` INT UNSIGNED NOT NULL DEFAULT 0,
  `platform_fee` INT UNSIGNED NOT NULL DEFAULT 0,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_review_buyer` (`review_id`, `buyer_member_id`),
  KEY `idx_review` (`review_id`),
  KEY `idx_buyer` (`buyer_member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
    } catch (Throwable) {
        /* 略過 */
    }

    $done = true;
}

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

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

/** @return array<string,int|bool> */
function fate_masseur_review_paid_config(): array
{
    $s = fate_masseur_rewards_settings();

    return [
        'enabled' => fate_masseur_review_paid_enabled(),
        'max_price' => max(1, (int) ($s['review_paid_max_price'] ?? 500)),
        'commission_pct' => max(0, min(100, (int) ($s['review_paid_commission_pct'] ?? 20))),
        'min_preview_chars' => max(0, (int) ($s['review_paid_min_preview_chars'] ?? 50)),
        'max_preview_chars' => max(50, (int) ($s['review_paid_max_preview_chars'] ?? 2000)),
        'default_preview_chars' => max(0, (int) ($s['review_paid_default_preview_chars'] ?? 200)),
        'min_body_chars' => max(0, (int) ($s['review_paid_min_body_chars'] ?? 500)),
    ];
}

/**
 * 從 POST 解析付費設定。
 *
 * @return array{ok:bool,is_paid:bool,price:int,preview_chars:int,error:?string}
 */
function fate_masseur_review_paid_options_from_post(array $post, int $bodyTextLength): array
{
    if (!fate_masseur_review_paid_enabled()) {
        return ['ok' => true, 'is_paid' => false, 'price' => 0, 'preview_chars' => 0, 'error' => null];
    }

    $isPaid = isset($post['is_paid']) && (string) $post['is_paid'] === '1';
    if (!$isPaid) {
        return ['ok' => true, 'is_paid' => false, 'price' => 0, 'preview_chars' => 0, 'error' => null];
    }

    $cfg = fate_masseur_review_paid_config();
    $minBody = (int) $cfg['min_body_chars'];
    if ($minBody > 0 && $bodyTextLength < $minBody) {
        return [
            'ok' => false,
            'is_paid' => true,
            'price' => 0,
            'preview_chars' => 0,
            'error' => '付費評論需正文至少 ' . number_format($minBody) . ' 字（目前 ' . number_format($bodyTextLength) . ' 字）。',
        ];
    }

    $price = isset($post['paid_price']) ? max(0, (int) $post['paid_price']) : 0;
    $preview = isset($post['paid_preview_chars'])
        ? (int) $post['paid_preview_chars']
        : (int) $cfg['default_preview_chars'];

    $maxPrice = (int) $cfg['max_price'];
    $minPreview = (int) $cfg['min_preview_chars'];
    $maxPreview = max($minPreview, (int) $cfg['max_preview_chars']);

    if ($price < 1) {
        return ['ok' => false, 'is_paid' => true, 'price' => 0, 'preview_chars' => 0, 'error' => '付費評論請設定至少 1 ' . fate_ncoin_display_name() . '。'];
    }
    if ($price > $maxPrice) {
        return ['ok' => false, 'is_paid' => true, 'price' => $price, 'preview_chars' => $preview, 'error' => '售價不可超過上限 ' . fate_ncoin_amount_with_unit($maxPrice) . '。'];
    }
    if ($preview < $minPreview || $preview > $maxPreview) {
        return ['ok' => false, 'is_paid' => true, 'price' => $price, 'preview_chars' => $preview, 'error' => '預讀字數須在 ' . $minPreview . '～' . $maxPreview . ' 字之間。'];
    }

    return ['ok' => true, 'is_paid' => true, 'price' => $price, 'preview_chars' => $preview, 'error' => null];
}

function fate_masseur_review_paid_post_is_paid(array $review): bool
{
    return fate_masseur_review_paid_enabled()
        && (int) ($review['is_paid'] ?? 0) === 1
        && (int) ($review['paid_price'] ?? 0) > 0;
}

/**
 * @param list<int> $reviewIds
 * @return list<int>
 */
function fate_masseur_review_paid_purchased_ids(PDO $pdo, int $buyerId, array $reviewIds): array
{
    if ($buyerId <= 0 || $reviewIds === []) {
        return [];
    }
    fate_masseur_review_paid_ensure_schema($pdo);
    $reviewIds = array_values(array_unique(array_filter(array_map('intval', $reviewIds), static fn (int $id): bool => $id > 0)));
    if ($reviewIds === []) {
        return [];
    }
    $placeholders = implode(',', array_fill(0, count($reviewIds), '?'));
    try {
        $st = $pdo->prepare(
            "SELECT `review_id` FROM `fate_masseur_review_purchases` WHERE `buyer_member_id` = ? AND `review_id` IN ($placeholders)"
        );
        $st->execute(array_merge([$buyerId], $reviewIds));
        $rows = $st->fetchAll(PDO::FETCH_COLUMN) ?: [];

        return array_map('intval', $rows);
    } catch (Throwable) {
        return [];
    }
}

function fate_masseur_review_paid_viewer_can_read_full(
    PDO $pdo,
    array $review,
    int $viewerId,
    bool $isAdmin
): bool {
    if (!fate_masseur_review_paid_post_is_paid($review)) {
        return true;
    }
    if ($isAdmin) {
        return true;
    }
    $authorId = (int) ($review['member_id'] ?? 0);
    if ($viewerId > 0 && $viewerId === $authorId) {
        return true;
    }
    if ($viewerId <= 0) {
        return false;
    }
    $reviewId = (int) ($review['id'] ?? 0);

    return in_array($reviewId, fate_masseur_review_paid_purchased_ids($pdo, $viewerId, [$reviewId]), true);
}

/**
 * @param list<array<string,mixed>> $rows
 * @return list<array<string,mixed>>
 */
function fate_masseur_review_paid_attach(PDO $pdo, array $rows, int $viewerId, bool $isAdmin): array
{
    if ($rows === [] || !fate_masseur_review_paid_enabled()) {
        foreach ($rows as &$r) {
            $r['paid_active'] = false;
            $r['paid_viewer_full'] = true;
            $r['paid_viewer_purchased'] = false;
        }
        unset($r);

        return $rows;
    }
    fate_masseur_review_paid_ensure_schema($pdo);
    $reviewIds = array_map(static fn ($r) => (int) ($r['id'] ?? 0), $rows);
    $purchased = $viewerId > 0 ? fate_masseur_review_paid_purchased_ids($pdo, $viewerId, $reviewIds) : [];
    $purchasedFlip = array_flip($purchased);

    foreach ($rows as &$r) {
        $rid = (int) ($r['id'] ?? 0);
        $paid = fate_masseur_review_paid_post_is_paid($r);
        $r['paid_active'] = $paid;
        $r['paid_viewer_full'] = !$paid || fate_masseur_review_paid_viewer_can_read_full($pdo, $r, $viewerId, $isAdmin);
        $r['paid_viewer_purchased'] = isset($purchasedFlip[$rid]);
    }
    unset($r);

    return $rows;
}

function fate_masseur_review_paid_split_amount(int $price): array
{
    $cfg = fate_masseur_review_paid_config();
    $pct = (int) $cfg['commission_pct'];
    $platformFee = (int) floor($price * $pct / 100);
    $authorPayout = max(0, $price - $platformFee);

    return ['author_payout' => $authorPayout, 'platform_fee' => $platformFee, 'commission_pct' => $pct];
}

/**
 * @return array{
 *   can_buy:bool,
 *   code:string,
 *   message:string,
 *   price:int,
 *   balance:?int,
 *   shortfall:int
 * }
 */
function fate_masseur_review_paid_buy_check(PDO $pdo, array $review, int $buyerId, bool $verified, string $buyerUsername = ''): array
{
    $fail = static function (string $code, string $message, int $price = 0, ?int $balance = null, int $shortfall = 0): array {
        return [
            'can_buy' => false,
            'code' => $code,
            'message' => $message,
            'price' => $price,
            'balance' => $balance,
            'shortfall' => max(0, $shortfall),
        ];
    };

    if (!fate_masseur_review_paid_enabled()) {
        return $fail('feature_off', '付費評論功能目前未開放。');
    }
    if ($buyerId <= 0) {
        $price = max(0, (int) ($review['paid_price'] ?? 0));

        return $fail('not_logged_in', '請先登入並完成 Email 驗證後才能購買。', $price);
    }
    if (!$verified) {
        $price = max(0, (int) ($review['paid_price'] ?? 0));

        return $fail('not_verified', '購買付費評論需先完成 Email 驗證。', $price);
    }

    $reviewId = (int) ($review['id'] ?? 0);
    if ($reviewId <= 0) {
        return $fail('invalid', '評論參數錯誤。');
    }
    if ((int) ($review['is_hidden'] ?? 0) === 1) {
        return $fail('hidden', '此評論已隱藏，無法購買。');
    }
    if (!fate_masseur_review_paid_post_is_paid($review)) {
        return $fail('not_paid', '此評論並非付費內容，無需購買。');
    }

    $price = (int) ($review['paid_price'] ?? 0);
    $authorId = (int) ($review['member_id'] ?? 0);
    if ($authorId <= 0) {
        return $fail('invalid', '無法辨識作者，暫時無法購買。', $price);
    }
    if ($buyerId === $authorId) {
        return $fail('own_review', '這是您自己的評論，無需購買。', $price);
    }
    if (in_array($reviewId, fate_masseur_review_paid_purchased_ids($pdo, $buyerId, [$reviewId]), true)) {
        return $fail('already_bought', '您已購買過此評論，請重新整理頁面即可閱讀全文。', $price);
    }

    $balance = fate_member_read_ncoin($pdo, $buyerId, $buyerUsername);
    if ($balance === null) {
        return $fail('balance_unknown', '無法讀取您的 ' . fate_ncoin_display_name() . '餘額，請稍後再試或聯絡管理員。', $price, null, $price);
    }
    if ($balance < $price) {
        $shortfall = $price - $balance;

        return $fail(
            'insufficient_balance',
            fate_ncoin_display_name() . '不足：需要 ' . number_format($price) . '，您目前有 ' . number_format($balance) . '，尚缺 ' . number_format($shortfall) . '。',
            $price,
            $balance,
            $shortfall
        );
    }

    return [
        'can_buy' => true,
        'code' => 'ok',
        'message' => '可以購買（將扣除 ' . fate_ncoin_amount_with_unit($price) . '）。',
        'price' => $price,
        'balance' => $balance,
        'shortfall' => 0,
    ];
}

function fate_masseur_review_paid_flash_set(string $type, string $msg, int $reviewId): void
{
    fate_public_session_start();
    $_SESSION['fate_masseur_review_paid_flash'] = [
        'review_id' => $reviewId,
        'type' => $type === 'ok' ? 'ok' : 'bad',
        'msg' => $msg,
    ];
}

/** @return array{type:string,msg:string}|null */
function fate_masseur_review_paid_flash_for_review(int $reviewId): ?array
{
    fate_public_session_start();
    if (!isset($_SESSION['fate_masseur_review_paid_flash']) || !is_array($_SESSION['fate_masseur_review_paid_flash'])) {
        return null;
    }
    $f = $_SESSION['fate_masseur_review_paid_flash'];
    if ((int) ($f['review_id'] ?? 0) !== $reviewId) {
        return null;
    }
    unset($_SESSION['fate_masseur_review_paid_flash']);

    return [
        'type' => (string) ($f['type'] ?? 'bad') === 'ok' ? 'ok' : 'bad',
        'msg' => (string) ($f['msg'] ?? ''),
    ];
}

/**
 * @return array{ok:bool,error:?string,code:?string}
 */
function fate_masseur_review_paid_purchase(PDO $pdo, int $reviewId, int $buyerId, string $buyerUsername = '', bool $verified = true): array
{
    fate_masseur_review_paid_ensure_schema($pdo);
    try {
        $st = $pdo->prepare('SELECT * FROM `fate_masseur_reviews` WHERE `id` = ? LIMIT 1');
        $st->execute([$reviewId]);
        $review = $st->fetch(PDO::FETCH_ASSOC);
    } catch (Throwable) {
        $review = false;
    }
    if (!is_array($review)) {
        return ['ok' => false, 'error' => '評論不存在。', 'code' => 'invalid'];
    }

    $check = fate_masseur_review_paid_buy_check($pdo, $review, $buyerId, $verified, $buyerUsername);
    if (!$check['can_buy']) {
        return ['ok' => false, 'error' => (string) $check['message'], 'code' => (string) $check['code']];
    }

    $price = (int) $check['price'];
    $authorId = (int) ($review['member_id'] ?? 0);
    $split = fate_masseur_review_paid_split_amount($price);

    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_masseurs.php';
    $authorName = '';
    $memberQt = fate_masseur_member_table_qualified();
    if ($memberQt !== null) {
        try {
            $st = $pdo->prepare("SELECT COALESCE(NULLIF(TRIM(`display_name`), ''), `username`, '') AS n FROM $memberQt WHERE `id` = ? LIMIT 1");
            $st->execute([$authorId]);
            $authorName = (string) ($st->fetchColumn() ?: '');
        } catch (Throwable) {
            /* 略過 */
        }
    }

    try {
        $pdo->beginTransaction();

        $st = $pdo->prepare(
            'INSERT INTO `fate_masseur_review_purchases`
             (`review_id`, `buyer_member_id`, `price_paid`, `author_payout`, `platform_fee`)
             VALUES (?, ?, ?, ?, ?)'
        );
        $st->execute([
            $reviewId,
            $buyerId,
            $price,
            $split['author_payout'],
            $split['platform_fee'],
        ]);
        $purchaseId = (int) $pdo->lastInsertId();

        $debit = fate_ncoin_debit_member($pdo, $buyerId, $price, $buyerUsername);
        if (!$debit['local'] && !$debit['spa']) {
            throw new RuntimeException('扣款失敗，' . fate_ncoin_display_name() . '可能不足或帳戶異常，請稍後再試。');
        }

        if ($split['author_payout'] > 0) {
            $credit = fate_ncoin_credit_member($pdo, $authorId, $split['author_payout'], $authorName);
            if (!$credit['local'] && !$credit['spa']) {
                throw new RuntimeException('作者入帳失敗，請聯絡管理員。');
            }
        }

        fate_reward_ledger_ensure_table($pdo);
        try {
            $pdo->prepare(
                'INSERT IGNORE INTO `fate_reward_ledger` (`member_id`, `action`, `ref_id`, `amount`, `note`, `applied_local`, `applied_spa`)
                 VALUES (?, ?, ?, ?, ?, 1, 0)'
            )->execute([$buyerId, 'masseur_review_paid_buy', $purchaseId, $price, '購買付費評論 #' . $reviewId]);
        } catch (Throwable) {
            /* 帳本非必要 */
        }
        if ($split['author_payout'] > 0) {
            try {
                $pdo->prepare(
                    'INSERT IGNORE INTO `fate_reward_ledger` (`member_id`, `action`, `ref_id`, `amount`, `note`, `applied_local`, `applied_spa`)
                     VALUES (?, ?, ?, ?, ?, 1, 0)'
                )->execute([$authorId, 'masseur_review_paid_sell', $purchaseId, $split['author_payout'], '付費評論 #' . $reviewId . ' 售出']);
            } catch (Throwable) {
                /* 帳本非必要 */
            }
        }

        $pdo->commit();

        return ['ok' => true, 'error' => null, 'code' => 'ok'];
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }

        return ['ok' => false, 'error' => $e->getMessage() !== '' ? $e->getMessage() : '購買失敗，請稍後再試。', 'code' => 'error'];
    }
}

/** 輸出正文（含預讀／鎖定區）。 */
function fate_masseur_review_paid_render_body_html(array $review, bool $canViewFull): string
{
    $body = (string) ($review['body'] ?? '');
    if (!$canViewFull && fate_masseur_review_paid_post_is_paid($review)) {
        $previewChars = max(0, (int) ($review['paid_preview_chars'] ?? 200));
        $plain = fate_rich_body_plain_text($body);
        if ($previewChars <= 0 || $plain === '') {
            $snippet = '';
            $truncated = false;
        } elseif (function_exists('mb_substr')) {
            $snippet = mb_substr($plain, 0, $previewChars);
            $truncated = mb_strlen($plain) > $previewChars;
        } else {
            $snippet = substr($plain, 0, $previewChars);
            $truncated = strlen($plain) > $previewChars;
        }
        $html = '<div class="masseur-review-paid-preview">' . htmlspecialchars($snippet, ENT_QUOTES, 'UTF-8');
        if (!empty($truncated)) {
            $html .= '<span class="masseur-review-paid-preview__fade">…</span>';
        }
        $html .= '</div>';
        $remain = max(0, (function_exists('mb_strlen') ? mb_strlen($plain) : strlen($plain)) - $previewChars);
        $html .= '<div class="masseur-review-paid-lock" aria-hidden="true">';
        $html .= '<p class="masseur-review-paid-lock__hint">以下為付費內容（約 ' . number_format($remain) . ' 字）</p>';
        $html .= '</div>';

        return $html;
    }

    $rich = fate_rich_body_looks_like_html($body);
    $edited = !empty($review['edited_by_admin'])
        ? ' <em class="masseur-review__edited">（管理員編輯）</em>'
        : '';

    return '<div class="masseur-review__body-inner' . ($rich ? ' masseur-review__body-inner--rich' : '') . '">' . fate_rich_body_html($body) . $edited . '</div>';
}
