<?php

declare(strict_types=1);

if (PHP_SAPI !== 'cli') exit(1);

// This test uses isolated JSON files and a fake REST transport, never the portal.
$testRoot = sys_get_temp_dir() . '/darex_dialog_test_' . bin2hex(random_bytes(5));
mkdir($testRoot, 0700, true);
$testConfig = ['CHAT_HISTORY_FILE' => $testRoot . '/history.json', 'STATE_FILE' => $testRoot . '/state.json'];
function config(string $key, $default = null) { return $GLOBALS['testConfig'][$key] ?? $default; }
function logMessage(...$args): void {}
function bitrixCall(string $method, array $params): array {
    if ($method !== 'crm.deal.userfield.list') throw new RuntimeException('Unexpected REST call: ' . $method);
    return ['result' => [['FIELD_NAME' => $params['filter']['FIELD_NAME'], 'LIST' => [
        ['ID' => '178', 'VALUE' => 'Владимир'], ['ID' => '179', 'VALUE' => 'Нижний Новгород'],
    ]]]];
}
require_once __DIR__ . '/../src/storage.php';
require_once __DIR__ . '/../src/history.php';
require_once __DIR__ . '/../src/crm.php';
require_once __DIR__ . '/../src/openai.php';

$checks = [];
foreach (['89506273943', '9506273943', '+7 (950) 627-39-43', '7 950 627 39 43'] as $phone) {
    $checks['phone_' . $phone] = normalizeCollectedPhone($phone) === '+7 (950) 627-39-43';
}
foreach (['+44 74 5470 8519', '+1234567890', '+7950627394', '123', ''] as $phone) {
    $checks['invalid_' . $phone] = normalizeCollectedPhone($phone) === '';
}
$checks['phone_from_text'] = phoneFromClientText('Мой телефон 8 (950) 627-39-43, город Казань') === '+7 (950) 627-39-43';
$checks['article_not_phone'] = !isExplicitClientPhoneMessage('Артикул 1234567890');
$checks['standalone_phone'] = isExplicitClientPhoneMessage('89506273943');
$checks['international_not_complete'] = !isContactDataComplete(['phone' => '+44 74 5470 8519', 'city' => 'Москва']);
$checks['phone_without_city'] = !isContactDataComplete(['phone' => '89506273943']);
$checks['complete'] = isContactDataComplete(['phone' => '89506273943', 'city' => 'Казань']);
$checks['service_import'] = isConnectorServiceText('[Системное сообщение] Импорт диалога завершен');
$checks['service_call'] = isConnectorServiceText('Входящий звонок');
$checks['real_question_kept'] = !isConnectorServiceText('Почему пропущенный звонок?');
$checks['city_explicit'] = clientCityFromExplicitText('Здравствуйте, цена с доставкой г.Владимир', [['value' => 'Владимир']]) === 'Владимир';
$checks['pickup_not_residence'] = isPickupLocationOnly('Могу забрать из Нижнего Новгорода');
$checks['residence_explicit'] = !isPickupLocationOnly('Я из Владимира, могу забрать завтра');
$known = ['phone' => '89506273943', 'city' => 'Владимир'];
$checks['known_phone_request_blocked'] = conversationAnswerIssues('Оставьте номер телефона.', $known) !== [];
$checks['known_city_request_blocked'] = conversationAnswerIssues('Из какого вы города?', $known) !== [];
$checks['false_receipt_blocked'] = conversationAnswerIssues('Спасибо, номер зафиксировала и передала менеджеру.', []) !== [];
$checks['unconfirmed_handoff_blocked'] = conversationAnswerIssues('Передала данные менеджеру.', $known) !== [];
$checks['useful_answer_kept'] = conversationAnswerIssues('Видеообзор нужно уточнить у менеджера.', $known) === [];
$checks['duplicate_question'] = conversationAnswerIssues('Можно дистанционно?', [], 'Можно дистанционно?') !== [];
$checks['multiple_questions'] = conversationAnswerIssues('Какая модель? Какой год?', []) !== [];
$checks['fallback_safe'] = conversationAnswerIssues(safeConversationAnswer('Оставьте номер телефона.', $known, ''), $known) === [];

$context = ['dialog_id' => 'test-one', 'deal_id' => '10', 'open_line_id' => '227'];
mergeChatContactData($context, ['phone' => '89506273943']);
mergeChatContactData($context, ['city' => 'Владимир']);
$checks['contacts_separate_messages'] = isContactDataComplete(getChatContactData($context));
appendChatHistory($context, 'assistant', 'Первый ответ.');
$stale = readChatFollowupOperationalState(chatHistoryKey($context));
appendChatHistory($context, 'user', 'Следующее сообщение');
saveChatFollowupOperationalState(chatHistoryKey($context), $stale);
$checks['stale_snapshot_cannot_erase_history'] = count(readChatFollowupOperationalState(chatHistoryKey($context))['messages']) === 2;
$checks['greeting_remembered'] = getChatConversationState($context)['assistant_has_replied'] === true;
$other = ['dialog_id' => 'test-two', 'deal_id' => '10', 'open_line_id' => '227'];
$deal = ['ID' => '10', 'UF_CRM_1783332070432' => '89506273943', 'UF_CRM_1693995123245' => '178'];
hydrateChatContactsFromDeal($other, $deal);
$checks['new_chat_hydrates_phone_city'] = getChatContactData($other)['city'] === 'Владимир' && isContactDataComplete(getChatContactData($other));
$checks['new_chat_no_other_history'] = getChatHistory($other) === [];
$wrong = ['dialog_id' => 'test-wrong', 'deal_id' => '11'];
hydrateChatContactsFromDeal($wrong, $deal);
$checks['wrong_deal_not_hydrated'] = getChatContactData($wrong)['phone'] === '';
recordChatCrmSyncResult($context, ['ok' => false, 'error' => 'OPERATION_TIME_LIMIT']);
$state = readChatFollowupOperationalState(chatHistoryKey($context));
$checks['failed_crm_write_pending'] = isset($state['crm_sync_retry']['next_attempt_at']);
recordChatCrmSyncResult($context, ['ok' => true]);
$checks['successful_crm_write_clears_retry'] = !isset(readChatFollowupOperationalState(chatHistoryKey($context))['crm_sync_retry']);
markQualificationWorkflowPending($context, '10', ['skipped' => 'missing branch']);
$checks['pending_workflow_cooldown'] = (retryPendingQualificationDistributionWorkflow($context)['skipped'] ?? '') === 'Qualification workflow retry cooldown.';
markQualificationWorkflowStarted($context, '10', ['response' => ['result' => 'workflow-test']]);
$checks['started_workflow_not_restarted'] = str_contains(startDealQualificationDistributionWorkflow('10', [], [], $context)['skipped'] ?? '', 'already started');

$testConfig['OPENAI_API_KEY'] = 'offline-test-no-network';
$testConfig['PROMPT_CACHE_FILE'] = $testRoot . '/prompts.json';
writeJsonFile($testConfig['PROMPT_CACHE_FILE'], ['version' => 2, 'expires_at' => time() + 300, 'data' => ['system' => [], 'corrections' => []]]);
$requests = [];
$responses = [];
$completion = static function (array $payload) use (&$requests, &$responses): array {
    $requests[] = $payload;
    if ($responses === []) throw new RuntimeException('Unexpected additional AI request');
    $response = array_shift($responses);
    if ($response instanceof Throwable) throw $response;
    return ['choices' => [['message' => ['content' => json_encode($response, JSON_UNESCAPED_UNICODE)]]]];
};
$responses = [
    ['answer' => 'Оставьте номер телефона.', 'contact' => []],
    ['answer' => 'Возможность дистанционной покупки уточнит менеджер.', 'contact' => ['phone' => '89999999999']],
];
$answer = generateAiAnswerResult('Можно дистанционно', $context, $completion);
$checks['repair_once'] = count($requests) === 2;
$checks['repaired_question_not_sent'] = $answer['answer'] === 'Возможность дистанционной покупки уточнит менеджер.';
$checks['repair_cannot_change_contacts'] = $answer['contact']['phone'] === '+7 (950) 627-39-43';
$requests = [];
$responses = [
    ['answer' => 'Спасибо, номер зафиксировала и передала менеджеру.', 'contact' => ['phone' => '89999999999']],
    new RuntimeException('Simulated AI timeout'),
];
$answer = generateAiAnswerResult('Завтра можно посмотреть?', ['dialog_id' => 'no-phone'], $completion);
$checks['hallucinated_phone_rejected'] = $answer['contact']['phone'] === '';
$checks['failed_repair_has_safe_answer'] = conversationAnswerIssues($answer['answer'], []) === [] && $answer['answer'] !== '';
$requests = [];
$responses = [['answer' => 'Стоимость доставки уточнит менеджер.', 'contact' => []]];
$answer = generateAiAnswerResult('Здравствуйте, цена с доставкой г.Владимир', ['dialog_id' => 'new-city'], $completion);
$checks['explicit_city_survives_model_omission'] = $answer['contact']['city'] === 'Владимир';
$checks['normal_answer_no_extra_request'] = count($requests) === 1;
$requests = [];
$responses = [['answer' => 'Уточню возможность самовывоза.', 'contact' => ['city' => 'Нижний Новгород', 'branch' => 'Нижний Новгород']]];
$answer = generateAiAnswerResult('Могу забрать из Нижнего Новгорода', $context, $completion);
$checks['pickup_preserves_client_city_pipeline'] = $answer['contact']['city'] === 'Владимир';
$checks['pickup_branch_stays_separate'] = $answer['contact']['branch'] === 'Нижний Новгород';

$failed = array_keys(array_filter($checks, static fn ($ok) => !$ok));
echo json_encode(['ok' => $failed === [], 'checks' => count($checks), 'failed' => $failed], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . PHP_EOL;
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($testRoot, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $file) { $file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname()); }
rmdir($testRoot);
exit($failed === [] ? 0 : 1);
