<?php

declare(strict_types=1);

if (PHP_SAPI !== 'cli') {
    http_response_code(403);
    echo "This script can be run only from CLI.\n";
    exit(1);
}

require_once __DIR__ . '/../src/config.php';
require_once __DIR__ . '/../src/logger.php';
require_once __DIR__ . '/../src/storage.php';
require_once __DIR__ . '/../src/history.php';
require_once __DIR__ . '/../src/bitrix.php';
require_once __DIR__ . '/../src/bitrix_bot.php';
require_once __DIR__ . '/../src/crm.php';

$options = getopt('', ['dry-run', 'delay::', 'help']);
if (isset($options['help'])) {
    echo "Usage: php tools/send_inactivity_followups.php [--dry-run] [--delay=3600]\n";
    echo "By default uses FOLLOWUP_OFFSETS_SECONDS and NO_RESPONSE_MARK_AFTER_SECONDS from .env.php.\n";
    exit(0);
}

$dryRun = isset($options['dry-run']);
$delaySeconds = isset($options['delay'])
    ? max(60, (int) $options['delay'])
    : max(60, (int) config('FOLLOWUP_DELAY_SECONDS', 3600));

$lockPath = dirname((string) config('STATE_FILE')) . '/followup.lock';
$lockHandle = fopen($lockPath, 'c');
if ($lockHandle === false) {
    fwrite(STDERR, 'Cannot open followup lock file: ' . $lockPath . PHP_EOL);
    exit(1);
}

if (!flock($lockHandle, LOCK_EX | LOCK_NB)) {
    echo json_encode([
        'ok' => true,
        'skipped' => 'Another followup process is already running.',
        'lock_file' => $lockPath,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
    exit(0);
}

try {
    $result = sendInactivityFollowups($delaySeconds, $dryRun);
    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
} catch (Throwable $exception) {
    fwrite(STDERR, $exception->getMessage() . PHP_EOL);
    exit(1);
} finally {
    flock($lockHandle, LOCK_UN);
    fclose($lockHandle);
}

function sendInactivityFollowups(int $delaySeconds, bool $dryRun = false): array
{
    if (!(bool) config('FOLLOWUP_ENABLED', true)) {
        return ['ok' => true, 'skipped' => 'Followups disabled.'];
    }

    $offsets = normalizeFollowupOffsets(config('FOLLOWUP_OFFSETS_SECONDS', []));
    if ($offsets === []) {
        $offsets = [$delaySeconds];
    }

    $history = listChatHistories();
    $chats = (array) ($history['chats'] ?? []);
    $now = time();
    $result = [
        'ok' => true,
        'dry_run' => $dryRun,
        'offsets_seconds' => $offsets,
        'no_response_mark_after_seconds' => (int) config('NO_RESPONSE_MARK_AFTER_SECONDS', 259200),
        'checked' => 0,
        'sent' => [],
        'marked_no_response' => [],
        'skipped' => [],
    ];

    foreach ($chats as $chatKey => $chat) {
        $result['checked']++;
        $chat = (array) $chat;
        $context = (array) ($chat['context'] ?? []);
        $messages = (array) ($chat['messages'] ?? []);
        $lastMessage = lastChatMessage($messages);
        $contact = (array) ($chat['contact'] ?? []);

        if ($lastMessage === null) {
            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'empty_history'];
            continue;
        }

        if (isContactDataComplete($contact)) {
            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'contact_already_complete'];
            continue;
        }

        if ((string) ($lastMessage['role'] ?? '') !== 'assistant') {
            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'last_message_is_not_assistant'];
            continue;
        }

        if ((bool) config('FOLLOWUP_ONLY_OPEN_LINES', true) && trim((string) ($context['open_line_id'] ?? '')) === '') {
            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'not_open_line'];
            continue;
        }

        $startedAtRaw = trim((string) ($chat['followup_started_at'] ?? ''));
        if ($startedAtRaw === '') {
            $startedAtRaw = (string) ($lastMessage['created_at'] ?? '');
        }

        $startedAt = strtotime($startedAtRaw);
        if ($startedAt === false) {
            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'invalid_followup_start_time'];
            continue;
        }

        $sentStages = (array) ($chat['followups']['sent'] ?? []);
        $dueStage = null;
        foreach ($offsets as $index => $offset) {
            if (isset($sentStages[(string) $index])) {
                continue;
            }

            if (($now - $startedAt) >= $offset) {
                $dueStage = [
                    'index' => $index,
                    'offset' => $offset,
                    'message' => followupMessageForStage($index, $contact),
                ];
            }

            break;
        }

        $noResponseDue = isNoResponseMarkDue($chat, $offsets, $startedAt, $now);
        if ($dueStage !== null || $noResponseDue) {
            $stageCheck = followupDealStageCheck($context);
            if (($stageCheck['allowed'] ?? false) !== true) {
                $result['skipped'][] = [
                    'chat' => $chatKey,
                    'reason' => (string) ($stageCheck['reason'] ?? 'deal_stage_check_failed'),
                    'deal' => $stageCheck,
                ];

                logMessage('info', 'Inactivity followup skipped because deal left initial stage', [
                    'chat' => $chatKey,
                    'context' => $context,
                    'stage_check' => $stageCheck,
                ]);
                continue;
            }
        }

        if ($dueStage === null) {
            $markResult = maybeMarkNoResponse($chatKey, $chat, $context, $contact, $offsets, $startedAt, $now, $dryRun);
            if ($markResult !== null) {
                $result['marked_no_response'][] = $markResult;
                continue;
            }

            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'not_due'];
            continue;
        }

        $dialogId = trim((string) ($context['dialog_id'] ?? ''));
        if ($dialogId === '') {
            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'dialog_id_empty'];
            continue;
        }

        $botId = (int) ($context['bot_id'] ?? 0);
        if ($botId <= 0) {
            $botId = (int) config('BITRIX_OPENLINE_BOT_ID', 0);
        }
        if ($botId <= 0) {
            $botId = (int) config('BITRIX_AI_BOT_ID', 0);
        }

        if ($botId <= 0) {
            $result['skipped'][] = ['chat' => $chatKey, 'reason' => 'bot_id_empty'];
            continue;
        }

        if ($dryRun) {
            $result['sent'][] = [
                'chat' => $chatKey,
                'dialog_id' => $dialogId,
                'bot_id' => $botId,
                'stage' => $dueStage['index'],
                'offset_seconds' => $dueStage['offset'],
                'started_at' => date('c', $startedAt),
                'message' => $dueStage['message'],
            ];
            continue;
        }

        markChatFollowupSent($chatKey, (int) $dueStage['index'], $dueStage['message']);
        $response = sendBitrixBotMessage($dialogId, $dueStage['message'], $botId);
        appendChatHistory($context, 'assistant', $dueStage['message']);

        logMessage('info', 'Inactivity followup sent', [
            'chat' => $chatKey,
            'dialog_id' => $dialogId,
            'bot_id' => $botId,
            'stage' => $dueStage['index'],
            'offset_seconds' => $dueStage['offset'],
            'started_at' => date('c', $startedAt),
            'response' => $response,
        ]);

        $result['sent'][] = [
            'chat' => $chatKey,
            'dialog_id' => $dialogId,
            'bot_id' => $botId,
            'stage' => $dueStage['index'],
            'response' => $response['result'] ?? null,
        ];
    }

    return $result;
}

function maybeMarkNoResponse(
    string $chatKey,
    array $chat,
    array $context,
    array $contact,
    array $offsets,
    int $startedAt,
    int $now,
    bool $dryRun
): ?array {
    if (!isNoResponseMarkDue($chat, $offsets, $startedAt, $now)) {
        return null;
    }

    if ($dryRun) {
        return [
            'chat' => $chatKey,
            'context' => $context,
            'contact' => $contact,
            'dry_run' => true,
        ];
    }

    $crmResult = markDealClientNotResponding($context);
    markChatNoResponseMarked($chatKey, $crmResult);

    return [
        'chat' => $chatKey,
        'crm' => $crmResult,
    ];
}

function isNoResponseMarkDue(array $chat, array $offsets, int $startedAt, int $now): bool
{
    $markAfterSeconds = max(0, (int) config('NO_RESPONSE_MARK_AFTER_SECONDS', 259200));
    $sentStages = (array) ($chat['followups']['sent'] ?? []);

    return $markAfterSeconds > 0
        && count($sentStages) >= count($offsets)
        && ($now - $startedAt) >= $markAfterSeconds
        && trim((string) ($chat['no_response_marked_at'] ?? '')) === '';
}

function followupDealStageCheck(array $context): array
{
    if (!(bool) config('FOLLOWUP_REQUIRE_INITIAL_DEAL_STAGE', true)) {
        return ['allowed' => true, 'reason' => 'stage_check_disabled'];
    }

    try {
        $dealId = normalizeCrmId($context['deal_id'] ?? '');
        if ($dealId === '') {
            $dealId = resolveDealIdFromOpenLineDialog($context);
        }
        if ($dealId === '') {
            return ['allowed' => false, 'reason' => 'deal_not_found'];
        }

        $response = bitrixCall('crm.deal.get', ['id' => $dealId]);
        $deal = (array) ($response['result'] ?? []);
        $categoryId = (string) ($deal['CATEGORY_ID'] ?? '');
        $stageId = (string) ($deal['STAGE_ID'] ?? '');
        $initialCategoryId = (string) config('FOLLOWUP_INITIAL_CATEGORY_ID', '0');
        $initialStageId = (string) config('FOLLOWUP_INITIAL_STAGE_ID', 'NEW');
        $allowed = $categoryId === $initialCategoryId && $stageId === $initialStageId;

        return [
            'allowed' => $allowed,
            'reason' => $allowed ? 'deal_is_in_initial_stage' : 'deal_stage_changed',
            'deal_id' => $dealId,
            'category_id' => $categoryId,
            'stage_id' => $stageId,
            'initial_category_id' => $initialCategoryId,
            'initial_stage_id' => $initialStageId,
        ];
    } catch (Throwable $exception) {
        logMessage('error', 'Cannot verify deal stage before inactivity followup', [
            'context' => $context,
            'error' => $exception->getMessage(),
        ]);

        return [
            'allowed' => false,
            'reason' => 'deal_stage_check_failed',
            'error' => $exception->getMessage(),
        ];
    }
}

function normalizeFollowupOffsets($offsets): array
{
    if (is_string($offsets)) {
        $offsets = array_map('trim', explode(',', $offsets));
    }

    if (!is_array($offsets)) {
        return [];
    }

    $result = [];
    foreach ($offsets as $offset) {
        $seconds = (int) $offset;
        if ($seconds > 0) {
            $result[] = $seconds;
        }
    }

    $result = array_values(array_unique($result));
    sort($result, SORT_NUMERIC);

    return $result;
}

function followupMessageForStage(int $stageIndex, array $contact = []): string
{
    $phone = trim((string) ($contact['phone'] ?? ''));
    $city = trim((string) ($contact['city'] ?? ''));
    $messageGroup = 'FOLLOWUP_MESSAGES';

    if ($phone !== '' && $city === '') {
        $messageGroup = 'FOLLOWUP_MESSAGES_NEED_CITY';
    } elseif ($phone === '' && $city !== '') {
        $messageGroup = 'FOLLOWUP_MESSAGES_NEED_PHONE';
    }

    $messages = config($messageGroup, []);
    if (is_array($messages) && trim((string) ($messages[$stageIndex] ?? '')) !== '') {
        return trim((string) $messages[$stageIndex]);
    }

    $messages = config('FOLLOWUP_MESSAGES', []);
    if (is_array($messages) && trim((string) ($messages[$stageIndex] ?? '')) !== '') {
        return trim((string) $messages[$stageIndex]);
    }

    $message = trim((string) config('FOLLOWUP_MESSAGE', 'Подскажите, пожалуйста, актуален ли еще ваш вопрос?'));
    if ($message === '') {
        throw new RuntimeException('FOLLOWUP_MESSAGE is empty.');
    }

    return $message;
}

function lastChatMessage(array $messages): ?array
{
    for ($index = count($messages) - 1; $index >= 0; $index--) {
        if (is_array($messages[$index] ?? null)) {
            return $messages[$index];
        }
    }

    return null;
}
