<?php

declare(strict_types=1);

require_once __DIR__ . '/storage.php';

function ensureDealAiContact(string $dealId, string $phone): array
{
    $phone = normalizePhoneForDeal($phone);
    if (!ctype_digit($dealId) || (int) $dealId <= 0 || $phone === '') {
        return ['ok' => false, 'mode' => 'code', 'error' => 'Invalid deal or phone.'];
    }
    $directory = dirname((string) config('STATE_FILE')) . '/contact_sync';
    try {
        // Serialize both updates of one deal and creation for the same phone in different chats.
        $result = withJsonFileLock($directory . '/deal_' . $dealId . '.json', LOCK_EX,
            static function () use ($directory, $dealId, $phone): array {
                $path = $directory . '/phone_' . hash('sha256', $phone) . '.json';
                return withJsonFileLock($path, LOCK_EX, static function () use ($path, $dealId, $phone): array {
                    return ensureDealAiContactUnlocked($dealId, $phone, $path);
                });
            });
        logMessage('info', 'AI contact linked by code', $result);
        return $result;
    } catch (Throwable $exception) {
        $result = ['ok' => false, 'mode' => 'code', 'deal_id' => $dealId, 'error' => $exception->getMessage()];
        logMessage('error', 'AI contact sync by code failed', $result);
        return $result;
    }
}

function aiContactRestResult(string $method, array $params): mixed
{
    $response = bitrixCall($method, $params);
    if (!array_key_exists('result', $response) || $response['result'] === false || $response['result'] === null) {
        throw new RuntimeException('Invalid CRM response: ' . $method);
    }
    return $response['result'];
}

function aiContactMatchesPhone(array $contact, string $phone): bool
{
    foreach ((array) ($contact['PHONE'] ?? []) as $item) {
        if (is_array($item) && normalizePhoneForDeal((string) ($item['VALUE'] ?? '')) === $phone) return true;
    }
    return false;
}

function ensureDealAiContactUnlocked(string $dealId, string $phone, string $statePath): array
{
    $deal = (array) aiContactRestResult('crm.deal.get', ['id' => $dealId]);
    if ((string) ($deal['ID'] ?? '') !== $dealId
        || normalizePhoneForDeal((string) ($deal[(string) config('DEAL_PHONE_FIELD', 'UF_CRM_1783332070432')] ?? '')) !== $phone) {
        throw new RuntimeException('Phone must be saved in the deal before contact creation.');
    }
    $bindings = aiContactRestResult('crm.deal.contact.items.get', ['id' => $dealId]);
    if (!is_array($bindings)) throw new RuntimeException('Invalid deal contact bindings.');
    $state = readJsonFileUnlocked($statePath);
    $contactId = '';
    foreach ($bindings as $binding) {
        $id = (string) ($binding['CONTACT_ID'] ?? '');
        if (!ctype_digit($id) || (int) $id <= 0) throw new RuntimeException('Invalid linked contact ID.');
        $contact = (array) aiContactRestResult('crm.contact.get', ['id' => $id]);
        if (aiContactMatchesPhone($contact, $phone)) {
            return ['ok' => true, 'mode' => 'code', 'deal_id' => $dealId, 'contact_id' => $id, 'created' => false, 'already_linked' => true];
        }
    }
    // Remember a successful create before attempting the separate deal-binding request.
    if (!empty($state['contact_id'])) {
        $id = (string) $state['contact_id'];
        $contact = (array) aiContactRestResult('crm.contact.get', ['id' => $id]);
        if (aiContactMatchesPhone($contact, $phone)) $contactId = $id;
    }
    if ($contactId === '') {
        $duplicates = aiContactRestResult('crm.duplicate.findbycomm', [
            'entity_type' => 'CONTACT', 'type' => 'PHONE',
            'values' => [$phone, '+' . preg_replace('/\D+/', '', $phone)],
        ]);
        if (!is_array($duplicates)) throw new RuntimeException('Invalid duplicate-search response.');
        $matches = [];
        foreach (array_unique((array) ($duplicates['CONTACT'] ?? [])) as $id) {
            $id = (string) $id;
            if (!ctype_digit($id) || (int) $id <= 0) throw new RuntimeException('Invalid duplicate contact ID.');
            $contact = (array) aiContactRestResult('crm.contact.get', ['id' => $id]);
            if (aiContactMatchesPhone($contact, $phone)) $matches[] = $id;
        }
        if (count($matches) > 1) throw new RuntimeException('Multiple contacts have this phone; manual selection required.');
        $contactId = $matches[0] ?? '';
    }
    $created = false;
    if ($contactId === '') {
        // A timeout may hide a successful remote create. Never blindly create again in that case.
        if (!empty($state['creation_pending'])) {
            throw new RuntimeException('Previous contact creation is unconfirmed; reconcile CRM before retrying.');
        }
        $state = ['creation_pending' => true, 'deal_id' => $dealId, 'updated_at' => date(DATE_ATOM)];
        writeJsonFileUnlocked($statePath, $state);
        $name = normalizeClientFullName($deal[(string) config('DEAL_FULL_NAME_FIELD', 'UF_CRM_1784807336296')] ?? '');
        $response = bitrixCallRaw('crm.contact.add', ['fields' => [
            'NAME' => $name !== '' ? $name : $phone,
            'PHONE' => [['VALUE' => $phone, 'VALUE_TYPE' => 'WORK']],
        ]]);
        $contactId = (string) ($response['result'] ?? '');
        if (!ctype_digit($contactId) || (int) $contactId <= 0) {
            throw new RuntimeException('Contact creation not confirmed: ' . (string) ($response['error'] ?? 'invalid response'));
        }
        $created = true;
    }
    writeJsonFileUnlocked($statePath, ['contact_id' => $contactId, 'updated_at' => date(DATE_ATOM)]);
    // Do not replace existing contacts or their primary-link flag.
    aiContactRestResult('crm.deal.contact.add', ['id' => $dealId, 'fields' => ['CONTACT_ID' => $contactId]]);
    $confirmed = aiContactRestResult('crm.deal.contact.items.get', ['id' => $dealId]);
    foreach ((array) $confirmed as $binding) {
        if ((string) ($binding['CONTACT_ID'] ?? '') === $contactId) {
            return ['ok' => true, 'mode' => 'code', 'deal_id' => $dealId, 'contact_id' => $contactId, 'created' => $created];
        }
    }
    throw new RuntimeException('Contact binding not confirmed.');
}
