<?php
namespace EasyChat\Library\Crm;

use EasyChat\Library\Http\ExternalHttpClient;
use EasyChat\Library\Http\PublicUrlGuard;

final class Bitrix24Provider implements CrmProviderInterface {
    private const MAX_RESPONSE_BYTES = 1048576;
    private $registry;
    private $http;
    private $guard;

    public function __construct($registry, ?ExternalHttpClient $http = null, ?PublicUrlGuard $guard = null) {
        $this->registry = $registry;
        $this->http = $http ?: new ExternalHttpClient($registry);
        $this->guard = $guard ?: new PublicUrlGuard();
    }

    public function code(): string { return 'bitrix24'; }

    public function capabilities(): array {
        return array('entity_modes'=>array('lead','deal'),'pipelines'=>false,'prepare_fields'=>false,'auth_type'=>'webhook');
    }

    public function testConnection(array $connection): array {
        $response = $this->call($connection, 'profile', array());
        if (empty($response['success'])) return $response;
        return array('success' => true, 'portal' => (string)parse_url($connection['webhook_url'], PHP_URL_HOST));
    }

    public function syncConversation(array $connection, array $conversation): array {
        $mode = ($connection['entity_mode'] ?? 'lead') === 'deal' ? 'deal' : 'lead';
        $existing_id = max(0, (int)($conversation['external_id'] ?? 0));
        if ($existing_id > 0) {
            $comment = $this->call($connection, 'crm.timeline.comment.add', array('fields' => array(
                'ENTITY_ID' => $existing_id,
                'ENTITY_TYPE' => $mode,
                'COMMENT' => mb_substr((string)($conversation['summary'] ?? ''), 0, 4000),
            )));
            if (empty($comment['success'])) return $comment;
            return array('success'=>true,'external_id'=>(string)$existing_id,'external_contact_id'=>(string)($conversation['external_contact_id'] ?? ''),'entity_type'=>$mode,'external_url'=>(string)($conversation['external_url'] ?? ''));
        }
        $fields = array(
            'TITLE' => mb_substr((string)($conversation['title'] ?? 'Обращение из EasyChat'), 0, 255),
            'NAME' => mb_substr((string)($conversation['customer_name'] ?? ''), 0, 128),
            'SOURCE_DESCRIPTION' => mb_substr((string)($conversation['source_description'] ?? ''), 0, 2000),
            'COMMENTS' => mb_substr((string)($conversation['summary'] ?? ''), 0, 4000),
        );
        $origin_id = mb_substr((string)($conversation['origin_id'] ?? ''), 0, 255);
        if ($origin_id !== '') {
            $entity = $mode === 'deal' ? 'deal' : 'lead';
            $found = $this->call($connection, 'crm.' . $entity . '.list', array(
                'filter'=>array('ORIGINATOR_ID'=>'EASYCHAT','ORIGIN_ID'=>$origin_id),
                'select'=>array('ID'),
                'start'=>-1,
            ));
            if (empty($found['success'])) return $found;
            if (!empty($found['success']) && !empty($found['result'][0]['ID'])) {
                $id = (int)$found['result'][0]['ID'];
                return array('success'=>true,'external_id'=>(string)$id,'entity_type'=>$mode,'external_url'=>'https://' . parse_url($connection['webhook_url'], PHP_URL_HOST) . '/crm/' . $entity . '/details/' . $id . '/');
            }
            $fields['ORIGINATOR_ID'] = 'EASYCHAT';
            $fields['ORIGIN_ID'] = $origin_id;
        }
        $phone = trim((string)($conversation['phone'] ?? ''));
        $email = trim((string)($conversation['email'] ?? ''));
        if ($phone !== '') $fields['PHONE'] = array(array('VALUE' => $phone, 'VALUE_TYPE' => 'WORK'));
        if ($email !== '') $fields['EMAIL'] = array(array('VALUE' => $email, 'VALUE_TYPE' => 'WORK'));
        if (!empty($connection['responsible_id'])) $fields['ASSIGNED_BY_ID'] = (int)$connection['responsible_id'];

        $contact_id = 0;
        if ($mode === 'deal') {
            $contact_id = max(0, (int)($conversation['external_contact_id'] ?? 0));
            if ($contact_id <= 0) $contact_id = $this->contactId($connection, (string)$fields['NAME'], $phone, $email);
            unset($fields['NAME'], $fields['PHONE'], $fields['EMAIL'], $fields['SOURCE_DESCRIPTION']);
            if ($contact_id > 0) $fields['CONTACT_ID'] = $contact_id;
        }

        $method = $mode === 'deal' ? 'crm.deal.add' : 'crm.lead.add';
        $created = $this->call($connection, $method, array('fields' => $fields, 'params' => array('REGISTER_SONET_EVENT' => 'N')));
        if (empty($created['success'])) return $created;
        $external_id = (int)($created['result'] ?? 0);
        if ($external_id <= 0) return array('success' => false, 'error' => 'crm_invalid_create_response', 'retryable' => false);
        return array(
            'success' => true,
            'external_id' => (string)$external_id,
            'entity_type' => $mode,
            'external_contact_id' => $contact_id > 0 ? (string)$contact_id : '',
            'external_url' => 'https://' . parse_url($connection['webhook_url'], PHP_URL_HOST)
                . '/crm/' . ($mode === 'deal' ? 'deal' : 'lead') . '/details/' . $external_id . '/',
        );
    }

    private function contactId(array $connection, string $name, string $phone, string $email): int {
        $matches_by_field = array();
        foreach (array(array('PHONE'=>$phone), array('EMAIL'=>$email)) as $filter) {
            if (trim((string)reset($filter)) === '') continue;
            $found = $this->call($connection, 'crm.contact.list', array('filter'=>$filter,'select'=>array('ID'),'start'=>-1));
            if (empty($found['success'])) throw new \RuntimeException((string)($found['error'] ?? 'crm_contact_lookup_failed'));
            $ids = array();
            foreach ((array)($found['result'] ?? array()) as $row) {
                $id = (int)($row['ID'] ?? 0);
                if ($id > 0) $ids[$id] = $id;
            }
            $matches_by_field[] = array_values($ids);
        }
        $non_empty = array_values(array_filter($matches_by_field));
        if ($non_empty) {
            $candidates = array_shift($non_empty);
            foreach ($non_empty as $ids) $candidates = array_values(array_intersect($candidates, $ids));
            if (count($candidates) === 1) return (int)$candidates[0];
             
             
            if (!$candidates && count($matches_by_field) === 2) {
                foreach ($matches_by_field as $ids) if (count($ids) === 1) $candidates[(int)$ids[0]] = (int)$ids[0];
                $candidates = array_values(array_unique($candidates));
                if (count($candidates) === 1) return (int)$candidates[0];
            }
            throw new \RuntimeException('crm_contact_ambiguous');
        }
        $fields = array('NAME'=>$name !== '' ? $name : 'Клиент EasyChat');
        if ($phone !== '') $fields['PHONE'] = array(array('VALUE'=>$phone,'VALUE_TYPE'=>'WORK'));
        if ($email !== '') $fields['EMAIL'] = array(array('VALUE'=>$email,'VALUE_TYPE'=>'WORK'));
        $created = $this->call($connection, 'crm.contact.add', array('fields'=>$fields,'params'=>array('REGISTER_SONET_EVENT'=>'N')));
        if (empty($created['success'])) throw new \RuntimeException((string)($created['error'] ?? 'crm_contact_create_failed'));
        return (int)($created['result'] ?? 0);
    }

    private function call(array $connection, string $method, array $params): array {
        $base = Bitrix24Webhook::normalize((string)($connection['webhook_url'] ?? ''));
        if ($base === '' || !preg_match('/^[a-z][a-z0-9.]*$/', $method)) {
            return array('success' => false, 'error' => 'crm_credentials_invalid', 'retryable' => false);
        }
        $url = $base . '/' . $method . '.json';
        $prepared = $this->guard->prepare($url);
        if (empty($prepared['success'])) {
            return array('success' => false, 'error' => 'crm_remote_host_denied', 'retryable' => false);
        }
        $buffer = '';
        $overflow = false;
        $request = array(
            'headers' => array('Accept: application/json', 'Content-Type: application/json'),
            'body' => json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            'connect_timeout' => 4,
            'timeout' => 15,
            'follow_location' => false,
            'trust_profile' => 'bitrix24',
            'curl_options' => (array)$prepared['curl_options'],
            'write_function' => function ($ch, string $chunk) use (&$buffer, &$overflow): int {
                if (strlen($buffer) + strlen($chunk) > self::MAX_RESPONSE_BYTES) { $overflow = true; return 0; }
                $buffer .= $chunk;
                return strlen($chunk);
            },
        );
        $result = $this->http->request('POST', $url, $request);
        $http = (int)($result['http_code'] ?? 0);
        $json = !$overflow && $buffer !== '' ? json_decode($buffer, true) : array();
        $json = is_array($json) ? $json : array();
        if (!$overflow && !empty($result['transport_ok']) && $http >= 200 && $http < 300 && empty($json['error'])) {
            return array('success' => true, 'result' => $json['result'] ?? null);
        }
        $code = strtoupper(preg_replace('/[^A-Z0-9_]/i', '', (string)($json['error'] ?? '')));
        $retryable = $http === 0 || $http === 429 || $http >= 500
            || in_array($code, array('QUERY_LIMIT_EXCEEDED', 'OPERATION_TIME_LIMIT', 'OVERLOAD_LIMIT'), true);
        return array('success' => false, 'error' => $overflow ? 'crm_response_too_large' : ($code ?: 'crm_request_failed'), 'retryable' => $retryable);
    }
}
