<?php

declare(strict_types=1);

/*
|--------------------------------------------------------------------------
| QRIS CALLBACK
|--------------------------------------------------------------------------
| Pasang URL ini pada panel payment gateway:
|
| https://domainanda.com/qris_callback.php
|
| Callback sukses akan:
| - memvalidasi trx_id, UUID merchant, username, custom_ref, dan nominal
| - mengunci transaksi
| - menambah balance_local satu kali
| - mencatat balance_transactions
|--------------------------------------------------------------------------
*/

require_once __DIR__ . '/bootstrap.php';

header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: no-store');

function qrisCallbackRespond(
    int $httpCode,
    array $payload
): void {
    http_response_code($httpCode);

    echo json_encode(
        $payload,
        JSON_UNESCAPED_SLASHES |
        JSON_UNESCAPED_UNICODE
    );

    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    qrisCallbackRespond(
        405,
        [
            'status' => false,
            'error' => 'Method not allowed',
        ]
    );
}

$rawBody = file_get_contents('php://input');

$payload = json_decode(
    (string) $rawBody,
    true
);

if (!is_array($payload)) {
    qrisCallbackRespond(
        400,
        [
            'status' => false,
            'error' => 'Invalid JSON',
        ]
    );
}

$status = strtolower(
    trim(
        (string) (
            $payload['status'] ??
            ''
        )
    )
);

$merchantId = trim(
    (string) (
        $payload['merchant_id'] ??
        ''
    )
);

$terminalId = trim(
    (string) (
        $payload['terminal_id'] ??
        ''
    )
);

$trxId = trim(
    (string) (
        $payload['trx_id'] ??
        ''
    )
);

$customRef = strtoupper(
    trim(
        (string) (
            $payload['custom_ref'] ??
            ''
        )
    )
);

$rrn = trim(
    (string) (
        $payload['rrn'] ??
        ''
    )
);

$vendor = trim(
    (string) (
        $payload['vendor'] ??
        ''
    )
);

$amount = isset($payload['amount']) &&
    is_numeric($payload['amount'])
        ? round((float) $payload['amount'], 2)
        : 0.0;

if ($status !== 'success') {
    qrisCallbackRespond(
        422,
        [
            'status' => false,
            'error' => 'Status is not success',
        ]
    );
}

if (
    $merchantId === '' ||
    $terminalId === '' ||
    $trxId === '' ||
    $amount <= 0
) {
    qrisCallbackRespond(
        422,
        [
            'status' => false,
            'error' => 'Required fields are missing',
        ]
    );
}

try {
    $pdo->beginTransaction();

    $depositStatement = $pdo->prepare(
        "SELECT
            d.*,
            u.username,
            u.balance_local
         FROM deposit_requests AS d
         INNER JOIN users AS u
            ON u.id = d.user_id
         WHERE d.gateway_trx_id = :trx_id
           AND d.method_type = 'QRIS_GATEWAY'
         LIMIT 1
         FOR UPDATE"
    );

    $depositStatement->execute([
        ':trx_id' => $trxId,
    ]);

    $deposit = $depositStatement->fetch();

    if (!$deposit) {
        throw new RuntimeException(
            'Transaction not found'
        );
    }

    /*
     * Idempotent: callback berulang tidak menambah saldo dua kali.
     */
    if (
        in_array(
            strtoupper(
                (string) $deposit['status']
            ),
            ['PAID', 'APPROVED'],
            true
        )
    ) {
        $pdo->commit();

        qrisCallbackRespond(
            200,
            [
                'status' => true,
                'message' => 'Already processed',
                'reference_code' =>
                    $deposit['reference_code'],
            ]
        );
    }

    if (
        strtoupper(
            (string) $deposit['status']
        ) !== 'PENDING'
    ) {
        throw new RuntimeException(
            'Transaction is not pending'
        );
    }

    if (
        !hash_equals(
            (string) $deposit[
                'gateway_uuid_snapshot'
            ],
            $merchantId
        )
    ) {
        throw new RuntimeException(
            'Merchant UUID mismatch'
        );
    }

    if (
        !hash_equals(
            (string) $deposit['username'],
            $terminalId
        )
    ) {
        throw new RuntimeException(
            'Terminal username mismatch'
        );
    }

    if (
        $customRef !== '' &&
        !hash_equals(
            strtoupper(
                (string) $deposit[
                    'reference_code'
                ]
            ),
            $customRef
        )
    ) {
        throw new RuntimeException(
            'Custom reference mismatch'
        );
    }

    $expectedAmount = round(
        (float) $deposit['amount'],
        2
    );

    if (
        abs(
            $expectedAmount -
            $amount
        ) > 0.001
    ) {
        throw new RuntimeException(
            'Amount mismatch'
        );
    }

    /*
     * Cek log sebelum kredit sebagai perlindungan tambahan.
     */
    $existingLogStatement = $pdo->prepare(
        "SELECT id
         FROM balance_transactions
         WHERE user_id = :user_id
           AND reference_code = :reference_code
           AND transaction_type =
               'DEPOSIT_QRIS_GATEWAY'
         LIMIT 1
         FOR UPDATE"
    );

    $existingLogStatement->execute([
        ':user_id' =>
            (int) $deposit['user_id'],
        ':reference_code' =>
            (string) $deposit['reference_code'],
    ]);

    if ($existingLogStatement->fetch()) {
        $markApproved = $pdo->prepare(
            "UPDATE deposit_requests
             SET
                status = 'APPROVED',
                paid_at = COALESCE(
                    paid_at,
                    CURRENT_TIMESTAMP
                ),
                approved_at = COALESCE(
                    approved_at,
                    CURRENT_TIMESTAMP
                ),
                gateway_rrn = :rrn,
                gateway_vendor = :vendor,
                gateway_callback_payload =
                    :callback_payload
             WHERE id = :deposit_id"
        );

        $markApproved->execute([
            ':rrn' => $rrn,
            ':vendor' => $vendor,
            ':callback_payload' =>
                json_encode(
                    $payload,
                    JSON_UNESCAPED_SLASHES |
                    JSON_UNESCAPED_UNICODE
                ),
            ':deposit_id' =>
                (int) $deposit['id'],
        ]);

        $pdo->commit();

        qrisCallbackRespond(
            200,
            [
                'status' => true,
                'message' => 'Already credited',
            ]
        );
    }

    $balanceBefore = (float) (
        $deposit['balance_local'] ??
        0
    );

    $balanceAfter =
        $balanceBefore +
        $expectedAmount;

    $updateUser = $pdo->prepare(
        "UPDATE users
         SET balance_local = :balance_after
         WHERE id = :user_id"
    );

    $updateUser->execute([
        ':balance_after' =>
            number_format(
                $balanceAfter,
                2,
                '.',
                ''
            ),
        ':user_id' =>
            (int) $deposit['user_id'],
    ]);

    $insertBalanceLog = $pdo->prepare(
        "INSERT INTO balance_transactions (
            user_id,
            transaction_type,
            direction,
            amount,
            balance_before,
            balance_after,
            reference_code,
            description
        ) VALUES (
            :user_id,
            'DEPOSIT_QRIS_GATEWAY',
            'CREDIT',
            :amount,
            :balance_before,
            :balance_after,
            :reference_code,
            'Deposit QRIS payment gateway berhasil'
        )"
    );

    $insertBalanceLog->execute([
        ':user_id' =>
            (int) $deposit['user_id'],
        ':amount' =>
            number_format(
                $expectedAmount,
                2,
                '.',
                ''
            ),
        ':balance_before' =>
            number_format(
                $balanceBefore,
                2,
                '.',
                ''
            ),
        ':balance_after' =>
            number_format(
                $balanceAfter,
                2,
                '.',
                ''
            ),
        ':reference_code' =>
            (string) $deposit['reference_code'],
    ]);

    $updateDeposit = $pdo->prepare(
        "UPDATE deposit_requests
         SET
            status = 'APPROVED',
            paid_at = CURRENT_TIMESTAMP,
            approved_at = CURRENT_TIMESTAMP,
            gateway_rrn = :rrn,
            gateway_vendor = :vendor,
            gateway_callback_payload =
                :callback_payload
         WHERE id = :deposit_id"
    );

    $updateDeposit->execute([
        ':rrn' => $rrn,
        ':vendor' => $vendor,
        ':callback_payload' =>
            json_encode(
                $payload,
                JSON_UNESCAPED_SLASHES |
                JSON_UNESCAPED_UNICODE
            ),
        ':deposit_id' =>
            (int) $deposit['id'],
    ]);

    $pdo->commit();

    qrisCallbackRespond(
        200,
        [
            'status' => true,
            'message' => 'Deposit credited',
            'reference_code' =>
                $deposit['reference_code'],
        ]
    );
} catch (Throwable $exception) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }

    error_log(
        'QRIS CALLBACK ERROR: ' .
        $exception->getMessage()
    );

    qrisCallbackRespond(
        422,
        [
            'status' => false,
            'error' =>
                $exception->getMessage(),
        ]
    );
}
