<?php

declare(strict_types=1);

function appSettingsGroups(): array
{
    return [
        settingsGroup('connection', 'Подключение', [
            settingsField('APP_BASE_URL', 'URL приложения', 'url', 'https://api.darex-moto.ru/darex_connector_app/public'),
            settingsField('BITRIX_PORTAL_URL', 'Портал Bitrix24', 'url', 'https://darex.bitrix24.ru'),
            settingsField('BITRIX_CLIENT_ID', 'Client ID', 'text', ''),
            settingsField('BITRIX_CLIENT_SECRET', 'Client secret', 'password', '', ['sensitive' => true]),
            settingsField('SETTINGS_ALLOWED_USER_IDS', 'Пользователи с доступом', 'csv', [162100], ['item_type' => 'integer']),
            settingsField('BUSINESS_TIMEZONE', 'Часовой пояс', 'text', 'Europe/Moscow'),
        ]),
        settingsGroup('ai', 'OpenAI и боты', [
            settingsField('OPENAI_API_KEY', 'OpenAI API key', 'password', '', ['sensitive' => true]),
            settingsField('OPENAI_MODEL', 'Модель ответов', 'text', 'gpt-4.1-mini'),
            settingsField('OPENAI_EXTRACTION_MODEL', 'Модель извлечения данных', 'text', 'gpt-4.1-mini'),
            settingsField('BITRIX_AI_BOT_ID', 'ID личного бота', 'integer', 0),
            settingsField('BITRIX_AI_BOT_CODE', 'Код личного бота', 'text', 'darex_ai_direct_bot'),
            settingsField('BITRIX_AI_BOT_NAME', 'Название личного бота', 'text', 'Darex AI Direct'),
            settingsField('BITRIX_AI_BOT_TYPE', 'Тип личного бота', 'select', 'H', ['options' => ['H' => 'Человекоподобный', 'B' => 'Бот']]),
            settingsField('BITRIX_OPENLINE_BOT_ID', 'ID бота открытых линий', 'integer', 0),
            settingsField('MANAGED_OPEN_LINE_BOT_IDS', 'ID обслуживаемых ботов', 'csv', [], ['item_type' => 'integer']),
            settingsField('BITRIX_OPENLINE_BOT_CODE', 'Код бота открытых линий', 'text', 'darex_ai_openline_bot'),
            settingsField('BITRIX_OPENLINE_BOT_NAME', 'Название бота открытых линий', 'text', 'Darex AI OpenLine'),
            settingsField('BITRIX_OPENLINE_BOT_TYPE', 'Тип бота открытых линий', 'select', 'H', ['options' => ['H' => 'Человекоподобный', 'B' => 'Бот']]),
        ]),
        settingsGroup('crm', 'Поля CRM', [
            settingsField('CRM_SYNC_ENABLED', 'Синхронизация со сделкой', 'boolean', true),
            settingsField('DEAL_PHONE_FIELD', 'Поле телефона', 'text', 'UF_CRM_1783332070432'),
            settingsField('DEAL_CITY_FIELD', 'Поле города', 'text', 'UF_CRM_1693995123245'),
            settingsField('DEAL_FULL_NAME_FIELD', 'Поле ФИО', 'text', 'UF_CRM_1784807336296'),
            settingsField('DEAL_INTEREST_GROUP_FIELD', 'Поле интереса', 'text', 'UF_CRM_1784809347681'),
            settingsField('DEAL_BRANCH_FIELD', 'Поле филиала', 'text', 'UF_CRM_1764919997'),
            settingsField('DEAL_UNDEFINED_BRANCH_VALUE', 'Значение «Филиал не определён»', 'text', 'Не определено'),
            settingsField('DEAL_ADVERTISEMENT_CONTEXT_FIELD', 'Поле ссылки на объявление', 'text', 'UF_CRM_1716203519003'),
            settingsField('DEAL_NO_RESPONSE_FIELD', 'Поле «Клиент не отвечает»', 'text', 'UF_CRM_1784546250803'),
            settingsField('DEAL_NO_RESPONSE_VALUE', 'Значение «Клиент не отвечает»', 'text', '1'),
        ]),
        settingsGroup('routing', 'Воронки и стадии', [
            settingsField('DEAL_ROUTING_LOCK_AFTER_INITIAL_STAGE', 'Не возвращать перемещённые сделки', 'boolean', true),
            settingsField('DEAL_ROUTING_INITIAL_CATEGORY_ID', 'Исходная воронка', 'integer', 0),
            settingsField('DEAL_ROUTING_INITIAL_STAGE_ID', 'Исходная стадия', 'text', 'NEW'),
            settingsField('INTEREST_ROUTING_ENABLED', 'Маршрутизация по интересу', 'boolean', true),
            settingsField('PARTS_DEAL_CATEGORY_ID', 'Воронка «Запчасти»', 'integer', 26),
            settingsField('PARTS_INITIAL_STAGE_ID', 'Начальная стадия «Запчасти»', 'text', 'C26:NEW'),
            settingsField('SERVICE_DEAL_CATEGORY_ID', 'Воронка «Сервис»', 'integer', 30),
            settingsField('SERVICE_INITIAL_STAGE_ID', 'Начальная стадия «Сервис»', 'text', 'C30:NEW'),
            settingsField('OTHER_DEAL_CATEGORY_ID', 'Воронка «Другое»', 'integer', 0),
            settingsField('OTHER_DEAL_STAGE_ID', 'Стадия «Нецелевой»', 'text', '26'),
            settingsField('TECHNIQUE_QUALIFICATION_ENABLED', 'Квалификация техники', 'boolean', true),
            settingsField('RETAIL_DEAL_CATEGORY_ID', 'Воронка «Розница»', 'integer', 0),
            settingsField('TECHNIQUE_QUALIFIED_STAGE_ID', 'Стадия «Квалифицированный»', 'text', '37'),
            settingsField('PHONE_ONLY_END_OF_DAY_ENABLED', 'Перенос сделок только с телефоном', 'boolean', true),
            settingsField('PHONE_ONLY_END_OF_DAY_CATEGORY_ID', 'Воронка второго касания', 'integer', 0),
            settingsField('PHONE_ONLY_END_OF_DAY_STAGE_ID', 'Стадия второго касания', 'text', '44'),
            settingsField('NO_PHONE_FINAL_STAGE_ENABLED', 'Финальная стадия без телефона', 'boolean', true),
            settingsField('NO_PHONE_FINAL_STAGE_ID', 'Стадия «3 раза недоступен»', 'text', '28'),
        ]),
        settingsGroup('automation', 'Процессы и уведомления', [
            settingsField('PHONE_WORKFLOW_ENABLED', 'Бизнес-процесс после телефона', 'boolean', true),
            settingsField('PHONE_WORKFLOW_TEMPLATE_ID', 'Шаблон БП после телефона', 'integer', 1905),
            settingsField('PHONE_WORKFLOW_PARAMETER_NAME', 'Параметр номера телефона', 'text', ''),
            settingsField('QUALIFICATION_DISTRIBUTION_WORKFLOW_ENABLED', 'БП после квалификации', 'boolean', true),
            settingsField('QUALIFICATION_DISTRIBUTION_WORKFLOW_TEMPLATE_ID', 'Шаблон БП после квалификации', 'integer', 1907),
            settingsField('QUALIFIED_DEAL_CHAT_SUMMARY_ENABLED', 'Резюме чата после квалификации', 'boolean', true),
            settingsField('DEAL_CHAT_SUMMARY_FIELD', 'Поле резюме чата', 'text', 'COMMENTS'),
            settingsField('NON_TARGET_NOTIFICATION_ENABLED', 'Уведомление о нецелевой сделке', 'boolean', true),
            settingsField('NON_TARGET_NOTIFICATION_DIALOG_ID', 'Чат нецелевых сделок', 'text', 'chat985965'),
            settingsField('NON_TARGET_NOTIFICATION_BOT_ID', 'Bot ID уведомления о нецелевой', 'integer', 0),
            settingsField('UNDEFINED_BRANCH_NOTIFICATION_ENABLED', 'Уведомление без филиала', 'boolean', true),
            settingsField('UNDEFINED_BRANCH_NOTIFICATION_DIALOG_ID', 'Чат сделок без филиала', 'text', 'chat985965'),
            settingsField('UNDEFINED_BRANCH_NOTIFICATION_BOT_ID', 'Bot ID уведомления без филиала', 'integer', 0),
            settingsField('PHONE_ONLY_NOTIFICATION_ENABLED', 'Уведомление о втором касании', 'boolean', true),
            settingsField('PHONE_ONLY_NOTIFICATION_DIALOG_ID', 'Чат второго касания', 'text', 'chat985965'),
            settingsField('PHONE_ONLY_NOTIFICATION_BOT_ID', 'Bot ID уведомления о втором касании', 'integer', 0),
        ]),
        settingsGroup('prompts', 'Промпты и история', [
            settingsField('SYSTEM_PROMPTS_LIST_CODE', 'Код списка системных промптов', 'text', 'darex_ai_system_prompts'),
            settingsField('CORRECTION_PROMPTS_LIST_CODE', 'Код списка корректировок', 'text', 'darex_ai_correction_prompts'),
            settingsField('SYSTEM_PROMPT_TEXT_PROPERTY', 'Текст системного промпта', 'text', 'PROPERTY_269'),
            settingsField('CORRECTION_PROMPT_TEXT_PROPERTY', 'Текст корректировки', 'text', 'PROPERTY_287'),
            settingsField('SYSTEM_PROMPT_OPEN_LINE_PROPERTY', 'Открытые линии системного промпта', 'text', ''),
            settingsField('CORRECTION_PROMPT_OPEN_LINE_PROPERTY', 'Открытые линии корректировки', 'text', ''),
            settingsField('SYSTEM_PROMPT_ACTIVE_PROPERTY', 'Активность системного промпта', 'text', 'PROPERTY_273'),
            settingsField('CORRECTION_PROMPT_ACTIVE_PROPERTY', 'Активность корректировки', 'text', 'PROPERTY_291'),
            settingsField('PROMPT_CACHE_TTL', 'Кеш промптов, секунд', 'integer', 300),
            settingsField('CHAT_HISTORY_MAX_MESSAGES', 'Сообщений в контексте', 'integer', 20),
        ]),
        settingsGroup('followups', 'Догоняющие сообщения', [
            settingsField('FOLLOWUP_ENABLED', 'Догоняющие сообщения', 'boolean', true),
            settingsField('FOLLOWUP_SETTINGS_LIST_CODE', 'Код списка настроек', 'text', 'darex_ai_followups'),
            settingsField('FOLLOWUP_SETTINGS_CACHE_TTL', 'Кеш списка, секунд', 'integer', 300),
            settingsField('FOLLOWUP_DELAY_SECONDS', 'Базовая задержка, секунд', 'integer', 3600),
            settingsField('FOLLOWUP_OFFSETS_SECONDS', 'Интервалы, секунд', 'csv', [3600, 10800, 86400, 172800], ['item_type' => 'integer']),
            settingsField('NO_RESPONSE_MARK_AFTER_SECONDS', 'Срок «Клиент не отвечает», секунд', 'integer', 259200),
            settingsField('FOLLOWUP_ONLY_OPEN_LINES', 'Только открытые линии', 'boolean', true),
            settingsField('FOLLOWUP_REQUIRE_INITIAL_DEAL_STAGE', 'Только в исходной стадии', 'boolean', true),
            settingsField('FOLLOWUP_INITIAL_CATEGORY_ID', 'Исходная воронка догоняющих', 'integer', 0),
            settingsField('FOLLOWUP_INITIAL_STAGE_ID', 'Исходная стадия догоняющих', 'text', 'NEW'),
            settingsField('FOLLOWUP_MESSAGE', 'Резервный текст сообщения', 'text', ''),
            settingsField('FOLLOWUP_MESSAGES', 'Если нет города и телефона', 'lines', []),
            settingsField('FOLLOWUP_MESSAGES_NEED_CITY', 'Если нет города', 'lines', []),
            settingsField('FOLLOWUP_MESSAGES_NEED_PHONE', 'Если нет телефона', 'lines', []),
        ]),
    ];
}

function settingsGroup(string $id, string $title, array $fields): array
{
    return ['id' => $id, 'title' => $title, 'fields' => $fields];
}

function settingsField(string $key, string $label, string $type, $default, array $extra = []): array
{
    return array_replace([
        'key' => $key,
        'label' => $label,
        'type' => $type,
        'default' => $default,
    ], $extra);
}

function appSettingsFieldsByKey(): array
{
    $result = [];
    foreach (appSettingsGroups() as $group) {
        foreach ($group['fields'] as $field) {
            $result[$field['key']] = $field;
        }
    }

    return $result;
}

function currentAppSettingsForUi(): array
{
    $values = [];
    foreach (appSettingsFieldsByKey() as $key => $field) {
        $value = config($key, $field['default']);
        if (($field['sensitive'] ?? false) === true) {
            $values[$key] = [
                'value' => '',
                'configured' => trim((string) $value) !== '',
            ];
            continue;
        }

        $values[$key] = ['value' => $value, 'configured' => true];
    }

    return $values;
}

function saveAppSettings(array $input, array $user): array
{
    $fields = appSettingsFieldsByKey();
    $values = [];

    foreach ($fields as $key => $field) {
        if (($field['sensitive'] ?? false) === true && trim((string) ($input[$key] ?? '')) === '') {
            $values[$key] = config($key, $field['default']);
            continue;
        }

        $raw = array_key_exists($key, $input) ? $input[$key] : config($key, $field['default']);
        $values[$key] = normalizeAppSettingValue($raw, $field);
    }

    validateAppSettings($values);

    $payload = [
        'version' => 1,
        'updated_at' => date(DATE_ATOM),
        'updated_by' => [
            'id' => (string) ($user['ID'] ?? ''),
            'name' => trim((string) (($user['NAME'] ?? '') . ' ' . ($user['LAST_NAME'] ?? ''))),
        ],
        'values' => $values,
    ];
    writeAppSettingsFile((string) config('APP_SETTINGS_FILE'), $payload);
    clearAppSettingsCaches();

    return $payload;
}

function normalizeAppSettingValue($value, array $field)
{
    $type = (string) ($field['type'] ?? 'text');
    if ($type === 'boolean') {
        if (is_bool($value)) {
            return $value;
        }

        return in_array(strtolower(trim((string) $value)), ['1', 'true', 'yes', 'y', 'on'], true);
    }
    if ($type === 'integer') {
        return (int) $value;
    }
    if (in_array($type, ['csv', 'lines'], true)) {
        if (!is_array($value)) {
            $pattern = $type === 'lines' ? '/\R+/' : '/[\s,;\/]+/';
            $value = preg_split($pattern, trim((string) $value)) ?: [];
        }
        $items = [];
        foreach ($value as $item) {
            $item = trim((string) $item);
            if ($item === '') {
                continue;
            }
            $items[] = ($field['item_type'] ?? '') === 'integer' ? (int) $item : $item;
        }

        return array_values(array_unique($items, SORT_REGULAR));
    }

    return trim((string) $value);
}

function validateAppSettings(array $values): void
{
    foreach (['APP_BASE_URL', 'BITRIX_PORTAL_URL'] as $key) {
        if (!filter_var((string) ($values[$key] ?? ''), FILTER_VALIDATE_URL)) {
            throw new InvalidArgumentException('Некорректный URL: ' . $key);
        }
    }
    if (trim((string) ($values['OPENAI_MODEL'] ?? '')) === '') {
        throw new InvalidArgumentException('Не указана модель OpenAI.');
    }
    if ((int) ($values['CHAT_HISTORY_MAX_MESSAGES'] ?? 0) < 1) {
        throw new InvalidArgumentException('Количество сообщений в контексте должно быть больше нуля.');
    }
}

function writeAppSettingsFile(string $path, array $payload): void
{
    $directory = dirname($path);
    if (!is_dir($directory) && !mkdir($directory, 02775, true) && !is_dir($directory)) {
        throw new RuntimeException('Не удалось создать каталог настроек.');
    }

    $content = "<?php\n\ndeclare(strict_types=1);\n\nreturn "
        . var_export($payload, true)
        . ";\n";
    $temporaryPath = $path . '.tmp.' . bin2hex(random_bytes(6));
    if (file_put_contents($temporaryPath, $content, LOCK_EX) === false) {
        throw new RuntimeException('Не удалось записать настройки.');
    }
    @chmod($temporaryPath, 0660);
    if (!rename($temporaryPath, $path)) {
        @unlink($temporaryPath);
        throw new RuntimeException('Не удалось применить настройки.');
    }
    @chmod($path, 0660);
}

function clearAppSettingsCaches(): void
{
    foreach (['PROMPT_CACHE_FILE', 'FOLLOWUP_SETTINGS_CACHE_FILE'] as $key) {
        $path = trim((string) config($key, ''));
        if ($path !== '' && is_file($path)) {
            @unlink($path);
        }
    }
}

function authorizeAppSettingsRequest(string $domain, string $accessToken): array
{
    $domain = strtolower(trim($domain));
    if (!preg_match('/^[a-z0-9.-]+$/', $domain) || $accessToken === '') {
        throw new RuntimeException('Нет данных авторизации Bitrix24.');
    }

    $configuredHost = strtolower((string) parse_url((string) config('BITRIX_PORTAL_URL', ''), PHP_URL_HOST));
    if ($configuredHost !== '' && $domain !== $configuredHost) {
        throw new RuntimeException('Настройки принадлежат другому порталу Bitrix24.');
    }

    $curl = curl_init('https://' . $domain . '/rest/user.current.json');
    curl_setopt_array($curl, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query(['auth' => $accessToken]),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT => 15,
    ]);
    $raw = curl_exec($curl);
    $status = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
    $error = curl_error($curl);
    curl_close($curl);

    $decoded = is_string($raw) ? json_decode($raw, true) : null;
    if ($status >= 400 || !is_array($decoded) || isset($decoded['error']) || !is_array($decoded['result'] ?? null)) {
        throw new RuntimeException('Bitrix24 не подтвердил пользователя' . ($error !== '' ? ': ' . $error : '.'));
    }

    $user = $decoded['result'];
    $allowedIds = normalizeAppSettingValue(config('SETTINGS_ALLOWED_USER_IDS', [162100]), [
        'type' => 'csv',
        'item_type' => 'integer',
    ]);
    if ($allowedIds !== [] && !in_array((int) ($user['ID'] ?? 0), $allowedIds, true)) {
        throw new RuntimeException('У пользователя нет доступа к настройкам приложения.');
    }

    return $user;
}
