<?php

declare(strict_types=1);

function syncDealFieldsFromConversation(string $latestUserMessage, array $context): array
{
    $extracted = extractDealContactFields($latestUserMessage, $context);

    return syncDealFieldsFromContactData($extracted, $context);
}

function syncDealFieldsFromContactData(array $extracted, array $context): array
{
    if (!(bool) config('CRM_SYNC_ENABLED', true)) {
        return ['ok' => true, 'skipped' => 'CRM sync disabled.'];
    }

    if (trim((string) ($context['open_line_id'] ?? '')) === '') {
        return ['ok' => true, 'skipped' => 'Not an open line dialog.'];
    }

    $phone = normalizePhoneForDeal($extracted['phone'] ?? '');
    $city = trim((string) ($extracted['city'] ?? ''));
    $fullName = normalizeClientFullName($extracted['full_name'] ?? '');
    $interestGroup = normalizeInterestGroup($extracted['interest_group'] ?? '');

    if ($phone === '' && $city === '' && $fullName === '' && $interestGroup === '') {
        return [
            'ok' => true,
            'skipped' => 'Phone, city, full name and interest group not found.',
            'extracted' => [
                'phone' => $phone,
                'city' => $city,
                'full_name' => $fullName,
                'interest_group' => $interestGroup,
            ],
        ];
    }

    $dealId = normalizeCrmId($context['deal_id'] ?? '');
    if ($dealId === '') {
        $dealId = resolveDealIdFromOpenLineDialog($context);
    }

    if ($dealId === '') {
        logMessage('warning', 'CRM sync skipped: deal id not found', [
            'context' => $context,
        ]);

        return ['ok' => true, 'skipped' => 'Deal id not found.'];
    }

    try {
        $fields = [];
        $cityMatch = null;

        if ($phone !== '') {
            $fields[(string) config('DEAL_PHONE_FIELD', 'UF_CRM_1783332070432')] = $phone;
        }

        if ($fullName !== '') {
            $fields[(string) config('DEAL_FULL_NAME_FIELD', 'UF_CRM_1784807336296')] = $fullName;
        }

        if ($interestGroup !== '') {
            $fields[(string) config('DEAL_INTEREST_GROUP_FIELD', 'UF_CRM_1784809347681')] = $interestGroup;
        }

        if ($city !== '') {
            $cityField = (string) config('DEAL_CITY_FIELD', 'UF_CRM_1693995123245');
            $cityItems = getDealUserFieldEnumItems($cityField);
            $cityMatch = matchDealCityEnumValue($city, $cityItems);

            if ($cityMatch === null) {
                logMessage('warning', 'CRM sync skipped city field: city enum value not found', [
                    'deal_id' => $dealId,
                    'city' => $city,
                    'available_cities' => array_map(static fn (array $item): string => $item['value'], $cityItems),
                ]);
            } else {
                $fields[$cityField] = $cityMatch['id'];
            }
        }

        if ($fields === []) {
            return [
                'ok' => true,
                'skipped' => 'No deal fields can be updated.',
                'extracted' => [
                    'phone' => $phone,
                    'city' => $city,
                    'full_name' => $fullName,
                    'interest_group' => $interestGroup,
                ],
                'city_match' => $cityMatch,
            ];
        }

        $update = bitrixCall('crm.deal.update', [
            'id' => $dealId,
            'fields' => $fields,
        ]);

        $phoneField = (string) config('DEAL_PHONE_FIELD', 'UF_CRM_1783332070432');
        $phoneWorkflow = null;
        if ($phone !== '' && array_key_exists($phoneField, $fields)) {
            $phoneWorkflow = startDealPhoneBusinessProcessOnce($dealId, $phone, $context);
        }

        logMessage('info', 'CRM deal fields updated from AI extraction', [
            'deal_id' => $dealId,
            'fields' => $fields,
            'extracted' => [
                'phone' => $phone,
                'city' => $city,
                'full_name' => $fullName,
                'interest_group' => $interestGroup,
            ],
            'city_match' => $cityMatch,
            'phone_workflow' => $phoneWorkflow,
            'update' => $update,
        ]);

        return [
            'ok' => true,
            'deal_id' => $dealId,
            'fields' => $fields,
            'extracted' => [
                'phone' => $phone,
                'city' => $city,
                'full_name' => $fullName,
                'interest_group' => $interestGroup,
            ],
            'city_match' => $cityMatch,
            'phone_workflow' => $phoneWorkflow,
            'bitrix' => $update,
        ];
    } catch (Throwable $exception) {
        logMessage('error', 'CRM sync failed', [
            'error' => $exception->getMessage(),
            'context' => $context,
        ]);

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

function startDealPhoneBusinessProcessOnce(string $dealId, string $phone, array $context): array
{
    if (!(bool) config('PHONE_WORKFLOW_ENABLED', true)) {
        return ['ok' => true, 'skipped' => 'Phone workflow disabled.'];
    }

    $templateId = (int) config('PHONE_WORKFLOW_TEMPLATE_ID', 1905);
    if ($templateId <= 0) {
        return ['ok' => true, 'skipped' => 'PHONE_WORKFLOW_TEMPLATE_ID is not configured.'];
    }

    if (function_exists('hasPhoneWorkflowStarted') && hasPhoneWorkflowStarted($context, $phone)) {
        return ['ok' => true, 'skipped' => 'Phone workflow already started for this phone.'];
    }

    $params = [
        'TEMPLATE_ID' => $templateId,
        'DOCUMENT_ID' => [
            'crm',
            'CCrmDocumentDeal',
            'DEAL_' . $dealId,
        ],
    ];

    $parameterName = trim((string) config('PHONE_WORKFLOW_PARAMETER_NAME', ''));
    if ($parameterName !== '') {
        $params['PARAMETERS'] = [
            $parameterName => $phone,
        ];
    }

    $response = bitrixCallRaw('bizproc.workflow.start', $params);
    $result = [
        'ok' => !isset($response['error']),
        'template_id' => $templateId,
        'deal_id' => $dealId,
        'phone' => $phone,
        'response' => $response,
    ];

    if (isset($response['error'])) {
        logMessage('error', 'Phone business process start failed', [
            'deal_id' => $dealId,
            'phone' => $phone,
            'params' => $params,
            'response' => $response,
        ]);

        return $result;
    }

    if (function_exists('markPhoneWorkflowStarted')) {
        markPhoneWorkflowStarted($context, $phone, $result);
    }

    logMessage('info', 'Phone business process started', [
        'deal_id' => $dealId,
        'phone' => $phone,
        'params' => $params,
        'response' => $response,
    ]);

    return $result;
}

function markDealClientNotResponding(array $context): array
{
    if (!(bool) config('CRM_SYNC_ENABLED', true)) {
        return ['ok' => true, 'skipped' => 'CRM sync disabled.'];
    }

    $fieldName = trim((string) config('DEAL_NO_RESPONSE_FIELD', ''));
    if ($fieldName === '') {
        return ['ok' => true, 'skipped' => 'DEAL_NO_RESPONSE_FIELD is not configured.'];
    }

    if (trim((string) ($context['open_line_id'] ?? '')) === '') {
        return ['ok' => true, 'skipped' => 'Not an open line dialog.'];
    }

    $dealId = normalizeCrmId($context['deal_id'] ?? '');
    if ($dealId === '') {
        $dealId = resolveDealIdFromOpenLineDialog($context);
    }

    if ($dealId === '') {
        logMessage('warning', 'No-response mark skipped: deal id not found', [
            'context' => $context,
        ]);

        return ['ok' => true, 'skipped' => 'Deal id not found.'];
    }

    $value = config('DEAL_NO_RESPONSE_VALUE', 'Y');
    $update = bitrixCall('crm.deal.update', [
        'id' => $dealId,
        'fields' => [
            $fieldName => $value,
        ],
    ]);

    logMessage('info', 'CRM deal marked as client not responding', [
        'deal_id' => $dealId,
        'field' => $fieldName,
        'value' => $value,
        'update' => $update,
    ]);

    return [
        'ok' => true,
        'deal_id' => $dealId,
        'field' => $fieldName,
        'value' => $value,
        'bitrix' => $update,
    ];
}

function resolveDealIdFromOpenLineDialog(array $context): string
{
    $params = [];
    $dialogId = trim((string) ($context['dialog_id'] ?? ''));
    $chatId = normalizeCrmId($context['chat_id'] ?? '');

    if ($dialogId !== '' && str_starts_with($dialogId, 'chat')) {
        $params['DIALOG_ID'] = $dialogId;
    } elseif ($chatId !== '') {
        $params['CHAT_ID'] = $chatId;
    } elseif ($dialogId !== '') {
        $dialogChatId = normalizeCrmId($dialogId);
        if ($dialogChatId !== '') {
            $params['CHAT_ID'] = $dialogChatId;
        }
    }

    if ($params === []) {
        return '';
    }

    $response = bitrixCallRaw('imopenlines.dialog.get', $params);
    if (isset($response['error'])) {
        logMessage('warning', 'Cannot resolve deal id from open line dialog', [
            'params' => $params,
            'response' => $response,
        ]);

        return '';
    }

    $result = (array) ($response['result'] ?? []);
    $dealId = extractDealIdFromDialogData($result);

    logMessage('info', 'Open line dialog CRM entity resolved', [
        'params' => $params,
        'deal_id' => $dealId,
        'entity_data_1' => findArrayValueByKey($result, ['entity_data_1', 'ENTITY_DATA_1']),
        'entity_data_2' => findArrayValueByKey($result, ['entity_data_2', 'ENTITY_DATA_2']),
    ]);

    return $dealId;
}

function extractDealIdFromDialogData(array $dialog): string
{
    $direct = normalizeCrmId(findArrayValueByKey($dialog, ['deal_id', 'DEAL_ID', 'crm_deal_id', 'CRM_DEAL_ID']));
    if ($direct !== '') {
        return $direct;
    }

    foreach (['entity_data_1', 'ENTITY_DATA_1', 'entity_data_2', 'ENTITY_DATA_2'] as $key) {
        $entityData = (string) findArrayValueByKey($dialog, [$key]);
        $entityId = extractDealIdFromEntityDataString($entityData);
        if ($entityId !== '') {
            return $entityId;
        }
    }

    $entityType = strtoupper((string) findArrayValueByKey($dialog, ['crm_entity_type', 'CRM_ENTITY_TYPE', 'entity_type', 'ENTITY_TYPE']));
    if (in_array($entityType, ['DEAL', 'D'], true)) {
        return normalizeCrmId(findArrayValueByKey($dialog, ['crm_entity_id', 'CRM_ENTITY_ID', 'entity_id', 'ENTITY_ID']));
    }

    return '';
}

function extractDealIdFromEntityDataString(string $entityData): string
{
    $entityData = trim($entityData);
    if ($entityData === '') {
        return '';
    }

    $parts = explode('|', $entityData);
    foreach ($parts as $index => $part) {
        if (strtoupper(trim($part)) !== 'DEAL') {
            continue;
        }

        $entityId = normalizeCrmId($parts[$index + 1] ?? '');
        if ($entityId !== '') {
            return $entityId;
        }
    }

    return '';
}

function findArrayValueByKey(array $data, array $keys)
{
    $normalizedKeys = array_map('strtoupper', $keys);
    foreach ($data as $key => $value) {
        if (in_array(strtoupper((string) $key), $normalizedKeys, true) && !is_array($value) && $value !== '') {
            return $value;
        }

        if (is_array($value)) {
            $found = findArrayValueByKey($value, $keys);
            if ($found !== null && $found !== '') {
                return $found;
            }
        }
    }

    return null;
}

function extractDealContactFields(string $latestUserMessage, array $context): array
{
    $apiKey = trim((string) config('OPENAI_API_KEY', ''));
    if ($apiKey === '') {
        return ['phone' => '', 'city' => '', 'full_name' => '', 'interest_group' => ''];
    }

    $historyLines = [];
    if (function_exists('getChatHistory')) {
        foreach (getChatHistory($context) as $message) {
            $role = (string) ($message['role'] ?? '');
            $content = trim((string) ($message['content'] ?? ''));
            if ($content !== '' && $role === 'user') {
                $historyLines[] = $role . ': ' . $content;
            }
        }
    }

    $historyText = implode("\n", array_slice($historyLines, -10));
    $requestId = 'crm-extract-' . date('YmdHis') . '-' . bin2hex(random_bytes(4));
    $payload = [
        'model' => (string) config('OPENAI_EXTRACTION_MODEL', config('OPENAI_MODEL', 'gpt-4.1-mini')),
        'messages' => [
            [
                'role' => 'system',
                'content' => implode("\n", [
                    'Extract the client phone number, city, full name and interest group from the conversation.',
                    'Return only JSON without explanations: {"phone":"","city":"","full_name":"","interest_group":""}.',
                    'Fill phone, city and full_name only if the client explicitly provided them.',
                    'If a value is missing or uncertain, return an empty string for it.',
                    'If the city is written in Russian, keep the normal Russian city name.',
                    'For full_name, use only the client name, not manager, bot, product or company names.',
                    'Classify interest_group as exactly one of: "техника", "запчасти", "сервис", "другое".',
                    'Use "техника" for vehicles and machinery, "запчасти" for parts and accessories, "сервис" for repair, maintenance or diagnostics.',
                    'Use an empty interest_group if it cannot be determined from the client messages.',
                ]),
            ],
            [
                'role' => 'user',
                'content' => "Conversation history:\n" . $historyText . "\n\nLatest client message:\n" . $latestUserMessage,
            ],
        ],
        'temperature' => 0,
        'response_format' => [
            'type' => 'json_object',
        ],
    ];

    logMessage('info', 'OpenAI CRM extraction request prepared', [
        'request_id' => $requestId,
        'context' => $context,
        'payload' => $payload,
    ]);

    $response = openAiChatCompletions($payload, $apiKey, $requestId);
    $content = trim((string) ($response['choices'][0]['message']['content'] ?? ''));
    $decoded = json_decode($content, true);

    if (!is_array($decoded)) {
        logMessage('warning', 'OpenAI CRM extraction returned invalid JSON', [
            'request_id' => $requestId,
            'content' => $content,
        ]);

        return ['phone' => '', 'city' => '', 'full_name' => '', 'interest_group' => ''];
    }

    $result = [
        'phone' => trim((string) ($decoded['phone'] ?? '')),
        'city' => trim((string) ($decoded['city'] ?? '')),
        'full_name' => trim((string) ($decoded['full_name'] ?? '')),
        'interest_group' => normalizeInterestGroup($decoded['interest_group'] ?? ''),
    ];

    logMessage('info', 'OpenAI CRM extraction response received', [
        'request_id' => $requestId,
        'result' => $result,
        'usage' => $response['usage'] ?? null,
    ]);

    return $result;
}

function getDealUserFieldEnumItems(string $fieldName): array
{
    $field = findDealUserFieldByName($fieldName);
    if ($field === null) {
        throw new RuntimeException('Deal user field not found: ' . $fieldName);
    }

    return normalizeUserFieldEnumItems((array) ($field['LIST'] ?? []));
}

function findDealUserFieldByName(string $fieldName): ?array
{
    $start = 0;

    do {
        $response = bitrixCall('crm.deal.userfield.list', [
            'filter' => [
                'FIELD_NAME' => $fieldName,
            ],
            'start' => $start,
        ]);

        foreach ((array) ($response['result'] ?? []) as $field) {
            $field = (array) $field;
            if ((string) ($field['FIELD_NAME'] ?? '') === $fieldName) {
                return $field;
            }
        }

        $next = $response['next'] ?? null;
        $start = is_numeric($next) ? (int) $next : 0;
    } while ($start > 0);

    return null;
}

function normalizeUserFieldEnumItems(array $items): array
{
    $result = [];

    foreach ($items as $item) {
        $item = (array) $item;
        $result[] = [
            'id' => isset($item['ID']) ? (string) $item['ID'] : '',
            'value' => (string) ($item['VALUE'] ?? ''),
            'sort' => isset($item['SORT']) ? (int) $item['SORT'] : null,
            'def' => (string) ($item['DEF'] ?? 'N'),
            'xml_id' => (string) ($item['XML_ID'] ?? ''),
        ];
    }

    usort($result, static function (array $a, array $b): int {
        return [$a['sort'] ?? 500, $a['value'], $a['id']] <=> [$b['sort'] ?? 500, $b['value'], $b['id']];
    });

    return $result;
}

function matchDealCityEnumValue(string $city, array $items): ?array
{
    $needle = normalizeCityName($city);
    if ($needle === '') {
        return null;
    }

    $best = null;
    foreach ($items as $item) {
        $value = (string) ($item['value'] ?? '');
        $normalized = normalizeCityName($value);
        if ($normalized === '') {
            continue;
        }

        if ($normalized === $needle) {
            return $item + ['match_type' => 'exact', 'score' => 100];
        }

        if (str_contains($normalized, $needle) || str_contains($needle, $normalized)) {
            $score = 90 - abs(strlen($normalized) - strlen($needle));
            if ($best === null || $score > $best['score']) {
                $best = $item + ['match_type' => 'partial', 'score' => $score];
            }
            continue;
        }

        $distance = levenshtein($needle, $normalized);
        $maxLength = max(strlen($needle), strlen($normalized), 1);
        $score = (int) round(100 - min(100, ($distance / $maxLength) * 100));
        if ($score >= 70 && ($best === null || $score > $best['score'])) {
            $best = $item + ['match_type' => 'nearest', 'score' => $score];
        }
    }

    return $best;
}

function normalizeCityName(string $city): string
{
    $city = trim($city);
    if ($city === '') {
        return '';
    }

    $city = str_replace('ё', 'е', function_exists('mb_strtolower') ? mb_strtolower($city, 'UTF-8') : strtolower($city));
    $city = preg_replace('/\b(город|гор|г)\.?\b/u', ' ', $city);
    $city = preg_replace('/[^\p{L}\p{N}\s-]+/u', ' ', $city);
    $city = preg_replace('/[\s-]+/u', ' ', (string) $city);

    return trim((string) $city);
}

function normalizePhoneForDeal(string $phone): string
{
    $phone = trim($phone);
    if ($phone === '') {
        return '';
    }

    $digits = preg_replace('/\D+/', '', $phone);
    if ($digits === null || strlen($digits) < 10) {
        return '';
    }

    if (strlen($digits) === 11 && str_starts_with($digits, '8')) {
        $digits = '7' . substr($digits, 1);
    }

    if (strlen($digits) === 10) {
        $digits = '7' . $digits;
    }

    if (strlen($digits) !== 11 || !str_starts_with($digits, '7')) {
        return '';
    }

    return sprintf(
        '+7 (%s) %s-%s-%s',
        substr($digits, 1, 3),
        substr($digits, 4, 3),
        substr($digits, 7, 2),
        substr($digits, 9, 2)
    );
}

function normalizeClientFullName($fullName): string
{
    $fullName = trim((string) $fullName);
    if ($fullName === '') {
        return '';
    }

    $fullName = html_entity_decode(strip_tags($fullName), ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $fullName = preg_replace('/\s+/u', ' ', (string) $fullName);
    $fullName = trim((string) $fullName);

    $length = function_exists('mb_strlen') ? mb_strlen($fullName, 'UTF-8') : strlen($fullName);
    if ($fullName === '' || $length < 2) {
        return '';
    }

    return $fullName;
}

function normalizeCrmId($value): string
{
    $value = trim((string) $value);
    if ($value === '') {
        return '';
    }

    if (preg_match('/\d+/', $value, $matches)) {
        return $matches[0];
    }

    return '';
}
