<?php
/**
 * GEM ACC - Android SMS payment auto approval endpoint
 *
 * Android admin app sends candidate bank deposit SMS messages here.
 * The server parses the amount, deduplicates the SMS, matches it against ONE
 * pending CARD order, and auto-approves only when the match is unambiguous.
 */
require_once 'config.php';
require_once '_vpn_helpers.php';
require_once __DIR__ . '/../../../src/GemAcc/ReferralService.php';
header('Content-Type: application/json; charset=utf-8');

function sms_env_value(string $key, string $default=''): string {
    try { if (function_exists('env')) return (string)env($key, $default); } catch (Throwable $e) {}
    $v = getenv($key);
    return $v === false ? $default : (string)$v;
}

function sms_setting(PDO $pdo, string $key, string $default=''): string {
    try {
        $st = $pdo->prepare("SELECT value FROM settings WHERE key_name=? LIMIT 1");
        $st->execute([$key]);
        $v = $st->fetchColumn();
        return $v === false || $v === null ? $default : (string)$v;
    } catch (Throwable $e) { return $default; }
}

function sms_bool(PDO $pdo, string $key, bool $default=true): bool {
    $v = strtolower(trim(sms_setting($pdo, $key, $default ? '1' : '0')));
    return in_array($v, ['1','true','yes','on','enabled'], true);
}

function sms_require_app_token(PDO $pdo): int {
    $configured = trim(sms_env_value('SMS_APP_TOKEN', ''));
    if ($configured === '') $configured = trim(sms_setting($pdo, 'sms_app_token', ''));
    // Backward-compatible fallback for one deployment cycle.
    $provided = '';
    if (!empty($_SERVER['HTTP_X_SMS_APP_TOKEN'])) $provided = (string)$_SERVER['HTTP_X_SMS_APP_TOKEN'];
    if ($provided === '' && !empty($_POST['sms_app_token'])) $provided = (string)$_POST['sms_app_token'];
    if ($configured === '' || strlen($configured) < 24 || !hash_equals($configured, trim($provided))) {
        http_response_code(401);
        echo json_encode(['ok'=>false,'error'=>'Unauthorized SMS app token'], JSON_UNESCAPED_UNICODE); exit;
    }
    $adminId = (int)sms_env_value('SMS_APP_ADMIN_ID', '0');
    if ($adminId <= 0) {
        $ids = array_values(array_filter(array_map('trim', explode(',', (string)(defined('ADMIN_IDS') ? ADMIN_IDS : '')))));
        $adminId = (int)($ids[0] ?? 0);
    }
    return $adminId;
}

function sms_ensure_schema(PDO $pdo): void {
    try {
        $pdo->exec("CREATE TABLE IF NOT EXISTS sms_payment_events (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            sms_hash CHAR(64) NOT NULL,
            sender VARCHAR(120) DEFAULT NULL,
            body_text MEDIUMTEXT NOT NULL,
            amount_rial BIGINT DEFAULT NULL,
            received_at DATETIME DEFAULT NULL,
            uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            status VARCHAR(30) NOT NULL DEFAULT 'received',
            matched_order_id BIGINT UNSIGNED DEFAULT NULL,
            match_count INT NOT NULL DEFAULT 0,
            error_text TEXT DEFAULT NULL,
            raw_json LONGTEXT DEFAULT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY uq_sms_hash (sms_hash),
            KEY idx_amount_received (amount_rial, received_at),
            KEY idx_status_uploaded (status, uploaded_at),
            KEY idx_matched_order (matched_order_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
        $pdo->exec("CREATE TABLE IF NOT EXISTS sms_payment_matches (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            event_id BIGINT UNSIGNED NOT NULL,
            order_id BIGINT UNSIGNED NOT NULL,
            amount_rial BIGINT NOT NULL,
            match_status VARCHAR(30) NOT NULL DEFAULT 'matched',
            approved_at DATETIME DEFAULT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            note TEXT DEFAULT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY uq_event_order (event_id, order_id),
            UNIQUE KEY uq_order_once (order_id),
            KEY idx_event (event_id),
            KEY idx_status (match_status)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    } catch (Throwable $e) { error_log('[sms_schema] '.$e->getMessage()); }
}

function sms_digits_en(string $s): string {
    static $map = ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9'];
    return strtr($s, $map);
}
function sms_norm(string $s): string {
    $s = sms_digits_en($s);
    $s = preg_replace('/[\x{200C}-\x{200F}\x{202A}-\x{202E}\x{2066}-\x{2069}]/u', ' ', $s);
    $s = str_replace(['ي','ك','ة'], ['ی','ک','ه'], $s);
    return $s;
}
function sms_clean_num(string $n): int {
    $n = sms_digits_en($n);
    $n = preg_replace('/[^0-9]/', '', $n);
    if ($n === '') return 0;
    // Ignore obvious card/account/reference numbers.
    if (strlen($n) > 13) return 0;
    return (int)$n;
}
function sms_extract_amount_rial(string $body, int $clientAmount=0): int {
    $text = sms_norm($body);
    // Prefer the transfer part and ignore balance/current balance sections.
    $parts = preg_split('/(مانده|موجودی|balance|bal\b|available)/iu', $text);
    $head = trim((string)($parts[0] ?? $text));
    $candidates = [];

    // Bank Shahr and similar formats: مبلغ:3,627,750ریال before موجودی.
    // This prevents card/account numbers in `واریز به:` from being used as amounts.
    if (preg_match_all('/(?:مبلغ|amount|amt)\s*[:：\-ـ]?\s*([0-9][0-9,\.\s]{2,})\s*(ریال|ريال|rial|irr)/iu', $head, $m, PREG_SET_ORDER)) {
        foreach ($m as $x) { $v = sms_clean_num($x[1]); if ($v >= 10000) $candidates[] = $v; }
    }
    if (!$candidates && preg_match_all('/(?:مبلغ|amount|amt)\s*[:：\-ـ]?\s*([0-9][0-9,\.\s]{2,})\s*(تومان|تومن|tmn|toman)/iu', $head, $m, PREG_SET_ORDER)) {
        foreach ($m as $x) { $v = sms_clean_num($x[1]); if ($v >= 1000) $candidates[] = $v * 10; }
    }

    // Strong formats with explicit unit.
    if (!$candidates && preg_match_all('/([0-9][0-9,\.\s]{2,})\s*(ریال|ريال|rial|irr)/iu', $head, $m, PREG_SET_ORDER)) {
        foreach ($m as $x) { $v = sms_clean_num($x[1]); if ($v >= 10000) $candidates[] = $v; }
    }
    if (preg_match_all('/([0-9][0-9,\.\s]{2,})\s*(تومان|تومن|tmn|toman)/iu', $head, $m, PREG_SET_ORDER)) {
        foreach ($m as $x) { $v = sms_clean_num($x[1]); if ($v >= 1000) $candidates[] = $v * 10; }
    }

    // Bank Mellat and several Iranian banks send compact SMS without a unit:
    // حساب 1362183352 / واریز126,630 / مانده478,108,470
    // The number right after واریز/دریافت/بستانکار is treated as Rial.
    if (!$candidates && preg_match_all('/(?:واریز(?:ی)?|واريز(?:ی)?|دریافت|دريافت|بستانکار|بستانكار|افزایش|افزايش|credited|credit|deposit|received)\s*[:：\-ـ]?\s*[+＋]?\s*([0-9][0-9,\.\s]{2,})/iu', $head, $m, PREG_SET_ORDER)) {
        foreach ($m as $x) { $v = sms_clean_num($x[1]); if ($v >= 10000) $candidates[] = $v; }
    }

    if (!$candidates && $clientAmount > 0) $candidates[] = $clientAmount;
    if (!$candidates) return 0;
    // The first valid amount before balance is normally the deposited amount.
    return (int)$candidates[0];
}
function sms_is_deposit_message(string $body): bool {
    $t = mb_strtolower(sms_norm($body), 'UTF-8');
    $hasDeposit = preg_match('/(واریز|واريز|واریزی|واريزی|دریافت|دريافت|بستانکار|بستانكار|افزایش|افزايش|انتقال وجه کارتی|انتقال وجه كارتی|credited|credit|deposit|received)/iu', $t);
    $hasWithdrawal = preg_match('/(برداشت|خرید|خريد|کسر|كسر|پرداخت|انتقال وجه خروجی|debit|withdraw|purchase)/iu', $t);
    return (bool)$hasDeposit && !$hasWithdrawal;
}
function sms_mask_body(string $body): string {
    $body = sms_norm($body);
    // Keep amounts readable, but mask long identifiers/cards.
    return preg_replace('/(?<!\d)\d{10,}(?!\d)/u', '********', $body);
}
function sms_order_payable_rial(array $order): int {
    $meta = [];
    if (!empty($order['receipt_text'])) {
        $d = json_decode((string)$order['receipt_text'], true);
        if (is_array($d)) $meta = $d;
    }
    if (isset($meta['payable_rial']) && (int)$meta['payable_rial'] > 0) return (int)$meta['payable_rial'];
    if (isset($meta['payable_amount']) && (int)$meta['payable_amount'] > 0) return ((int)$meta['payable_amount']) * 10;
    if (isset($meta['payable_price']) && (int)$meta['payable_price'] > 0) return ((int)$meta['payable_price']) * 10;
    return max(0, (int)($order['price'] ?? 0)) * 10;
}
function sms_admin_notify(string $title, array $lines=[]): void {
    if (!function_exists('tg')) return;
    $txt = "🤖 <b>تأیید خودکار پیامکی</b>\n" . $title;
    foreach ($lines as $k=>$v) $txt .= "\n" . tgHtmlEscape((string)$k) . ': <b>' . tgHtmlEscape((string)$v) . '</b>';
    foreach (array_filter(array_map('trim', explode(',', (string)(defined('ADMIN_IDS') ? ADMIN_IDS : '')))) as $adminId) {
        try { tg('sendMessage', ['chat_id'=>$adminId, 'text'=>$txt, 'parse_mode'=>'HTML']); } catch (Throwable $ignored) {}
    }
}
function sms_match_and_maybe_approve(PDO $pdo, int $eventId, int $amountRial, ?string $receivedAt, int $adminId, bool $notifyAdmins=true): array {
    if ($amountRial <= 0) return ['status'=>'ignored','error'=>'amount_not_found','matches'=>0];
    $windowHours = max(1, min(240, (int)sms_setting($pdo, 'sms_auto_approve_window_hours', sms_env_value('SMS_AUTO_APPROVE_WINDOW_HOURS', '48'))));
    $tolerance = max(0, (int)sms_setting($pdo, 'sms_auto_approve_tolerance_rial', sms_env_value('SMS_AUTO_APPROVE_TOLERANCE_RIAL', '0')));
    $requireReceiptEnv = strtolower(trim(sms_env_value('SMS_AUTO_APPROVE_REQUIRE_RECEIPT', '')));
    $requireReceipt = $requireReceiptEnv !== ''
        ? in_array($requireReceiptEnv, ['1','true','yes','on','enabled'], true)
        : sms_bool($pdo, 'sms_auto_approve_require_receipt', false);
    $since = date('Y-m-d H:i:s', time() - ($windowHours * 3600));

    $sql = "SELECT o.*, u.first_name, u.username
              FROM orders o
              LEFT JOIN users u ON u.id=o.user_id
             WHERE UPPER(COALESCE(o.payment_method,''))='CARD'
               AND UPPER(COALESCE(o.status,'')) IN ('PENDING','WAITING_PAYMENT','PAID')
               AND LOWER(COALESCE(o.payment_status,'')) NOT IN ('paid','completed','success')
               AND o.created_at >= ?
             ORDER BY o.created_at DESC
             LIMIT 200";
    $st = $pdo->prepare($sql); $st->execute([$since]);
    $orders = $st->fetchAll(PDO::FETCH_ASSOC);
    $matches = [];
    foreach ($orders as $o) {
        if ($requireReceipt && empty($o['receipt_file_id']) && empty($o['receipt_text'])) continue;
        $payable = sms_order_payable_rial($o);
        if ($payable > 0 && abs($payable - $amountRial) <= $tolerance) {
            $o['_payable_rial'] = $payable;
            $matches[] = $o;
        }
    }
    if (count($matches) === 0) {
        $pdo->prepare("UPDATE sms_payment_events SET status='no_match', match_count=0, error_text=NULL WHERE id=?")->execute([$eventId]);
        return ['status'=>'no_match','matches'=>0,'amount_rial'=>$amountRial];
    }
    if (count($matches) > 1) {
        $pdo->prepare("UPDATE sms_payment_events SET status='ambiguous', match_count=?, error_text=? WHERE id=?")->execute([count($matches), 'چند سفارش با همین مبلغ پیدا شد؛ تأیید دستی لازم است.', $eventId]);
        if ($notifyAdmins) sms_admin_notify('⚠️ چند سفارش با مبلغ پیامک یکی است؛ تایید خودکار انجام نشد.', ['مبلغ ریال'=>number_format($amountRial), 'تعداد سفارش مشابه'=>count($matches)]);
        return ['status'=>'ambiguous','matches'=>count($matches),'amount_rial'=>$amountRial];
    }

    $order = $matches[0];
    $orderId = (int)$order['id'];
    try {
        $ins = $pdo->prepare("INSERT IGNORE INTO sms_payment_matches(event_id, order_id, amount_rial, match_status, note) VALUES(?,?,?,?,?)");
        $ins->execute([$eventId, $orderId, $amountRial, 'matched', 'auto_sms_match']);
        if ($ins->rowCount() === 0) {
            $pdo->prepare("UPDATE sms_payment_events SET status='duplicate_match', matched_order_id=?, match_count=1, error_text='این سفارش قبلاً با پیامک دیگری مچ شده است' WHERE id=?")->execute([$orderId, $eventId]);
            return ['status'=>'duplicate_match','matches'=>1,'order_id'=>$orderId];
        }
    } catch (Throwable $e) {
        $pdo->prepare("UPDATE sms_payment_events SET status='match_lock_failed', matched_order_id=?, match_count=1, error_text=? WHERE id=?")->execute([$orderId, $e->getMessage(), $eventId]);
        return ['status'=>'match_lock_failed','matches'=>1,'order_id'=>$orderId,'error'=>$e->getMessage()];
    }

    try {
        if (function_exists('tg')) {
            tg('sendMessage', [
                'chat_id'=>(int)$order['user_id'],
                'text'=>"✅ پرداخت سفارش #{$orderId} به‌صورت خودکار بر اساس پیامک واریزی بانکی تأیید شد.\n⏳ سفارش شما در حال آماده‌سازی و تحویل است."
            ]);
        }
    } catch (Throwable $ignored) {}

    $result = vpnApproveOrderAndNotify($pdo, $orderId, true);
    $matchStatus = 'failed';
    if (!empty($result['success'])) $matchStatus = 'approved';
    elseif (!empty($result['queued'])) $matchStatus = 'queued';
    elseif (!empty($result['already_delivered'])) $matchStatus = 'already_delivered';
    $msg = (string)($result['message'] ?? '');
    $pdo->prepare("UPDATE sms_payment_matches SET match_status=?, approved_at=IF(? IN ('approved','queued','already_delivered'), NOW(), approved_at), note=? WHERE event_id=? AND order_id=?")
        ->execute([$matchStatus, $matchStatus, $msg, $eventId, $orderId]);
    $pdo->prepare("UPDATE sms_payment_events SET status=?, matched_order_id=?, match_count=1, error_text=? WHERE id=?")
        ->execute([$matchStatus, $orderId, $msg, $eventId]);

    if (in_array($matchStatus, ['approved','queued','already_delivered'], true)) {
        try {
            $referralReward = \GemAcc\ReferralService::rewardFirstPurchase($pdo, (int)$order['user_id'], $orderId, (int)$order['price']);
            if (!empty($referralReward['ok']) && (int)($referralReward['amount'] ?? 0) > 0 && function_exists('tg')) {
                tg('sendMessage', ['chat_id'=>(int)$referralReward['referrer_id'], 'text'=>'🎁 پاداش معرفی شما برای اولین خرید کاربر جدید اضافه شد: ' . money((int)($referralReward['amount'] ?? 0))]);
            }
        } catch (Throwable $ignored) {}
        if ($notifyAdmins) sms_admin_notify($matchStatus === 'queued' ? '✅ پرداخت تأیید شد؛ سفارش به صف تحویل رفت.' : '✅ سفارش با پیامک واریزی خودکار تأیید شد.', ['سفارش'=>'#'.$orderId, 'کاربر'=>(string)$order['user_id'], 'مبلغ ریال'=>number_format($amountRial)]);
    } elseif ($notifyAdmins) {
        sms_admin_notify('❌ پیامک با سفارش مچ شد اما تأیید ناموفق بود.', ['سفارش'=>'#'.$orderId, 'خطا'=>$msg ?: 'نامشخص']);
    }
    return ['status'=>$matchStatus,'matches'=>1,'order_id'=>$orderId,'amount_rial'=>$amountRial,'result'=>$result];
}

try {
    sms_ensure_schema($pdo);
    $adminId = sms_require_app_token($pdo);
    $raw = file_get_contents('php://input');
    $input = json_decode($raw ?: '', true);
    if (!is_array($input)) $input = $_POST;
    if ((string)($input['action'] ?? '') === 'ping') {
        echo json_encode([
            'ok'=>true,
            'status'=>'online',
            'auto_approve_enabled'=>sms_bool($pdo, 'sms_auto_approve_enabled', true),
            'server_time'=>date('c'),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }
    if (!sms_bool($pdo, 'sms_auto_approve_enabled', true)) {
        echo json_encode(['ok'=>false,'error'=>'SMS auto approve is disabled'], JSON_UNESCAPED_UNICODE); exit;
    }
    $sender = trim((string)($input['sender'] ?? ''));
    $body = (string)($input['body'] ?? '');
    $clientAmount = (int)($input['amount_rial'] ?? 0);
    $receivedAtRaw = (string)($input['received_at'] ?? '');
    $receivedAt = null;
    if ($receivedAtRaw !== '') {
        $ts = is_numeric($receivedAtRaw) ? ((int)$receivedAtRaw / 1000) : strtotime($receivedAtRaw);
        if ($ts > 0) $receivedAt = date('Y-m-d H:i:s', (int)$ts);
    }
    if ($receivedAt === null) $receivedAt = date('Y-m-d H:i:s');
    if (trim($body) === '') { echo json_encode(['ok'=>false,'error'=>'SMS body is empty'], JSON_UNESCAPED_UNICODE); exit; }

    $allowed = trim(sms_setting($pdo, 'sms_auto_approve_allowed_senders', sms_env_value('SMS_AUTO_APPROVE_ALLOWED_SENDERS', '')));
    if ($allowed !== '' && $sender !== '') {
        $okSender = false;
        foreach (array_filter(array_map('trim', explode(',', $allowed))) as $needle) {
            if ($needle !== '' && stripos($sender, $needle) !== false) { $okSender = true; break; }
        }
        if (!$okSender) { echo json_encode(['ok'=>true,'status'=>'ignored','reason'=>'sender_not_allowed'], JSON_UNESCAPED_UNICODE); exit; }
    }

    $isDeposit = sms_is_deposit_message($body);
    $amountRial = sms_extract_amount_rial($body, $clientAmount);
    $hash = strtolower(trim((string)($input['sms_hash'] ?? '')));
    if (!preg_match('/^[a-f0-9]{64}$/', $hash)) $hash = hash('sha256', $sender . '|' . $receivedAt . '|' . sms_norm($body));
    $maskedBody = sms_mask_body($body);
    $rawJson = json_encode(['sender'=>$sender,'received_at'=>$receivedAt,'manual_scan'=>!empty($input['manual_scan']),'client_amount_rial'=>$clientAmount,'ua'=>$_SERVER['HTTP_USER_AGENT'] ?? ''], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    try {
        $st = $pdo->prepare("INSERT INTO sms_payment_events(sms_hash,sender,body_text,amount_rial,received_at,status,raw_json) VALUES(?,?,?,?,?,?,?)");
        $st->execute([$hash, $sender, $maskedBody, $amountRial ?: null, $receivedAt, $isDeposit ? 'received' : 'ignored', $rawJson]);
        $eventId = (int)$pdo->lastInsertId();
    } catch (Throwable $e) {
        // Duplicate SMS: try matching again if it was previously no_match/received.
        $st = $pdo->prepare("SELECT * FROM sms_payment_events WHERE sms_hash=? LIMIT 1");
        $st->execute([$hash]);
        $existing = $st->fetch(PDO::FETCH_ASSOC);
        if (!$existing) throw $e;
        $eventId = (int)$existing['id'];
        $amountRial = (int)($existing['amount_rial'] ?: $amountRial);
        $oldStatus = (string)($existing['status'] ?? '');
        if (in_array($oldStatus, ['approved','queued','already_delivered','ambiguous','duplicate_match'], true)) {
            echo json_encode(['ok'=>true,'duplicate'=>true,'status'=>$oldStatus,'order_id'=>(int)($existing['matched_order_id'] ?? 0)], JSON_UNESCAPED_UNICODE); exit;
        }
    }

    if (!$isDeposit) {
        $pdo->prepare("UPDATE sms_payment_events SET status='ignored', error_text='not_a_deposit_sms' WHERE id=?")->execute([$eventId]);
        echo json_encode(['ok'=>true,'status'=>'ignored','reason'=>'not_a_deposit_sms'], JSON_UNESCAPED_UNICODE); exit;
    }
    if ($amountRial <= 0) {
        $pdo->prepare("UPDATE sms_payment_events SET status='ignored', error_text='amount_not_found' WHERE id=?")->execute([$eventId]);
        echo json_encode(['ok'=>true,'status'=>'ignored','reason'=>'amount_not_found'], JSON_UNESCAPED_UNICODE); exit;
    }
    $notifyAdmins = sms_bool($pdo, 'sms_auto_approve_notify_admins', true);
    $match = sms_match_and_maybe_approve($pdo, $eventId, $amountRial, $receivedAt, $adminId, $notifyAdmins);
    echo json_encode(['ok'=>true,'event_id'=>$eventId,'amount_rial'=>$amountRial] + $match, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (Throwable $e) {
    error_log('[sms_payment_ingest] '.$e->getMessage());
    echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
