<?php

declare(strict_types=1);

/**
 * 站內 N 幣會員轉帳。
 */

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

function fate_ncoin_transfers_ddl(): string
{
    return <<<'SQL'
CREATE TABLE IF NOT EXISTS `fate_ncoin_transfers` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `from_member_id` INT UNSIGNED NOT NULL,
  `to_member_id` INT UNSIGNED NOT NULL,
  `amount_gross` INT UNSIGNED NOT NULL,
  `fee_amount` INT UNSIGNED NOT NULL DEFAULT 0,
  `amount_net` INT UNSIGNED NOT NULL,
  `fee_pct` TINYINT UNSIGNED NOT NULL DEFAULT 0,
  `note` VARCHAR(120) NOT NULL DEFAULT '',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_from` (`from_member_id`),
  KEY `idx_to` (`to_member_id`),
  KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL;
}

function fate_ncoin_transfers_ensure_table(PDO $pdo): void
{
    static $done = false;
    if ($done) {
        return;
    }
    try {
        $pdo->exec(fate_ncoin_transfers_ddl());
        $done = true;
    } catch (Throwable) {
    }
}

/** 手續費（從轉出金額內扣除）。 */
function fate_ncoin_transfer_calc_fee(int $amount, int $feePct): int
{
    if ($amount <= 0 || $feePct <= 0) {
        return 0;
    }

    return intdiv($amount * $feePct, 100);
}

/**
 * 可轉出上限（餘額扣除留底後）。
 */
function fate_ncoin_transfer_max_amount(int $balance, int $reserve): int
{
    if ($balance <= $reserve) {
        return 0;
    }

    return $balance - $reserve;
}

/**
 * 是否達留底門檻可啟用轉帳。
 */
function fate_ncoin_transfer_eligible(int $balance, int $reserve): bool
{
    return $balance >= $reserve && $reserve >= 0;
}

/**
 * 查詢轉帳對象（僅公開編號與顯示名稱）。
 *
 * @return array{ok:bool,code:string,message:string,member?:array{id:int,display_name:string}}
 */
function fate_ncoin_transfer_lookup_member(PDO $pdo, int $targetId, int $selfId): array
{
    if ($targetId <= 0) {
        return ['ok' => false, 'code' => 'invalid_id', 'message' => '請輸入有效的會員編號。'];
    }
    if ($targetId === $selfId) {
        return ['ok' => false, 'code' => 'self', 'message' => '不能轉帳給自己。'];
    }

    $fr = fate_public_member_login_row_fetch_by_id($pdo, $targetId);
    if (!$fr['found'] || !is_array($fr['row'])) {
        return ['ok' => false, 'code' => 'not_found', 'message' => '找不到此會員編號。'];
    }
    if ((int) ($fr['row']['_mem_active'] ?? 0) !== 1) {
        return ['ok' => false, 'code' => 'inactive', 'message' => '此會員帳號目前無法收款。'];
    }

    $disp = trim((string) ($fr['row']['display_name'] ?? ''));
    if ($disp === '') {
        $disp = trim((string) ($fr['row']['username'] ?? ''));
    }
    if ($disp === '') {
        $disp = '會員 #' . $targetId;
    }

    return [
        'ok' => true,
        'code' => 'ok',
        'message' => '已找到收款會員。',
        'member' => [
            'id' => $targetId,
            'display_name' => $disp,
        ],
    ];
}

/**
 * 轉帳前檢查（不實際扣款）。
 *
 * @return array{
 *   can_transfer:bool,
 *   code:string,
 *   message:string,
 *   amount?:int,
 *   fee?:int,
 *   net?:int,
 *   balance?:int,
 *   max_amount?:int,
 *   reserve?:int,
 *   fee_pct?:int
 * }
 */
function fate_ncoin_transfer_check(
    PDO $pdo,
    int $fromId,
    int $toId,
    int $amount,
    string $fromUsername = ''
): array {
    $cfg = fate_ncoin_settings();
    $reserve = (int) $cfg['transfer_reserve'];
    $feePct = (int) $cfg['transfer_fee_pct'];

    $fail = static function (string $code, string $message, array $extra = []): array {
        return array_merge([
            'can_transfer' => false,
            'code' => $code,
            'message' => $message,
        ], $extra);
    };

    if (!$cfg['transfer_enabled']) {
        return $fail('disabled', '站方尚未開放 ' . fate_ncoin_display_name() . '轉帳功能。');
    }
    if ($fromId <= 0) {
        return $fail('invalid', '無效的轉出帳號。');
    }
    if ($amount < 1) {
        return $fail('invalid_amount', '轉帳金額至少為 1 ' . fate_ncoin_display_name() . '。');
    }

    $lookup = fate_ncoin_transfer_lookup_member($pdo, $toId, $fromId);
    if (!$lookup['ok']) {
        return $fail((string) $lookup['code'], (string) $lookup['message']);
    }

    $balance = fate_member_read_ncoin($pdo, $fromId, $fromUsername);
    if ($balance === null) {
        return $fail('balance_unknown', '無法讀取您的 ' . fate_ncoin_display_name() . '餘額，請稍後再試。');
    }

    $ncoin = fate_ncoin_display_name();
    if (!fate_ncoin_transfer_eligible($balance, $reserve)) {
        return $fail(
            'below_reserve',
            '餘額須達 ' . fate_ncoin_amount_with_unit($reserve) . '（留底金額）才可使用轉帳；您目前有 ' . fate_ncoin_amount_with_unit($balance) . '。',
            ['balance' => $balance, 'reserve' => $reserve, 'max_amount' => 0, 'fee_pct' => $feePct]
        );
    }

    $maxAmount = fate_ncoin_transfer_max_amount($balance, $reserve);
    if ($amount > $maxAmount) {
        return $fail(
            'exceeds_max',
            '超出可轉出上限：留底 ' . fate_ncoin_amount_with_unit($reserve) . '不可轉出，您最多可轉 ' . fate_ncoin_amount_with_unit($maxAmount) . '。',
            ['balance' => $balance, 'reserve' => $reserve, 'max_amount' => $maxAmount, 'fee_pct' => $feePct]
        );
    }

    $fee = fate_ncoin_transfer_calc_fee($amount, $feePct);
    $net = $amount - $fee;
    if ($net < 1) {
        return $fail(
            'fee_too_high',
            '扣除手續費 ' . fate_ncoin_amount_with_unit($fee) . '後，對方實收須至少 1 ' . $ncoin . '，請提高轉帳金額。',
            ['balance' => $balance, 'reserve' => $reserve, 'max_amount' => $maxAmount, 'fee_pct' => $feePct, 'fee' => $fee]
        );
    }

    return [
        'can_transfer' => true,
        'code' => 'ok',
        'message' => '可轉出 ' . fate_ncoin_amount_with_unit($amount) . '；手續費 ' . number_format($fee) . '，對方實收 ' . number_format($net) . ' ' . $ncoin . '。',
        'amount' => $amount,
        'fee' => $fee,
        'net' => $net,
        'balance' => $balance,
        'max_amount' => $maxAmount,
        'reserve' => $reserve,
        'fee_pct' => $feePct,
    ];
}

/**
 * 執行轉帳。
 *
 * @return array{ok:bool,error:?string,code:?string,transfer_id?:int}
 */
function fate_ncoin_transfer_execute(
    PDO $pdo,
    int $fromId,
    int $toId,
    int $amount,
    string $fromUsername = '',
    string $note = ''
): array {
    $check = fate_ncoin_transfer_check($pdo, $fromId, $toId, $amount, $fromUsername);
    if (!$check['can_transfer']) {
        return ['ok' => false, 'error' => (string) $check['message'], 'code' => (string) $check['code']];
    }

    $fee = (int) $check['fee'];
    $net = (int) $check['net'];
    $feePct = (int) $check['fee_pct'];
    $note = mb_substr(trim($note), 0, 120);

    $toLookup = fate_ncoin_transfer_lookup_member($pdo, $toId, $fromId);
    $toDisplay = $toLookup['member']['display_name'] ?? ('#' . $toId);

    fate_ncoin_transfers_ensure_table($pdo);
    fate_reward_ledger_ensure_table($pdo);

    try {
        $pdo->beginTransaction();

        $balanceNow = fate_member_read_ncoin($pdo, $fromId, $fromUsername);
        if ($balanceNow === null) {
            throw new RuntimeException('無法讀取餘額。');
        }
        $reserve = (int) ($check['reserve'] ?? 0);
        $maxNow = fate_ncoin_transfer_max_amount($balanceNow, $reserve);
        if ($amount > $maxNow) {
            throw new RuntimeException('餘額已變動，請重新確認可轉出上限後再試。');
        }

        $st = $pdo->prepare(
            'INSERT INTO `fate_ncoin_transfers`
             (`from_member_id`, `to_member_id`, `amount_gross`, `fee_amount`, `amount_net`, `fee_pct`, `note`)
             VALUES (?, ?, ?, ?, ?, ?, ?)'
        );
        $st->execute([$fromId, $toId, $amount, $fee, $net, $feePct, $note]);
        $transferId = (int) $pdo->lastInsertId();
        if ($transferId <= 0) {
            throw new RuntimeException('建立轉帳紀錄失敗。');
        }

        $debit = fate_ncoin_debit_member($pdo, $fromId, $amount, $fromUsername);
        if (!$debit['local'] && !$debit['spa']) {
            throw new RuntimeException('扣款失敗，' . fate_ncoin_display_name() . '可能不足或帳戶異常。');
        }

        $credit = fate_ncoin_credit_member($pdo, $toId, $net, $toDisplay);
        if (!$credit['local'] && !$credit['spa']) {
            throw new RuntimeException('收款方入帳失敗，請聯絡管理員。');
        }

        $outNote = '轉帳給會員 #' . $toId;
        if ($note !== '') {
            $outNote .= '：' . $note;
        }
        $inNote = '來自會員 #' . $fromId . ' 的轉帳';
        if ($note !== '') {
            $inNote .= '：' . $note;
        }

        $pdo->prepare(
            'INSERT INTO `fate_reward_ledger`
             (`member_id`, `action`, `ref_id`, `amount`, `note`, `applied_local`, `applied_spa`)
             VALUES (?, ?, ?, ?, ?, ?, ?)'
        )->execute([
            $fromId,
            'ncoin_transfer_out',
            $transferId,
            $amount,
            mb_substr($outNote, 0, 255),
            $debit['local'] ? 1 : 0,
            $debit['spa'] ? 1 : 0,
        ]);

        $pdo->prepare(
            'INSERT INTO `fate_reward_ledger`
             (`member_id`, `action`, `ref_id`, `amount`, `note`, `applied_local`, `applied_spa`)
             VALUES (?, ?, ?, ?, ?, ?, ?)'
        )->execute([
            $toId,
            'ncoin_transfer_in',
            $transferId,
            $net,
            mb_substr($inNote, 0, 255),
            $credit['local'] ? 1 : 0,
            $credit['spa'] ? 1 : 0,
        ]);

        $pdo->commit();

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

        return ['ok' => false, 'error' => $e->getMessage(), 'code' => 'failed'];
    }
}
