<?php
/**
 * کلاینت مستقل API «شماره مجازی» سرویس Callinoo/Ozvinoo
 * ------------------------------------------------------------
 * فقط همان Endpoint هایی که در مستندات ارائه‌شده توسط کاربر آمده پیاده‌سازی
 * شده‌اند: applications, get-prices, getNumber, getCode, logout.
 * هیچ Endpoint یا پارامتر دیگری (Stars/Premium/Numbers V2/...) حدس زده
 * یا اضافه نشده است.
 *
 * ساختار Endpoint ها طبق مستندات:
 *   GET  /web/{token}/applications
 *   GET  /web/{token}/get-prices/{service_id}
 *   GET|POST /web/{token}/getNumber/{service_id}/{country}
 *   GET|POST /web/{token}/getCode/{request_id}
 *   GET|POST /web/{token}/logout/{request_id}
 *
 * توکن هرگز در این فایل هاردکد نشده و هرگز در لاگ چاپ نمی‌شود.
 */

require_once __DIR__ . '/request.php';

class CallinooClient
{
    private $apiUrl;
    private $token;
    private $timeoutMs;

    public function __construct($apiUrl, $token, $timeoutMs = 8000)
    {
        $this->apiUrl    = rtrim((string) $apiUrl, '/');
        $this->token     = (string) $token;
        $this->timeoutMs = $timeoutMs;
    }

    public function hasToken()
    {
        return trim($this->token) !== '' && $this->apiUrl !== '';
    }

    #-----------------[ عملیات مستند شده ]-----------------#
    public function get_applications()
    {
        return $this->callWithFallback('/applications');
    }

    public function get_prices($service_id)
    {
        return $this->callWithFallback('/get-prices/' . rawurlencode((string) $service_id));
    }

    public function get_number($service_id, $country)
    {
        return $this->callWithFallback('/getNumber/' . rawurlencode((string) $service_id) . '/' . rawurlencode((string) $country));
    }

    public function get_code($request_id)
    {
        return $this->callWithFallback('/getCode/' . rawurlencode((string) $request_id));
    }

    public function logout($request_id)
    {
        return $this->callWithFallback('/logout/' . rawurlencode((string) $request_id));
    }

    #-----------------[ داخلی ]-----------------#
    private function buildUrl($path)
    {
        return $this->apiUrl . '/web/' . rawurlencode($this->token) . $path;
    }

    // طبق مستندات اکثر Endpoint ها GET هستند و «در صورت نیاز» POST هم پشتیبانی
    // می‌شود. یعنی همان Endpoint مستند شده؛ فقط اگر GET با متد غیرمجاز مواجه
    // شد (404/405)، یک بار همان مسیر با POST هم امتحان می‌شود.
    private function callWithFallback($path)
    {
        $result = $this->call('GET', $path);
        if (!$result['ok'] && ($result['error_type'] ?? '') === 'method_not_allowed') {
            $result = $this->call('POST', $path);
        }
        return $result;
    }

    private function call($method, $path)
    {
        if (!$this->hasToken()) {
            return ['ok' => false, 'error_type' => 'auth', 'raw' => null];
        }

        $url = $this->buildUrl($path);
        $req = new CurlRequest($url);
        $req->setTimeout($this->timeoutMs);

        $res = ($method === 'POST') ? $req->post([]) : $req->get();

        if (!empty($res['error'])) {
            $isTimeout = (stripos($res['error'], 'timed out') !== false || stripos($res['error'], 'timeout') !== false);
            $this->logSafe('connection error (' . $method . ' ' . $path . '): ' . $res['error']);
            return ['ok' => false, 'error_type' => $isTimeout ? 'timeout' : 'connection', 'raw' => null];
        }

        $status = $res['status'] ?? 0;
        $body   = $res['body'] ?? '';

        if (!$status) {
            return ['ok' => false, 'error_type' => 'connection', 'raw' => null];
        }
        if ($status == 404 || $status == 405) {
            return ['ok' => false, 'error_type' => 'method_not_allowed', 'raw' => null];
        }
        if ($status >= 500) {
            $this->logSafe('server error ' . $status . ' on ' . $path);
            return ['ok' => false, 'error_type' => 'server', 'raw' => null];
        }

        $data = json_decode($body, true);
        if (!is_array($data)) {
            $this->logSafe('invalid json response (http ' . $status . ') on ' . $path);
            return ['ok' => false, 'error_type' => 'invalid_response', 'raw' => null];
        }

        $apiErrorMsg = $this->extractApiErrorMessage($data);
        if ($apiErrorMsg !== null) {
            $this->logSafe('api logical error on ' . $path . ': ' . $apiErrorMsg);
            return ['ok' => false, 'error_type' => vnClassifyApiError($apiErrorMsg), 'raw' => $apiErrorMsg];
        }

        if ($status >= 400) {
            $this->logSafe('http error ' . $status . ' on ' . $path);
            return ['ok' => false, 'error_type' => 'http_' . $status, 'raw' => null];
        }

        return ['ok' => true, 'data' => $data];
    }

    // چون ساختار دقیق پیام خطای این API در مستندات مشخص نشده، چند کلید متداول
    // را به‌صورت تدافعی بررسی می‌کنیم؛ Endpoint یا پارامتر جدیدی حدس زده نمی‌شود.
    private function extractApiErrorMessage($data)
    {
        if (isset($data['error']) && $data['error'] !== false && $data['error'] !== null && $data['error'] !== '') {
            return is_string($data['error']) ? $data['error'] : json_encode($data['error'], JSON_UNESCAPED_UNICODE);
        }
        if (isset($data['status']) && is_string($data['status']) && strtolower($data['status']) === 'error') {
            return (string) ($data['message'] ?? $data['msg'] ?? 'error');
        }
        if (isset($data['success']) && $data['success'] === false) {
            return (string) ($data['message'] ?? $data['msg'] ?? 'error');
        }
        if (isset($data['ok']) && $data['ok'] === false) {
            return (string) ($data['message'] ?? $data['msg'] ?? 'error');
        }
        return null;
    }

    // هرگز URL/توکن را در لاگ چاپ نکن؛ فقط پیام خطای کتابخانه cURL یا کد HTTP.
    private function logSafe($msg)
    {
        error_log('[CallinooClient] ' . $msg);
    }
}

/**
 * دسته‌بندی پیام خطای متنی API روی چند دسته‌ی شناخته‌شده (بر اساس کلیدواژه)،
 * صرفاً برای انتخاب یک پیام فارسی مناسب. متن خام API هرگز مستقیم به کاربر
 * نمایش داده نمی‌شود.
 */
function vnClassifyApiError($message)
{
    $m = mb_strtolower((string) $message);
    if (strpos($m, 'token') !== false || strpos($m, 'auth') !== false || strpos($m, 'unauthor') !== false) {
        return 'auth';
    }
    if (strpos($m, 'countr') !== false) {
        return 'invalid_country';
    }
    if (strpos($m, 'service') !== false || strpos($m, 'application') !== false) {
        return 'invalid_service';
    }
    if (strpos($m, 'not found') !== false || strpos($m, 'not available') !== false || strpos($m, 'no number') !== false
        || strpos($m, 'sold out') !== false || strpos($m, 'unavailable') !== false || strpos($m, 'empty') !== false
        || strpos($m, 'out of stock') !== false) {
        return 'no_stock';
    }
    if (strpos($m, 'balance') !== false || strpos($m, 'credit') !== false || strpos($m, 'insufficient') !== false) {
        return 'provider_balance';
    }
    if (strpos($m, 'request') !== false || strpos($m, 'order') !== false || strpos($m, 'invalid id') !== false) {
        return 'invalid_request';
    }
    if (strpos($m, 'wait') !== false || strpos($m, 'pending') !== false || strpos($m, 'not yet') !== false || strpos($m, 'no code') !== false) {
        return 'code_pending';
    }
    return 'unknown';
}

/**
 * نگاشت نوع خطا به پیام فارسی مناسب کاربر. طبق نیازمندی، پیام خام API هرگز
 * مستقیم نمایش داده نمی‌شود.
 */
function vnErrorMessage($errorType)
{
    $map = [
        'auth'               => '❌ خطا در احراز هویت با سرویس شماره مجازی. لطفاً موضوع را به مدیر ربات اطلاع دهید.',
        'invalid_service'    => '❌ این سرویس معتبر نیست یا در حال حاضر در دسترس نیست.',
        'invalid_country'    => '❌ این کشور برای سرویس انتخابی معتبر نیست.',
        'no_stock'           => '❌ در حال حاضر شماره‌ای برای این سرویس/کشور موجود نیست. لطفاً سرویس یا کشور دیگری را امتحان کنید.',
        'provider_balance'   => '❌ سرویس شماره مجازی موقتاً در دسترس نیست. لطفاً بعداً تلاش کنید.',
        'invalid_request'    => '❌ این سفارش دیگر معتبر نیست یا منقضی شده است.',
        'code_pending'       => '⏳ هنوز کدی برای این شماره دریافت نشده است. کمی صبر کنید و دوباره تلاش کنید.',
        'timeout'            => '⏱ زمان درخواست به پایان رسید. لطفاً دوباره تلاش کنید.',
        'connection'         => '❌ خطا در برقراری ارتباط با سرویس شماره مجازی. لطفاً چند لحظه‌ی دیگر دوباره تلاش کنید.',
        'server'             => '❌ سرویس شماره مجازی موقتاً در دسترس نیست. لطفاً بعداً تلاش کنید.',
        'method_not_allowed' => '❌ سرویس شماره مجازی موقتاً در دسترس نیست. لطفاً بعداً تلاش کنید.',
        'invalid_response'   => '❌ پاسخ نامعتبری از سرویس شماره مجازی دریافت شد. لطفاً بعداً تلاش کنید.',
    ];
    return $map[$errorType] ?? '❌ خطای نامشخصی رخ داد. لطفاً بعداً تلاش کنید یا با پشتیبانی تماس بگیرید.';
}
