<?php
declare(strict_types=1);

function botInboxDirectory(): string { return dirname((string) config('STATE_FILE')) . '/bot_inbox'; }

function enqueueBotInboxEvent(array $payload): string
{
    $message = normalizeBitrixBotPayload($payload);
    $key = hash('sha256', $message['event'] . ':' . ($message['message_id'] ?: json_encode($payload)));
    $path = botInboxDirectory() . '/' . $key . '.json';
    updateJsonFile($path, [], static function (array $record) use ($payload, $message): array {
        if ($record !== []) return $record;
        // Authentication is held by CRest, not by a queued webhook payload.
        unset($payload['auth'], $payload['AUTH']);
        return ['status' => 'pending', 'received_at' => date('c'), 'attempts' => 0,
            'dialog_id' => $message['dialog_id'], 'message_id' => $message['message_id'], 'payload' => $payload];
    });
    return $path;
}

function recordBotInboxDelivery(array $context, string $clientMessage, string $answer, array $contact, array $reply): void
{
    $path = $GLOBALS['active_bot_inbox_path'] ?? null;
    if (!is_string($path)) return;
    updateJsonFile($path, [], static function (array $r) use ($context, $clientMessage, $answer, $contact, $reply): array {
        $r['delivery'] = ['at' => date('c'), 'context' => $context, 'client_message' => $clientMessage,
            'answer' => $answer, 'contact' => $contact, 'reply_id' => $reply['result']];
        return $r;
    });
}

function recoverBotInboxDelivery(array $record): array
{
    $d = $record['delivery']; $context = $d['context'];
    $lock = acquireBotDialogLock((string) $context['dialog_id']);
    try {
        $chat = chatStoreRead(chatHistoryKey($context));
        $messages = (array) ($chat['messages'] ?? []); $last = $messages === [] ? [] : end($messages);
        // Never replace a later client conversation with recovery of an older reply.
        if (strtotime((string) ($last['created_at'] ?? '')) > strtotime($d['at']) + 2
            && ($last['content'] ?? '') !== $d['answer']) return ['ok' => true, 'recovered' => 'superseded_by_newer_conversation'];
        $pair = array_slice($messages, -2);
        if (count($pair) !== 2 || ($pair[0]['role'] ?? '') !== 'user' || ($pair[0]['content'] ?? '') !== $d['client_message']
            || ($pair[1]['role'] ?? '') !== 'assistant' || ($pair[1]['content'] ?? '') !== $d['answer']) {
            appendChatHistoryMessages($context, [['role'=>'user','content'=>$d['client_message']],['role'=>'assistant','content'=>$d['answer']]]);
        }
        $contact = mergeChatContactData($context, $d['contact']);
        try { $crm = syncDealFieldsFromContactData($contact, $context); }
        catch (Throwable $e) { $crm = ['ok'=>false,'error'=>$e->getMessage()]; }
        recordChatCrmSyncResult($context, $crm);
        if (isContactDataComplete($contact)) clearChatAwaitingClientReply($context);
        else markChatAwaitingClientReply($context);
        return ['ok'=>true,'recovered'=>'delivered_reply_bookkeeping'];
    } finally { releaseBotDialogLock($lock); }
}

function processBotInboxEvent(string $path): array
{
    ensureJsonDirectory(dirname($path));
    $guard = @fopen($path . '.processing.lock', 'c');
    if (!$guard && is_file($path . '.processing.lock')) $guard = @fopen($path . '.processing.lock', 'r');
    if (!$guard) throw new RuntimeException('Cannot open inbox processing lock');
    @chmod($path . '.processing.lock', 0664);
    if (!flock($guard, LOCK_EX|LOCK_NB)) { fclose($guard); return ['ok'=>true,'skipped'=>'busy']; }
    try {
        $r = readJsonFile($path);
        if (($r['status']??'')==='done') return ['ok'=>true,'skipped'=>'done'];
        if (strtotime((string)($r['next_attempt_at']??''))>time()) return ['ok'=>true,'skipped'=>'not_due'];
        $message = normalizeBitrixBotPayload($r['payload']);
        $chat = chatStoreRead('dialog:' . $message['dialog_id']);
        if (empty($r['delivery']) && ($chat['followups_stopped_reason']??'')==='user_requested_black_stage_20261001'
            && strtotime((string)($chat['followups_stopped_at']??'')) >= strtotime($r['received_at'])) {
            $r['status']='done';$r['skipped']='closed_by_user_after_receipt';writeJsonFile($path,$r);return ['ok'=>true,'skipped'=>$r['skipped']];
        }
        if (empty($r['delivery']) && (int)($r['attempts']??0)>0) {
            foreach ((array)($chat['messages']??[]) as $m) {
                if (($m['role']??'')==='user' && strtotime((string)($m['created_at']??''))>strtotime($r['received_at'])+2) {
                    $r['status']='done';$r['skipped']='superseded_by_newer_conversation';writeJsonFile($path,$r);return ['ok'=>true,'skipped'=>$r['skipped']];
                }
            }
        }
        $eventId = 'bot:' . ($message['message_id'] ?: sha1(json_encode($r['payload'])));
        // A previous process died holding a claim, but the processing lock is now free.
        if (($r['status']??'')==='processing' && empty($r['delivery'])) unmarkEventProcessed($eventId);
        $r['status']='processing';$r['started_at']=date('c');$r['attempts']=(int)($r['attempts']??0)+1;
        writeJsonFile($path,$r);$GLOBALS['active_bot_inbox_path']=$path;
        try {
            $result = !empty($r['delivery']) ? recoverBotInboxDelivery($r) : handleBitrixBotEvent($r['payload']);
            if (($result['ok']??false)!==true) throw new RuntimeException('Bot processing did not complete: '.json_encode($result));
            updateJsonFile($path,[],static function(array $r):array{$r['status']='done';$r['finished_at']=date('c');unset($r['last_error'],$r['next_attempt_at']);return$r;});
            return ['ok'=>true];
        } catch(Throwable $e) {
            updateJsonFile($path,[],static function(array $r)use($e):array{$r['status']='pending';$r['last_error']=$e->getMessage();$r['next_attempt_at']=date('c',time()+min(3600,60*max(1,(int)$r['attempts'])));return$r;});
            logMessage('error','Durable bot event pending retry',['dialog_id'=>$message['dialog_id'],'message_id'=>$message['message_id'],'error'=>$e->getMessage()]);
            return ['ok'=>false,'error'=>$e->getMessage()];
        } finally { unset($GLOBALS['active_bot_inbox_path']); }
    } finally { flock($guard,LOCK_UN);fclose($guard); }
}
