<?php
declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
header('X-Content-Type-Options: nosniff');

function reply(int $status, array $payload): never
{
    http_response_code($status);
    echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    reply(405, ['success' => false, 'error' => 'POST is required.']);
}

$length = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
if ($length > 2_000_000) {
    reply(413, ['success' => false, 'error' => 'The request body is too large.']);
}

try {
    $request = json_decode(file_get_contents('php://input'), true, 64, JSON_THROW_ON_ERROR);
} catch (JsonException) {
    reply(400, ['success' => false, 'error' => 'The request body must be valid JSON.']);
}

if (!is_array($request)) {
    reply(400, ['success' => false, 'error' => 'The request body must be a JSON object.']);
}

$action = (string) ($request['action'] ?? '');
$txid = strtolower(trim((string) ($request['txid'] ?? '')));
if (!preg_match('/^[a-f0-9]{64}$/', $txid)) {
    reply(400, ['success' => false, 'error' => 'A valid 64-character transaction hash is required.']);
}

$storageDirectory = __DIR__ . '/storage/dual-signatures';
if (!is_dir($storageDirectory) && !mkdir($storageDirectory, 0770, true) && !is_dir($storageDirectory)) {
    reply(500, ['success' => false, 'error' => 'The JSON storage directory could not be created.']);
}

$fileName = $txid . '.json';
$path = $storageDirectory . '/' . $fileName;

if ($action === 'load') {
    if (!is_file($path)) {
        reply(404, ['success' => false, 'error' => 'No saved transaction exists for that hash.']);
    }
    $handle = fopen($path, 'rb');
    if ($handle === false || !flock($handle, LOCK_SH)) {
        if (is_resource($handle)) fclose($handle);
        reply(500, ['success' => false, 'error' => 'The saved transaction could not be opened.']);
    }
    $record = json_decode(stream_get_contents($handle) ?: '', true);
    flock($handle, LOCK_UN);
    fclose($handle);
    if (!is_array($record)) {
        reply(500, ['success' => false, 'error' => 'The saved transaction JSON is invalid.']);
    }
    reply(200, ['success' => true, 'data' => ['file' => $fileName, 'record' => $record]]);
}

if ($action !== 'save_signature') {
    reply(400, ['success' => false, 'error' => 'The storage action is not supported.']);
}

$kind = strtolower(trim((string) ($request['kind'] ?? '')));
$slot = (int) ($request['signer_slot'] ?? 0);
$transaction = $request['transaction'] ?? null;
$signature = $request['signature'] ?? null;
if (!in_array($kind, ['swap', 'loan'], true)) {
    reply(400, ['success' => false, 'error' => 'The transaction kind must be swap or loan.']);
}
if ($slot !== 1 && $slot !== 2) {
    reply(400, ['success' => false, 'error' => 'The signer slot must be 1 or 2.']);
}
if (!is_array($transaction) || !is_array($signature)) {
    reply(400, ['success' => false, 'error' => 'The transaction and signature result are required.']);
}
if (strtolower((string) ($signature['txid'] ?? '')) !== $txid) {
    reply(400, ['success' => false, 'error' => 'The signature result does not match the transaction hash.']);
}
if ((int) ($signature['signerSlot'] ?? 0) !== $slot) {
    reply(400, ['success' => false, 'error' => 'The signature result does not match the signer slot.']);
}

$handle = fopen($path, 'c+');
if ($handle === false || !flock($handle, LOCK_EX)) {
    if (is_resource($handle)) fclose($handle);
    reply(500, ['success' => false, 'error' => 'The transaction JSON could not be locked.']);
}

$existingJson = stream_get_contents($handle) ?: '';
$record = $existingJson === '' ? null : json_decode($existingJson, true);
if ($record !== null && !is_array($record)) {
    flock($handle, LOCK_UN);
    fclose($handle);
    reply(500, ['success' => false, 'error' => 'The existing transaction JSON is invalid.']);
}
if ($record !== null && (
    ($record['kind'] ?? null) !== $kind
    || json_encode($record['transaction'] ?? null) !== json_encode($transaction)
)) {
    flock($handle, LOCK_UN);
    fclose($handle);
    reply(409, ['success' => false, 'error' => 'That hash is already associated with different transaction data.']);
}

$record ??= [
    'txid' => $txid,
    'kind' => $kind,
    'transaction' => $transaction,
    'signer1' => null,
    'signer2' => null,
    'created_at' => gmdate(DATE_ATOM),
];
$record['signer' . $slot] = $signature;
$record['updated_at'] = gmdate(DATE_ATOM);
$encoded = json_encode($record, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if ($encoded === false) {
    flock($handle, LOCK_UN);
    fclose($handle);
    reply(500, ['success' => false, 'error' => 'The transaction record could not be encoded.']);
}

rewind($handle);
ftruncate($handle, 0);
if (fwrite($handle, $encoded . PHP_EOL) === false) {
    flock($handle, LOCK_UN);
    fclose($handle);
    reply(500, ['success' => false, 'error' => 'The transaction record could not be saved.']);
}
fflush($handle);
flock($handle, LOCK_UN);
fclose($handle);

reply(200, ['success' => true, 'data' => ['file' => $fileName, 'record' => $record]]);

