<?php
/**
 * POST /api/bank/webhook.php
 * Receives webhooks from partner banks (BankAdapter integration)
 *
 * SECURITY CHAIN (in order):
 *   1. HMAC signature → BankAdapter::processWebhook
 *   2. DB-level idempotency → INSERT bank_webhooks (UNIQUE bank_txn_id)
 *      Duplicate entry = already_processed — stops here
 *   3. processWebhook() executes only ONCE per bank_txn_id
 *   4. Logger::info audit trail
 *
 * IDEMPOTENCY GUARANTEE:
 *   100 concurrent identical webhooks → exactly 1 processes
 *   MySQL UNIQUE constraint prevents any race window
 */
header('Content-Type: application/json');
require_once dirname(dirname(__DIR__)) . '/config.php';
require_once dirname(__DIR__) . '/engine/Logger.php';
require_once dirname(dirname(__DIR__)) . '/engine/BankAdapter.php';

Logger::traceId();
header('X-Trace-Id: ' . Logger::traceId());

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    die(json_encode(['error' => 'method_not_allowed']));
}

$rawBody   = file_get_contents('php://input');
$payload   = json_decode($rawBody, true) ?? [];
$signature = $_SERVER['HTTP_X_BANK_SIGNATURE'] ?? $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$instCode  = $_SERVER['HTTP_X_INSTITUTION'] ?? 'BOK';

if (!$payload) {
    http_response_code(400);
    die(json_encode(['error' => 'invalid_payload']));
}

// ── Extract transaction reference ────────────────────────────────
$bankTxnId = trim($payload['bank_txn_id'] ?? $payload['transaction_id'] ?? $payload['txn_ref'] ?? '');

if (!$bankTxnId) {
    http_response_code(422);
    die(json_encode(['error' => 'missing_bank_txn_id', 'required' => 'bank_txn_id or transaction_id']));
}

// ── Institution lookup ────────────────────────────────────────────
$institutionId = 0;
try {
    $inst = db()->prepare("SELECT id FROM financial_institutions WHERE code=? LIMIT 1");
    $inst->execute([$instCode]);
    $institutionId = (int)($inst->fetchColumn() ?? 0);
} catch (Throwable $ignored) {}

// ── DB-LEVEL IDEMPOTENCY GATE ────────────────────────────────────
// UNIQUE INDEX idx_bank_txn (bank_txn_id) prevents double-processing
// even under 100 concurrent requests — no race window possible
// ────────────────────────────────────────────────────────────────
try {
    db()->prepare(
        "INSERT INTO bank_webhooks
         (institution_id, event_type, bank_txn_id, sdpay_ref, payload, signature, is_verified)
         VALUES (?, ?, ?, ?, ?, ?, 0)"
    )->execute([
        $institutionId,
        $payload['event'] ?? 'unknown',
        $bankTxnId,
        $payload['reference'] ?? null,
        $rawBody,
        $signature,
    ]);
    // INSERT succeeded → first time seeing this webhook → proceed
    $webhookRowId = (int)db()->lastInsertId();

} catch (Throwable $e) {
    // Duplicate entry on bank_txn_id → already processed
    if (str_contains($e->getMessage(), 'Duplicate entry') ||
        str_contains($e->getMessage(), '1062')) {
        Logger::info('bank_webhook_duplicate', [
            'bank_txn_id' => $bankTxnId,
            'institution' => $instCode,
        ]);
        http_response_code(200);
        echo json_encode([
            'received'    => true,
            'action'      => 'already_processed',
            'bank_txn_id' => $bankTxnId,
            'trace_id'    => Logger::traceId(),
        ], JSON_UNESCAPED_UNICODE);
        exit;
    }
    // DB error — log and fail
    Logger::error('bank_webhook_db_error', ['error' => $e->getMessage(), 'bank_txn_id' => $bankTxnId]);
    http_response_code(500);
    die(json_encode(['error' => 'db_error', 'trace_id' => Logger::traceId()]));
}

// ── HMAC verification + business logic via BankAdapter ───────────
$result = BankAdapter::processWebhook($payload, $signature, $institutionId);

// ── Update webhook row with result ───────────────────────────────
try {
    db()->prepare(
        "UPDATE bank_webhooks
         SET is_verified  = ?,
             is_processed = ?,
             process_result = ?,
             processed_at = NOW()
         WHERE id = ?"
    )->execute([
        (int)($result['ok'] ?? false),
        (int)($result['ok'] ?? false),
        json_encode($result),
        $webhookRowId,
    ]);
} catch (Throwable $ignored) {}

// ── Structured audit log ──────────────────────────────────────────
Logger::info('bank_webhook_processed', [
    'bank_txn_id'  => $bankTxnId,
    'institution'  => $instCode,
    'event'        => $payload['event'] ?? 'unknown',
    'ok'           => $result['ok'] ?? false,
    'webhook_id'   => $webhookRowId,
]);

http_response_code(($result['ok'] ?? false) ? 200 : 400);
echo json_encode(array_merge($result, [
    'trace_id'    => Logger::traceId(),
    'bank_txn_id' => $bankTxnId,
]), JSON_UNESCAPED_UNICODE);
