<?php
/**
 * Безопасная SMS верификация с использованием Redis (Predis)
 * 
 * Endpoints:
 * - POST /sms-verify.php?action=send - Отправка SMS кода
 * - POST /sms-verify.php?action=verify - Проверка кода
 */

error_reporting(E_ALL);
ini_set('display_errors', 0); // Отключаем вывод ошибок в продакшене

// Загружаем .env
require_once __DIR__ . '/env_loader.php';

// Загружаем Predis (чистый PHP клиент для Redis)
require_once __DIR__ . '/vendor/autoload.php';

// Устанавливаем заголовки
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');

// Обработка preflight запроса
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}

// Логирование
$log_file = __DIR__ . '/logs/sms_verify.log';
$log_dir = dirname($log_file);
if (!is_dir($log_dir)) {
    mkdir($log_dir, 0755, true);
}

function log_message($message) {
    global $log_file;
    $timestamp = date('Y-m-d H:i:s');
    file_put_contents($log_file, "[$timestamp] $message\n", FILE_APPEND);
}

// Подключение к Redis через Predis (чистый PHP клиент)
function getRedis($force_reconnect = false) {
    static $redis = null;
    
    if ($redis === null || $force_reconnect) {
        // Закрываем старое подключение
        if ($redis !== null) {
            try {
                $redis->disconnect();
            } catch (Exception $e) {
                // Игнорируем
            }
            $redis = null;
        }
        
        try {
            $host = env('REDIS_HOST', 'crm.clientright.ru');
            $port = (int)env('REDIS_PORT', 6379);
            $password = env('REDIS_PASSWORD', '');
            
            log_message("Подключение к Redis через Predis: $host:$port");
            
            $options = [
                'scheme' => 'tcp',
                'host'   => $host,
                'port'   => $port,
                'timeout' => 3.0,
                'read_write_timeout' => 3.0,
            ];
            
            if (!empty($password)) {
                $options['password'] = $password;
            }
            
            $redis = new Predis\Client($options);
            
            // Проверяем подключение
            $pong = $redis->ping();
            log_message("Redis (Predis) подключен успешно. Ping: $pong");
            
        } catch (Predis\Connection\ConnectionException $e) {
            log_message("Ошибка подключения к Redis (Predis): " . $e->getMessage());
            $redis = null;
            return null;
        } catch (Exception $e) {
            log_message("Ошибка Redis (Predis): " . $e->getMessage());
            $redis = null;
            return null;
        }
    }
    
    return $redis;
}

// Функции для работы с файловым хранилищем (fallback если Redis недоступен)
function saveCodeToFile($phone, $code) {
    $storage_dir = __DIR__ . '/storage/sms_codes';
    if (!is_dir($storage_dir)) {
        @mkdir($storage_dir, 0777, true);
        @chmod($storage_dir, 0777);
    }
    $file = $storage_dir . '/' . md5($phone) . '.json';
    $data = [
        'code' => $code,
        'phone' => $phone,
        'expires' => time() + 600, // 10 минут
        'created' => time()
    ];
    $result = @file_put_contents($file, json_encode($data));
    if ($result === false) {
        log_message("Ошибка записи файла: $file. Ошибка: " . (error_get_last()['message'] ?? 'неизвестная'));
        return false;
    }
    @chmod($file, 0666);
    log_message("Код успешно сохранен в файл: $file");
    return true;
}

function getCodeFromFile($phone) {
    $storage_dir = __DIR__ . '/storage/sms_codes';
    $file = $storage_dir . '/' . md5($phone) . '.json';
    log_message("Проверка файла: $file, существует: " . (file_exists($file) ? 'да' : 'нет'));
    if (!file_exists($file)) {
        // Проверяем все файлы в директории
        $files = @scandir($storage_dir);
        log_message("Файлы в директории: " . json_encode($files));
        return false;
    }
    $content = @file_get_contents($file);
    log_message("Содержимое файла: $content");
    $data = json_decode($content, true);
    if (!$data || !isset($data['expires']) || $data['expires'] < time()) {
        log_message("Код истек или данные некорректны");
        @unlink($file); // Удаляем истекший файл
        return false;
    }
    log_message("Код из файла: " . $data['code']);
    return $data['code'];
}

function deleteCodeFromFile($phone) {
    $storage_dir = __DIR__ . '/storage/sms_codes';
    $file = $storage_dir . '/' . md5($phone) . '.json';
    @unlink($file);
    return true;
}

// Очистка номера телефона
function clear_phone($phone) {
    // Убираем все пробелы, скобки, дефисы
    $phone = preg_replace('/[() -]+/', '', $phone);
    
    // Убираем +7 или 8 в начале, оставляем только цифры
    $phone = preg_replace('/^(\+?7|8)/', '', $phone);
    
    return $phone;
}

// Генерация 6-значного кода
function generateCode() {
    return str_pad(rand(100000, 999999), 6, '0', STR_PAD_LEFT);
}

// Отправка SMS через n8n webhook
function sendSMS($phone, $code) {
    $webhook_url = env('N8N_SMS_WEBHOOK', '');
    
    if (empty($webhook_url)) {
        log_message("Ошибка: не указан N8N_SMS_WEBHOOK в .env");
        return false;
    }
    
    // Очищаем номер телефона
    $phone_cleaned = clear_phone($phone);
    
    // Формируем текст сообщения
    $text = "Код подтверждения: $code";
    
    // Данные для отправки в n8n
    $data = [
        'phone' => $phone_cleaned,
        'code' => $code,
        'text' => $text,
        'timestamp' => date('Y-m-d H:i:s')
    ];
    
    log_message("Отправка SMS через n8n на номер: $phone_cleaned, код: $code");
    
    // Отправляем запрос на n8n webhook
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $webhook_url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json; charset=utf-8'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curl_error = curl_error($ch);
    $curl_errno = curl_errno($ch);
    curl_close($ch);
    
    if ($curl_error) {
        log_message("Ошибка CURL при отправке SMS через n8n: $curl_error (код: $curl_errno)");
        return false;
    }
    
    // Логируем ответ от n8n
    log_message("Ответ от n8n: HTTP $http_code, ответ: " . substr($response, 0, 200));
    
    // Проверяем успешность отправки
    // n8n может вернуть разные форматы ответов, поэтому проверяем HTTP код
    if ($http_code >= 200 && $http_code < 300) {
        // Пытаемся распарсить ответ, если это JSON
        $response_data = json_decode($response, true);
        if ($response_data !== null) {
            // Если есть поле success или status, проверяем его
            if (isset($response_data['success']) && $response_data['success'] === false) {
                log_message("n8n вернул ошибку: " . ($response_data['message'] ?? 'неизвестная ошибка'));
                return false;
            }
            if (isset($response_data['status']) && $response_data['status'] === 'error') {
                log_message("n8n вернул статус ошибки: " . ($response_data['message'] ?? 'неизвестная ошибка'));
                return false;
            }
        }
        
        log_message("SMS отправлено успешно через n8n на номер: $phone_cleaned");
        return true;
    } else {
        log_message("Ошибка отправки SMS через n8n. HTTP код: $http_code, Ответ: " . substr($response, 0, 200));
        return false;
    }
}

// Проверка rate limiting
function checkRateLimit($phone, $redis) {
    if (!$redis) {
        return true; // Если Redis недоступен, пропускаем проверку
    }
    
    $phone_cleaned = clear_phone($phone);
    $key_send = "sms:ratelimit:send:$phone_cleaned";
    $key_attempts = "sms:ratelimit:attempts:$phone_cleaned";
    
    // Проверяем количество отправок за последний час (максимум 5)
    $send_count = $redis->get($key_send);
    if ($send_count && $send_count >= 5) {
        return false;
    }
    
    // Проверяем количество попыток проверки за последние 15 минут (максимум 10)
    $attempts_count = $redis->get($key_attempts);
    if ($attempts_count && $attempts_count >= 10) {
        return false;
    }
    
    return true;
}

// Обработка запросов
$action = $_GET['action'] ?? $_POST['action'] ?? '';

try {
    if ($action === 'send') {
        // Отправка SMS кода
        $phone = $_POST['phonenumber'] ?? '';
        
        log_message("=== ОТПРАВКА SMS ===");
        log_message("Входящий номер (raw): '$phone'");
        log_message("POST данные: " . json_encode($_POST, JSON_UNESCAPED_UNICODE));
        
        if (empty($phone)) {
            throw new Exception("Номер телефона не указан");
        }
        
        $phone_cleaned = clear_phone($phone);
        log_message("Номер после очистки: '$phone_cleaned'");
        
        // Проверка rate limiting
        $redis = getRedis();
        if ($redis && !checkRateLimit($phone_cleaned, $redis)) {
            throw new Exception("Превышен лимит запросов. Попробуйте позже.");
        }
        
        // Генерируем код
        $code = generateCode();
        
        $code_saved = false;
        
        // Сохраняем код в Redis на 10 минут ПЕРЕД отправкой SMS
        if ($redis) {
            try {
                $key = "sms:code:$phone_cleaned";
                $saved = $redis->setex($key, 600, $code); // 10 минут
                
                if (!$saved) {
                    log_message("Ошибка: не удалось сохранить код в Redis для номера $phone_cleaned");
                } else {
                    log_message("Код сохранен в Redis для номера $phone_cleaned: $code");
                    $code_saved = true;
                    
                    // Увеличиваем счетчик отправок
                    $key_send = "sms:ratelimit:send:$phone_cleaned";
                    $current = $redis->get($key_send) ?: 0;
                    $redis->setex($key_send, 3600, $current + 1); // 1 час
                }
            } catch (RedisException $e) {
                log_message("Ошибка Redis при сохранении кода: " . $e->getMessage());
            }
        }
        
        // Если Redis недоступен или не удалось сохранить, сохраняем в файл (fallback)
        if (!$code_saved) {
            log_message("Сохранение кода в файл (fallback) для номера $phone_cleaned");
            saveCodeToFile($phone_cleaned, $code);
            $code_saved = true;
        }
        
        // Отправляем SMS
        $sms_sent = sendSMS($phone, $code);
        
        if (!$sms_sent) {
            // Если SMS не отправилось, удаляем код из Redis и файла
            if ($redis) {
                try {
                    $key = "sms:code:$phone_cleaned";
                    $deleted = $redis->del($key);
                    log_message("SMS не отправлено, код удален из Redis (ключ: $key, удалено: $deleted)");
                } catch (Exception $e) {
                    log_message("Ошибка при удалении кода из Redis: " . $e->getMessage());
                }
            }
            deleteCodeFromFile($phone_cleaned); // Удаляем из файла тоже
            throw new Exception("Не удалось отправить SMS");
        }
        
        $storage_info = $code_saved ? ($redis ? "Redis (ключ: sms:code:$phone_cleaned)" : "файловое хранилище") : "не сохранен";
        log_message("Код отправлен на номер: $phone_cleaned, код сохранен в: $storage_info");
        
        echo json_encode([
            'success' => true,
            'message' => 'Код отправлен на ваш номер телефона'
        ], JSON_UNESCAPED_UNICODE);
        
    } elseif ($action === 'verify') {
        // Проверка кода
        log_message("=== НАЧАЛО ПРОВЕРКИ КОДА ===");
        log_message("REQUEST_METHOD: " . ($_SERVER['REQUEST_METHOD'] ?? 'не установлен'));
        log_message("POST данные: " . json_encode($_POST, JSON_UNESCAPED_UNICODE));
        log_message("GET данные: " . json_encode($_GET, JSON_UNESCAPED_UNICODE));
        
        $phone = $_POST['phonenumber'] ?? '';
        $code = $_POST['code'] ?? '';
        
        log_message("Входящий номер (raw): '$phone'");
        log_message("Входящий код: '$code'");
        
        if (empty($phone) || empty($code)) {
            log_message("Ошибка: номер телефона или код не указаны");
            throw new Exception("Номер телефона или код не указаны");
        }
        
        $phone_cleaned = clear_phone($phone);
        log_message("Номер после очистки: '$phone_cleaned'");
        
        // Проверка rate limiting - пытаемся переподключиться при необходимости
        log_message("Попытка подключения к Redis для проверки кода...");
        $redis = getRedis(true); // Принудительно пытаемся переподключиться
        
        $stored_code = false;
        
        $stored_code = false;
        
        // Пытаемся получить код из Redis
        if ($redis) {
            log_message("Redis подключен успешно для проверки кода");
            try {
                // Проверяем количество попыток
                $key_attempts = "sms:ratelimit:attempts:$phone_cleaned";
                $attempts = $redis->get($key_attempts) ?: 0;
                
                if ($attempts >= 10) {
                    log_message("Превышено количество попыток для номера $phone_cleaned");
                    throw new Exception("Превышено количество попыток. Попробуйте позже.");
                }
                
                // Увеличиваем счетчик попыток
                $redis->setex($key_attempts, 900, $attempts + 1); // 15 минут
                
                // Проверяем код
                $key = "sms:code:$phone_cleaned";
                log_message("Проверка кода для номера $phone_cleaned, ключ Redis: $key, введенный код: $code");
                
                $stored_code = $redis->get($key);
                
                if ($stored_code !== null) {
                    log_message("Код найден в Redis: $stored_code");
                }
            } catch (Exception $e) {
                // Пробрасываем исключение, если это ошибка rate limiting
                if (strpos($e->getMessage(), 'Превышено количество попыток') !== false) {
                    throw $e;
                }
                log_message("Ошибка при работе с Redis: " . $e->getMessage());
            }
        }
        
        // Если код не найден в Redis, пытаемся получить из файла (fallback)
        if ($stored_code === null) {
            log_message("Код не найден в Redis, проверяем файловое хранилище...");
            $stored_code = getCodeFromFile($phone_cleaned);
            if ($stored_code !== false) {
                log_message("Код найден в файловом хранилище для номера $phone_cleaned");
            }
        }
        
        // Если код все еще не найден, выдаем ошибку
        if ($stored_code === null || $stored_code === false) {
            log_message("КРИТИЧЕСКАЯ ОШИБКА: Код не найден ни в Redis, ни в файловом хранилище для номера $phone_cleaned");
            throw new Exception("Код не найден или истек. Запросите новый код.");
        }
        
        log_message("Проверка кода: сохраненный=$stored_code, введенный=$code");
        
        if ($stored_code !== $code) {
            log_message("Неверный код для номера $phone_cleaned. Введен: $code, ожидался: $stored_code");
            throw new Exception("Неверный код");
        }
        
        // Код верный - удаляем его из Redis и файла, создаем сессию подтверждения
        if ($redis) {
            try {
                $key = "sms:code:$phone_cleaned";
                $redis->del($key);
                $key_attempts = "sms:ratelimit:attempts:$phone_cleaned";
                $redis->del($key_attempts); // Сбрасываем счетчик попыток
            } catch (Exception $e) {
                log_message("Ошибка при удалении кода из Redis: " . $e->getMessage());
            }
        }
        deleteCodeFromFile($phone_cleaned); // Удаляем из файла тоже
        
        // Создаем токен подтверждения (действует 1 час) - только если Redis доступен
        $verify_token = bin2hex(random_bytes(32));
        if ($redis) {
            try {
                $verify_key = "sms:verified:$phone_cleaned";
                $redis->setex($verify_key, 3600, $verify_token);
                log_message("Токен верификации сохранен в Redis для номера $phone_cleaned");
            } catch (Exception $e) {
                log_message("Ошибка при создании токена верификации: " . $e->getMessage());
            }
        } else {
            log_message("Токен верификации сгенерирован, но не сохранен (Redis недоступен)");
        }
        
        log_message("Код подтвержден для номера: $phone_cleaned");
        
        echo json_encode([
            'success' => true,
            'message' => 'Код подтвержден',
            'token' => $verify_token // Токен для последующей проверки
        ], JSON_UNESCAPED_UNICODE);
        
    } elseif ($action === 'check_verified') {
        // Проверка статуса верификации (для проверки перед отправкой формы)
        $phone = $_POST['phonenumber'] ?? '';
        $token = $_POST['token'] ?? '';
        
        if (empty($phone) || empty($token)) {
            throw new Exception("Данные не указаны");
        }
        
        $phone_cleaned = clear_phone($phone);
        $redis = getRedis();
        
        if (!$redis) {
            throw new Exception("Сервис временно недоступен");
        }
        
        $verify_key = "sms:verified:$phone_cleaned";
        $stored_token = $redis->get($verify_key);
        
        if ($stored_token === null || $stored_token !== $token) {
            echo json_encode([
                'success' => false,
                'verified' => false
            ], JSON_UNESCAPED_UNICODE);
        } else {
            echo json_encode([
                'success' => true,
                'verified' => true
            ], JSON_UNESCAPED_UNICODE);
        }
        
    } else {
        throw new Exception("Неизвестное действие");
    }
    
} catch (Exception $e) {
    log_message("Ошибка: " . $e->getMessage());
    http_response_code(400);
    echo json_encode([
        'success' => false,
        'message' => $e->getMessage()
    ], JSON_UNESCAPED_UNICODE);
}
