<?php
// parse-outbox.php — parse outbox.log and send a dynamic mention notification with note content

date_default_timezone_set('UTC');

$logFile = __DIR__ . '/outbox.log';
$notifyLog = __DIR__ . '/notify.log';

function logNotify(string $msg) {
    global $notifyLog;
    file_put_contents($notifyLog, "[" . date('c') . "] $msg\n", FILE_APPEND);
}

// --- Functions to discover inbox and send signed requests ---

function discoverInbox(string $actorUrl): ?string {
    logNotify("Fetching actor JSON from: $actorUrl");
    $actorUrl = rtrim($actorUrl, '/') . '.json';
    $ctx = stream_context_create([
        'http' => [
            'method' => 'GET',
            'header' => "Accept: application/activity+json, application/ld+json\r\n",
            'timeout' => 5,
        ]
    ]);
    $json = @file_get_contents($actorUrl, false, $ctx);
    if (!$json) {
        logNotify("Failed to fetch actor JSON from $actorUrl");
        return null;
    }
    $actor = json_decode($json, true);
    if (!$actor) {
        logNotify("Invalid JSON from actor URL $actorUrl");
        return null;
    }
    $inbox = $actor['inbox'] ?? null;
    if ($inbox) {
        logNotify("Discovered inbox: $inbox");
    } else {
        logNotify("Inbox not found in actor JSON");
    }
    return $inbox;
}

function sendSignedRequest(string $inboxUrl, array $body): bool {
    global $notifyLog;

    $privateKeyFile = __DIR__ . '/private.pem';
    $baseUrl = 'https://alceawis.com';
    $username = 'alceawis';

    logNotify("Preparing to send Create activity to $inboxUrl");

    $privateKeyPem = @file_get_contents($privateKeyFile);
    if (!$privateKeyPem) {
        logNotify("Private key file not found or unreadable.");
        return false;
    }

    $bodyJson = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    if ($bodyJson === false) {
        logNotify("Failed to encode JSON body for sending.");
        return false;
    }

    $digest = 'SHA-256=' . base64_encode(hash('sha256', $bodyJson, true));
    $date = gmdate('D, d M Y H:i:s \G\M\T');
    $urlParts = parse_url($inboxUrl);
    $host = $urlParts['host'] ?? '';
    $path = $urlParts['path'] ?? '/';

    $signatureString = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: $digest";
    $privateKey = openssl_pkey_get_private($privateKeyPem);
    if (!$privateKey) {
        logNotify("Failed to load private key for signing.");
        return false;
    }
    $signatureOk = openssl_sign($signatureString, $signature, $privateKey, OPENSSL_ALGO_SHA256);
    if (!$signatureOk) {
        logNotify("Failed to create signature.");
        return false;
    }
    $signatureB64 = base64_encode($signature);
    $keyId = "$baseUrl/$username#main-key";

    $signatureHeader = sprintf(
        'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="%s"',
        $keyId,
        $signatureB64
    );

    $headers = [
        "Host: $host",
        "Date: $date",
        "Digest: $digest",
        "Signature: $signatureHeader",
        "Content-Type: application/activity+json"
    ];

    $ch = curl_init($inboxUrl);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $bodyJson,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
    ]);

    $response = curl_exec($ch);
    $curlError = curl_error($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($curlError) {
        logNotify("cURL error sending to $inboxUrl: $curlError");
        return false;
    }

    logNotify("Sent Create activity to $inboxUrl");
    logNotify("HTTP $httpCode");
    logNotify("Response: " . substr($response, 0, 300));

    return $httpCode >= 200 && $httpCode < 300;
}

// --- Main logic ---

// 1. Read full outbox log
$logContent = file_get_contents($logFile);
if (!$logContent) {
    logNotify("Could not read outbox.log");
    exit("Error: outbox.log not readable\n");
}

// 2. Extract latest post entry (greedy match till next timestamp or EOF)
preg_match_all('/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] New post created.*?(?=\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]|$)/s', $logContent, $entries);
if (empty($entries[0])) {
    logNotify("No entries found in outbox.log");
    exit("No entries found\n");
}
$latestEntry = trim(end($entries[0]));

// 3. Parse fields
preg_match('/Status URL:\s*(\S+)/', $latestEntry, $statusMatch);
preg_match('/Reply To:\s*(\S+)/', $latestEntry, $replyMatch);
preg_match('/Content:\s*(.*)/s', $latestEntry, $contentMatch);

$statusUrl = $statusMatch[1] ?? '';
$replyToUrl = $replyMatch[1] ?? '';
$contentRaw = $contentMatch[1] ?? '';

// 4. Extract mention user and domain from Reply To URL
if (!preg_match('#https?://([^/]+)/@([^/]+)/#', $replyToUrl, $mentionParts)) {
    logNotify("Failed to parse reply-to mention: $replyToUrl");
    exit("Failed to parse reply-to mention URL.\n");
}
$mentionDomain = $mentionParts[1];   // e.g. mas.to
$mentionUser = $mentionParts[2];     // e.g. alcea
$mentionHandle = "@$mentionUser";

// Construct actor URL (user JSON) for discoverInbox()
$actorUrl = "https://$mentionDomain/users/$mentionUser";

// Also the mentionUrl for the notification
$mentionUrl = $actorUrl;

// 5. Prepare activity with public visibility and note content as content
$baseUrl = 'https://alceawis.com';
$username = 'alceawis';
$activityId = "$baseUrl/$username#notification-" . time();

$activity = [
    '@context' => 'https://www.w3.org/ns/activitystreams',
    'id' => $activityId,
    'type' => 'Create',
    'actor' => "$baseUrl/$username",
    'to' => [$actorUrl],
    'cc' => ['https://www.w3.org/ns/activitystreams#Public'], // public visibility
    'object' => [
        'id' => $activityId . '/object',
        'type' => 'Note',
        'attributedTo' => "$baseUrl/$username",
        'to' => [$actorUrl],
        'url' => $statusUrl,
        'cc' => ['https://www.w3.org/ns/activitystreams#Public'], // public visibility
        'inReplyTo' => $replyToUrl,
        'content' => nl2br(htmlspecialchars(trim($contentRaw), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')),
        'tag' => [
            [
                'type' => 'Mention',
                'href' => $actorUrl,
                'name' => $mentionHandle,
            ],
        ],
    ],
];

// 6. Discover inbox and send notification
$inbox = discoverInbox($actorUrl);
if (!$inbox) {
    logNotify("No inbox discovered for $actorUrl, aborting.");
    exit("No inbox discovered\n");
}

$sent = sendSignedRequest($inbox, $activity);

if ($sent) {
    logNotify("Notification sent successfully to $mentionHandle at inbox $inbox");
    echo "✅ Notification sent to $mentionHandle\n";
} else {
    logNotify("Failed to send notification to $mentionHandle");
    echo "❌ Failed to send notification\n";
}
