<?php
require_once 'config.php';
require_once '_vpn_helpers.php';
function admin_credentials_add_column_if_missing(PDO $pdo, string $table, string $column, string $definition): void {
    try {
        if (function_exists('gemacc_column_exists') && gemacc_column_exists($pdo, $table, $column)) return;
        $pdo->exec("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
    } catch (Throwable $e) {}
}

function admin_credentials_save_qr_upload(): ?string {
    if (empty($_FILES['qr_code']) || !is_array($_FILES['qr_code'])) return null;
    $f = $_FILES['qr_code'];
    if (($f['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) return null;
    if (($f['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) throw new RuntimeException('آپلود QR Code ناموفق بود');
    if ((int)($f['size'] ?? 0) > 8 * 1024 * 1024) throw new RuntimeException('حجم QR Code بیشتر از ۸ مگابایت است');
    $mime = '';
    if (function_exists('finfo_open')) { $fi=finfo_open(FILEINFO_MIME_TYPE); $mime=(string)finfo_file($fi,(string)$f['tmp_name']); finfo_close($fi); }
    if (($mime === '' || $mime === 'application/octet-stream') && function_exists('mime_content_type')) $mime=(string)mime_content_type((string)$f['tmp_name']);
    if (($mime === '' || $mime === 'application/octet-stream') && function_exists('getimagesize')) { $info=@getimagesize((string)$f['tmp_name']); $mime=(string)($info['mime'] ?? ''); }
    $map=['image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp'];
    if (!isset($map[$mime])) throw new RuntimeException('فرمت QR Code باید JPG، PNG یا WEBP باشد');
    $dir = dirname(__DIR__) . '/uploads/v2ray_qr';
    if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) throw new RuntimeException('ساخت پوشه QR Code ممکن نشد');
    $name = bin2hex(random_bytes(16)).'.'.$map[$mime];
    $dest = $dir.'/'.$name;
    if (!move_uploaded_file((string)$f['tmp_name'], $dest)) throw new RuntimeException('ذخیره QR Code ناموفق بود');
    return 'public/mini_app/uploads/v2ray_qr/'.$name;
}


function admin_credentials_local_qr_absolute(?string $relativePath): ?string {
    $relativePath = trim((string)$relativePath);
    if ($relativePath === '') return null;
    $projectRoot = dirname(__DIR__, 3);
    $allowedDir = realpath($projectRoot . '/public/mini_app/uploads/v2ray_qr');
    $candidate = realpath($projectRoot . '/' . ltrim($relativePath, '/'));
    if ($allowedDir === false || $candidate === false || !is_file($candidate)) return null;
    $prefix = rtrim($allowedDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    if ($candidate !== $allowedDir && strncmp($candidate, $prefix, strlen($prefix)) !== 0) return null;
    return $candidate;
}

function admin_credentials_delete_local_qr(?string $relativePath): void {
    $absolute = admin_credentials_local_qr_absolute($relativePath);
    if ($absolute !== null) @unlink($absolute);
}

function admin_credentials_text_chunks(string $text, int $limit = 3900): array {
    $text = trim($text);
    if ($text === '') return [];
    $chunks = [];
    while (function_exists('mb_strlen') ? mb_strlen($text, 'UTF-8') > $limit : strlen($text) > $limit) {
        $chunk = function_exists('mb_substr') ? mb_substr($text, 0, $limit, 'UTF-8') : substr($text, 0, $limit);
        $lineBreak = strrpos($chunk, "\n");
        $spaceBreak = strrpos($chunk, ' ');
        $breakAt = max($lineBreak === false ? 0 : $lineBreak, $spaceBreak === false ? 0 : $spaceBreak);
        if ($breakAt > (int)($limit * 0.55)) {
            $chunk = function_exists('mb_substr') ? mb_substr($chunk, 0, $breakAt, 'UTF-8') : substr($chunk, 0, $breakAt);
        }
        $chunks[] = trim($chunk);
        $used = function_exists('mb_strlen') ? mb_strlen($chunk, 'UTF-8') : strlen($chunk);
        $text = ltrim(function_exists('mb_substr') ? mb_substr($text, $used, null, 'UTF-8') : substr($text, $used));
    }
    if ($text !== '') $chunks[] = $text;
    return $chunks;
}

header('Content-Type: application/json; charset=utf-8');
try {
    $input = json_decode(file_get_contents('php://input'), true) ?: $_REQUEST;
    vpnEnsureColumns($pdo);
    $userId = miniapp_require_admin();
    try { $pdo->exec("ALTER TABLE credentials MODIFY sellable_until DATETIME NULL"); } catch (Throwable $e) {} // compatibility for older installs
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'sales_paused', "TINYINT(1) NOT NULL DEFAULT 0 AFTER `is_active`");
admin_credentials_add_column_if_missing($pdo, 'credentials', 'is_sold', "TINYINT(1) NOT NULL DEFAULT 0");
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'archived_at', "DATETIME NULL");
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'archived_by', "BIGINT NULL");
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'archive_reason', "VARCHAR(255) NULL");
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'status', "VARCHAR(30) NOT NULL DEFAULT 'available'");
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'username', "VARCHAR(128) NULL");
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'traffic_type', "VARCHAR(20) NOT NULL DEFAULT 'unlimited'");
    admin_credentials_add_column_if_missing($pdo, 'credentials', 'traffic_limit_gb', "INT UNSIGNED NULL AFTER `traffic_type`");
    admin_credentials_add_column_if_missing($pdo, 'plan_categories', 'service_name', "VARCHAR(100) NULL AFTER `service_type`");
    admin_credentials_add_column_if_missing($pdo, 'plan_categories', 'traffic_type', "VARCHAR(20) NOT NULL DEFAULT 'unlimited' AFTER `months`");
    admin_credentials_add_column_if_missing($pdo, 'plan_categories', 'traffic_limit_gb', "INT UNSIGNED NULL AFTER `traffic_type`");
    try { $pdo->exec("UPDATE credentials SET is_sold=0 WHERE is_sold IS NULL"); } catch (Throwable $e) {}
    try { $pdo->exec("UPDATE credentials SET status='available', sold_to_user_id=NULL, order_id=NULL, assigned_user_id=NULL, assigned_order_id=NULL, sold_at=NULL, delivered_at=NULL WHERE (status IS NULL OR status='' OR LOWER(status) IN ('sold','delivered','assigned','used')) AND used_seats=0 AND COALESCE(is_sold,0)=0 AND sold_to_user_id IS NULL AND order_id IS NULL"); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'GET') {
        $showInactive = (int)($_GET['show_inactive'] ?? 0);
        $includeSold = (int)($_GET['include_sold'] ?? 0) === 1;
        $soldOnly = (int)($_GET['sold_only'] ?? 0) === 1 || strtolower((string)($_GET['state'] ?? '')) === 'sold';
        $where = $showInactive ? '1=1' : 'c.is_active=1';
        if ($soldOnly) {
            // Credential-level sold view for admins: no user/order details are exposed here.
            $where .= " AND COALESCE(c.used_seats,0) > 0 AND LOWER(COALESCE(c.status,'available')) NOT IN ('archived','removed') AND COALESCE(c.archived_at,'1970-01-01')='1970-01-01'";
        } elseif (!$includeSold) {
            $where .= " AND LOWER(COALESCE(c.status,'available')) NOT IN ('archived','removed','sold','delivered','assigned','used') AND COALESCE(c.archived_at,'1970-01-01')='1970-01-01' AND NOT (COALESCE(c.is_sold,0)=1 AND COALESCE(c.used_seats,0)>=COALESCE(c.max_seats,1))";
        } else {
            $where .= " AND LOWER(COALESCE(c.status,'available')) NOT IN ('archived','removed') AND COALESCE(c.archived_at,'1970-01-01')='1970-01-01'";
        }
        $sql = "SELECT c.*,
                   COALESCE(CASE WHEN pc.traffic_type='limited' THEN CONCAT(pc.name,' - حجمی ',COALESCE(pc.traffic_limit_gb,0),' گیگ') ELSE CONCAT(pc.name,' - نامحدود') END,c.duration) category_name,
                   COALESCE(pc.months, c.months, CASE WHEN c.duration='1Y' THEN 12 ELSE 1 END) months,
                   COALESCE(pc.service_type,c.protocol,'openvpn_l2tp') service_type,
                   COALESCE(pc.service_name, CASE WHEN pc.service_type='windscribe' THEN 'Windscribe' WHEN pc.service_type='expressvpn' THEN 'ExpressVPN' WHEN pc.service_type='v2ray' THEN 'V2Ray' ELSE 'OpenVPN | L2TP' END) service_name,
                   GREATEST(COALESCE(c.max_seats,1)-COALESCE(c.used_seats,0),0) remaining_seats,
                   CASE WHEN COALESCE(c.used_seats,0)=0 AND COALESCE(c.is_sold,0)=0 AND LOWER(COALESCE(c.status,'available')) IN ('available','ready') THEN 0 ELSE 1 END is_sold_calc,
                   CASE WHEN c.is_active=1 AND COALESCE(c.sales_paused,0)=0 AND COALESCE(c.used_seats,0)<COALESCE(c.max_seats,1) AND LOWER(COALESCE(c.status,'available')) IN ('available','ready','partial') THEN 1 ELSE 0 END is_sellable
            FROM credentials c
            LEFT JOIN plan_categories pc ON pc.id=c.category_id
            WHERE {$where}
            GROUP BY c.id
            ORDER BY is_sold_calc ASC, c.created_at DESC
            LIMIT 500";
        $stmt = $pdo->query($sql);
        $credentials = $stmt->fetchAll();
        $stats = [];
        $st = $pdo->query("SELECT COALESCE(pc.code,c.duration) code, COALESCE(pc.name,c.duration) name, SUM(CASE WHEN c.is_active=1 AND COALESCE(c.sales_paused,0)=0 AND COALESCE(c.used_seats,0)<COALESCE(c.max_seats,1) AND LOWER(COALESCE(c.status,'available')) IN ('available','ready','partial') THEN GREATEST(COALESCE(c.max_seats,1)-COALESCE(c.used_seats,0),0) ELSE 0 END) available, SUM(CASE WHEN COALESCE(c.used_seats,0)>0 OR COALESCE(c.is_sold,0)>0 OR LOWER(COALESCE(c.status,'available')) IN ('sold','delivered','assigned','used') THEN 1 ELSE 0 END) sold, COUNT(DISTINCT c.id) total FROM credentials c LEFT JOIN plan_categories pc ON pc.id=c.category_id GROUP BY COALESCE(pc.code,c.duration), COALESCE(pc.name,c.duration)");
        foreach ($st->fetchAll() as $r) { $r['seats_remaining'] = (int)$r['available']; $stats[$r['code']] = $r; }
        $qst=$pdo->query("SELECT w.*, o.price, o.payment_method, o.status AS order_status, u.first_name, u.username FROM waiting_queue w JOIN orders o ON o.id=w.order_id LEFT JOIN users u ON u.id=w.user_id WHERE w.processed_at IS NULL AND o.status='QUEUED' ORDER BY w.id ASC LIMIT 100");
        $queuedOrders=$qst->fetchAll();
        echo json_encode(['ok'=>true,'data'=>['credentials'=>$credentials,'stats'=>$stats,'queued_orders'=>$queuedOrders]], JSON_UNESCAPED_UNICODE); exit;
    }
    $action = $input['action'] ?? '';
    if ($action === 'add') {
        $cat = null;
        $categoryId = (int)($input['category_id'] ?? 0);
        $duration = trim((string)($input['duration'] ?? ''));
        if ($categoryId) {
            $st=$pdo->prepare("SELECT * FROM plan_categories WHERE id=? AND deleted_at IS NULL");
            $st->execute([$categoryId]); $cat=$st->fetch(PDO::FETCH_ASSOC);
            if(!$cat){ echo json_encode(['ok'=>false,'error'=>'دسته پلن پیدا نشد'], JSON_UNESCAPED_UNICODE); exit; }
            $duration=(string)$cat['code'];
        }
        $rawSvc = strtolower((string)($cat['service_type'] ?? $input['service_type'] ?? 'openvpn_l2tp'));
        $serviceType = function_exists('vpnServiceType') ? vpnServiceType(['service_type'=>$rawSvc,'title'=>($cat['name']??'')]) : (in_array($rawSvc,['windscribe','expressvpn','v2ray'],true)?$rawSvc:'openvpn_l2tp');
        $trafficType = strtolower(trim((string)($input['traffic_type'] ?? ($cat['traffic_type'] ?? 'unlimited'))));
        if (!in_array($trafficType, ['limited','unlimited'], true)) $trafficType='unlimited';
        $trafficLimitGb = $trafficType === 'limited' ? max(1, (int)($input['traffic_limit_gb'] ?? ($cat['traffic_limit_gb'] ?? 1))) : null;
        if (!$duration) $duration='1M';
        $months = !empty($cat['months']) ? max(1,(int)$cat['months']) : ($duration==='1Y'?12:1);
        $qrPath = null;
        $qrFileId = null;
        $configText = trim((string)($input['config_text'] ?? ''));
        $pair = trim((string)($input['username_password'] ?? $input['pair'] ?? ''));
        $username = trim((string)($input['username'] ?? $input['email'] ?? ''));
        $password = trim((string)($input['password'] ?? ''));
        if ($pair && strpos($pair, ':') !== false) [$username,$password]=array_map('trim',explode(':',$pair,2));
        if ($serviceType === 'v2ray') {
            if ($configText === '') { echo json_encode(['ok'=>false,'error'=>'متن کانفیگ V2Ray الزامی است'], JSON_UNESCAPED_UNICODE); exit; }
            $qrPath = admin_credentials_save_qr_upload();
            $qrFileId = trim((string)($input['qr_code_file_id'] ?? '')) ?: null;
            if ($qrPath !== null) $qrFileId = null;
            $username = 'v2ray_' . substr(hash('sha256', $configText.'|'.microtime(true).'|'.random_int(1, PHP_INT_MAX)),0,24);
            $password = '-';
            $maxSeats = 1;
        } else {
            if (!$username || !$password) { echo json_encode(['ok'=>false,'error'=>'فرمت صحیح: username:password'], JSON_UNESCAPED_UNICODE); exit; }
            if (!preg_match('/^[A-Za-z0-9_.@\-\+]{3,128}$/', $username)) { echo json_encode(['ok'=>false,'error'=>'یوزرنیم نامعتبر است'], JSON_UNESCAPED_UNICODE); exit; }
            $maxSeats = max(1, min(100, (int)($input['max_seats'] ?? $input['capacity'] ?? ($serviceType === 'windscribe' ? 15 : 1))));
        }
        $st=$pdo->prepare("SELECT id FROM credentials WHERE email=? LIMIT 1"); $st->execute([$username]);
        if($st->fetch()){ echo json_encode(['ok'=>false,'error'=>'این اشتراک قبلاً ثبت شده'], JSON_UNESCAPED_UNICODE); exit; }
        $st=$pdo->prepare("INSERT INTO credentials (category_id,email,username,password,config_text,qr_code_file_id,qr_code_path,duration,months,protocol,max_seats,traffic_type,traffic_limit_gb,used_seats,sellable_until,is_active,is_sold,status,assigned_user_id,assigned_order_id,sold_to_user_id,order_id,sold_at,delivered_at,created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,0,DATE_ADD(CURDATE(), INTERVAL 10 YEAR),1,0,'available',NULL,NULL,NULL,NULL,NULL,NULL,?)");
        $st->execute([$categoryId ?: null,$username,$username,$password,$configText ?: null,$qrFileId,$qrPath,$duration,$months,$serviceType,$maxSeats,$trafficType,$trafficLimitGb,$userId]);
        $newId=(int)$pdo->lastInsertId();
        $q=vpnProcessWaitingQueue($pdo,$duration,20,$categoryId?:null);
        $processedOrderIds=[]; foreach(($q['processed']??[]) as $pr){ if(is_array($pr)&&!empty($pr['order_id']))$processedOrderIds[]=(int)$pr['order_id']; }
        $retry=$processedOrderIds?vpnRetryPendingDeliveryNotifications($pdo,20,$processedOrderIds):['processed'=>[],'processed_count'=>0];
        $msg=$serviceType==='v2ray'?'کانفیگ V2Ray اضافه شد':'اکانت اختصاصی اضافه شد';
        if(!empty($q['processed']))$msg.=' و سفارش‌های صف به‌صورت خودکار بررسی و تحویل شدند';
        gemaccAdminActionNotify('اشتراک جدید به موجودی اضافه شد',[
            'شناسه اشتراک'=>$newId,'سرویس'=>$serviceType,'دسته'=>$duration,
            'شناسه نمایشی'=>$serviceType==='v2ray'?'V2Ray #'.$newId:$username,
            'نوع ترافیک'=>$trafficType==='limited'?($trafficLimitGb.' گیگ'):'نامحدود',
            'QR Code'=>($qrPath||$qrFileId)?'ثبت شد':'ثبت نشد','تحویل خودکار صف'=>count($q['processed']??[]),
        ],$userId);
        echo json_encode(['ok'=>true,'message'=>$msg,'id'=>$newId,'queue'=>$q,'notification_retry'=>$retry], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); exit;
    }
    if ($action === 'edit_v2ray') {
        $id=(int)($input['credential_id'] ?? $input['id'] ?? 0);
        $configText=trim((string)($input['config_text'] ?? ''));
        $updateDeliveries=!isset($input['update_deliveries']) || (int)$input['update_deliveries']===1;
        if($id<=0 || $configText===''){ echo json_encode(['ok'=>false,'error'=>'شناسه و متن کانفیگ V2Ray الزامی است'], JSON_UNESCAPED_UNICODE); exit; }
        $st=$pdo->prepare("SELECT c.*,COALESCE(pc.service_type,c.protocol,'openvpn_l2tp') resolved_service_type FROM credentials c LEFT JOIN plan_categories pc ON pc.id=c.category_id WHERE c.id=? LIMIT 1");
        $st->execute([$id]);$row=$st->fetch(PDO::FETCH_ASSOC);
        if(!$row){ echo json_encode(['ok'=>false,'error'=>'کانفیگ پیدا نشد'], JSON_UNESCAPED_UNICODE); exit; }
        $serviceType=function_exists('vpnServiceType')?vpnServiceType(['service_type'=>$row['resolved_service_type']??'','title'=>$row['duration']??'']):strtolower((string)($row['resolved_service_type']??''));
        if($serviceType!=='v2ray'){ echo json_encode(['ok'=>false,'error'=>'این رکورد از نوع V2Ray نیست'], JSON_UNESCAPED_UNICODE); exit; }
        $qrPath=admin_credentials_save_qr_upload();
        $qrFileId=trim((string)($input['qr_code_file_id']??''));
        if($qrPath!==null) $qrFileId='';
        $oldConfig=(string)($row['config_text']??'');
        $oldQrPath=(string)($row['qr_code_path']??'');
        $pdo->beginTransaction();
        $sql="UPDATE credentials SET config_text=?";
        $args=[$configText];
        if($qrPath!==null){$sql.=', qr_code_path=?, qr_code_file_id=NULL';$args[]=$qrPath;}
        elseif($qrFileId!==''){$sql.=', qr_code_file_id=?, qr_code_path=NULL';$args[]=$qrFileId;}
        $sql.=', updated_at=NOW() WHERE id=?';$args[]=$id;
        $pdo->prepare($sql)->execute($args);
        $deliveryUpdated=0;$notifyUserIds=[];
        if($updateDeliveries){
            $ns=$pdo->prepare("SELECT DISTINCT user_id FROM deliveries WHERE credential_id=? AND status='delivered' AND user_id IS NOT NULL AND user_id<>0");$ns->execute([$id]);$notifyUserIds=array_values(array_unique(array_map('intval',$ns->fetchAll(PDO::FETCH_COLUMN))));
            $dsql="UPDATE deliveries SET config_text=?";$dargs=[$configText];
            if($qrPath!==null){$dsql.=', qr_code_path=?, qr_code_file_id=NULL';$dargs[]=$qrPath;}
            elseif($qrFileId!==''){$dsql.=', qr_code_file_id=?, qr_code_path=NULL';$dargs[]=$qrFileId;}
            $dsql.=' WHERE credential_id=?';$dargs[]=$id;
            $ds=$pdo->prepare($dsql);$ds->execute($dargs);$deliveryUpdated=$ds->rowCount();
        }
        try{$pdo->prepare("INSERT INTO admin_logs(admin_id,action,details,created_at) VALUES(?,'v2ray_config_edit',?,NOW())")->execute([$userId,json_encode(['credential_id'=>$id,'config_changed'=>$oldConfig!==$configText,'qr_updated'=>$qrPath!==null||$qrFileId!=='','delivery_rows'=>$deliveryUpdated],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);}catch(Throwable $ignored){}
        $pdo->commit();
        if ($updateDeliveries && ($qrPath !== null || $qrFileId !== '') && $oldQrPath !== '' && $oldQrPath !== (string)$qrPath) {
            admin_credentials_delete_local_qr($oldQrPath);
        }
        $photo='';
        if($qrFileId!=='') $photo=$qrFileId;
        elseif($qrPath===null && !empty($row['qr_code_file_id'])) $photo=(string)$row['qr_code_file_id'];
        else {
            $selectedQrPath=$qrPath??(string)($row['qr_code_path']??'');
            $localPhoto=admin_credentials_local_qr_absolute($selectedQrPath);
            if($localPhoto!==null) $photo=$localPhoto;
        }
        $notified=0;
        foreach($notifyUserIds as $uid){
            try{
                $sentAny=false;
                $chunks=admin_credentials_text_chunks("⚠️ کانفیگ V2Ray شما به‌روزرسانی شد.

کانفیگ جدید:
".$configText);
                foreach($chunks as $chunk){
                    $r=tg('sendMessage',['chat_id'=>$uid,'text'=>$chunk]);
                    if(is_array($r)&&!empty($r['ok'])) $sentAny=true;
                }
                if($sentAny)$notified++;
                if($photo!=='')tg('sendPhoto',['chat_id'=>$uid,'photo'=>$photo,'caption'=>'🔳 QR Code جدید کانفیگ V2Ray']);
            }catch(Throwable $ignored){}
        }
        gemaccAdminActionNotify('کانفیگ V2Ray ویرایش شد',['شناسه'=>$id,'تحویل‌های آپدیت‌شده'=>$deliveryUpdated,'کاربران مطلع‌شده'=>$notified,'QR جدید'=>($qrPath!==null||$qrFileId!=='')?'بله':'خیر'],$userId);
        echo json_encode(['ok'=>true,'message'=>'کانفیگ V2Ray اصلاح شد','delivery_rows'=>$deliveryUpdated,'notified_users'=>$notified],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);exit;
    }
    if ($action === 'edit' || $action === 'safe_edit' || $action === 'update_credential') {
        $id=(int)($input['credential_id'] ?? $input['id'] ?? 0);
        $pair=trim((string)($input['username_password'] ?? $input['pair'] ?? ''));
        $username=trim((string)($input['username'] ?? $input['email'] ?? ''));
        $password=trim((string)($input['password'] ?? ''));
        $updateDeliveries = !isset($input['update_deliveries']) || (int)$input['update_deliveries'] === 1;
        if ($pair && strpos($pair, ':') !== false) { [$username,$password]=array_map('trim', explode(':',$pair,2)); }
        if (!$id || $username === '' || $password === '') { echo json_encode(['ok'=>false,'error'=>'اطلاعات ناقص است'], JSON_UNESCAPED_UNICODE); exit; }
        if (!preg_match('/^[A-Za-z0-9_.@\-\+]{3,128}$/', $username)) { echo json_encode(['ok'=>false,'error'=>'یوزرنیم نامعتبر است. فقط حروف انگلیسی، عدد، نقطه، خط تیره، آندرلاین و @ مجاز است.'], JSON_UNESCAPED_UNICODE); exit; }
        if (mb_strlen($password, 'UTF-8') > 255) { echo json_encode(['ok'=>false,'error'=>'پسورد بیش از حد طولانی است'], JSON_UNESCAPED_UNICODE); exit; }
        $maxSeats = isset($input['max_seats']) || isset($input['capacity']) ? max(1, min(100, (int)($input['max_seats'] ?? $input['capacity']))) : null;
        $pdo->beginTransaction();
        $st=$pdo->prepare("SELECT id, email, username, password, used_seats, max_seats, sales_paused, is_sold, status FROM credentials WHERE id=? FOR UPDATE");
        $st->execute([$id]);
        $row=$st->fetch(PDO::FETCH_ASSOC);
        if(!$row){ $pdo->rollBack(); echo json_encode(['ok'=>false,'error'=>'اشتراک پیدا نشد'], JSON_UNESCAPED_UNICODE); exit; }
        $used=(int)($row['used_seats'] ?? 0);
        $oldMax=(int)($row['max_seats'] ?? 1);
        if ($maxSeats === null) $maxSeats = max($oldMax, $used, 1);
        if ($maxSeats < $used) {
            $pdo->rollBack();
            echo json_encode(['ok'=>false,'error'=>'ظرفیت کل نمی‌تواند کمتر از تعداد فروش/تحویل فعلی باشد. مقدار فعلی فروخته‌شده: '.$used], JSON_UNESCAPED_UNICODE); exit;
        }
        $du=$pdo->prepare("SELECT id FROM credentials WHERE email=? AND id<>? LIMIT 1");
        $du->execute([$username,$id]);
        if($du->fetch()){
            $pdo->rollBack();
            echo json_encode(['ok'=>false,'error'=>'این یوزرنیم برای اشتراک دیگری ثبت شده است'], JSON_UNESCAPED_UNICODE); exit;
        }
        $paused=(int)($row['sales_paused'] ?? 0);
        $newStatus = $paused === 1 ? 'paused' : ($used >= $maxSeats ? 'sold' : ($used > 0 ? 'partial' : 'available'));
        $newIsSold = $used >= $maxSeats ? 1 : 0;
        $st=$pdo->prepare("UPDATE credentials SET email=?, username=?, password=?, max_seats=?, is_sold=?, status=?, updated_at=NOW() WHERE id=?");
        $st->execute([$username,$username,$password,$maxSeats,$newIsSold,$newStatus,$id]);
        $oldUsername = (string)($row['email'] ?? $row['username'] ?? '');
        $oldPassword = (string)($row['password'] ?? '');
        $loginChanged = ($oldUsername !== $username) || ($oldPassword !== $password);
        $deliveryUpdated = 0;
        $notifyUserIds = [];
        if ($updateDeliveries) {
            try {
                if ($loginChanged) {
                    $ns=$pdo->prepare("SELECT DISTINCT user_id FROM deliveries WHERE credential_id=? AND status='delivered' AND user_id IS NOT NULL AND user_id<>0");
                    $ns->execute([$id]);
                    $notifyUserIds = array_values(array_unique(array_map('intval', $ns->fetchAll(PDO::FETCH_COLUMN))));
                }
                $d=$pdo->prepare("UPDATE deliveries SET username=?, password=? WHERE credential_id=?");
                $d->execute([$username,$password,$id]);
                $deliveryUpdated = $d->rowCount();
            } catch(Throwable $e) { $deliveryUpdated = 0; $notifyUserIds = []; }
        }
        try {
            $pdo->prepare("INSERT INTO admin_logs (admin_id, action, details, created_at) VALUES (?, 'credential_safe_edit', ?, NOW())")
                ->execute([$userId, json_encode([
                    'credential_id'=>$id,
                    'old_username'=>$row['email'] ?? $row['username'] ?? null,
                    'new_username'=>$username,
                    'old_max_seats'=>$oldMax,
                    'new_max_seats'=>$maxSeats,
                    'used_seats'=>$used,
                    'updated_deliveries'=>$updateDeliveries,
                    'delivery_rows'=>$deliveryUpdated,
                ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]);
        } catch(Throwable $e) {}
        $pdo->commit();
        $notified = 0; $notifyFailed = 0;
        if ($updateDeliveries && $loginChanged && $notifyUserIds) {
            $notice = "⚠️ اشتراک شما تغییر کرد.

به دلیل مسائل امنیتی اطلاعات ورود اشتراک شما به‌روزرسانی شد. لطفاً به هیچ عنوان تغییری در حساب ویندسکرایب انجام ندهید. در صورت مشاهده، حساب شما برای همیشه مسدود می‌شود و دیگر از سمت ما سرویسی دریافت نخواهید کرد.

برای مشاهده یوزرنیم و پسورد جدید، وارد بخش «فیلترشکن‌های من» در ربات/مینی‌اپ شوید و روی سفارش مربوط به همان تاریخ خرید بزنید.";
            foreach ($notifyUserIds as $uid) {
                try {
                    $tgRes = tg('sendMessage', ['chat_id'=>$uid, 'text'=>$notice]);
                    if (is_array($tgRes) && !empty($tgRes['ok'])) $notified++; else $notifyFailed++;
                } catch (Throwable $e) { $notifyFailed++; }
            }
        }
        $remaining=max($maxSeats-$used,0);
        $msg='اکانت اصلاح شد. ظرفیت کل: '.$maxSeats.'، فروخته/تحویل‌شده: '.$used.'، باقی‌مانده برای فروش: '.$remaining.'.';
        if ($updateDeliveries) $msg.=' اطلاعات '.$deliveryUpdated.' تحویل قبلی هم آپدیت شد.';
        if ($updateDeliveries && $loginChanged) $msg.=' نوتیفیکیشن برای '.$notified.' کاربر ارسال شد'.($notifyFailed?('؛ ناموفق: '.$notifyFailed):'').'.';
        gemaccAdminActionNotify('ویرایش امن اشتراک انجام شد', [
            'شناسه اشتراک'=>$id,
            'یوزرنیم قبلی'=>$oldUsername,
            'یوزرنیم جدید'=>$username,
            'ظرفیت قبلی'=>$oldMax,
            'ظرفیت جدید'=>$maxSeats,
            'فروخته/تحویل‌شده'=>$used,
            'باقی‌مانده'=>$remaining,
            'تحویل‌های آپدیت‌شده'=>$deliveryUpdated,
            'نوتیفیکیشن کاربران'=>$notified,
            'ناموفق'=>$notifyFailed,
        ], $userId);
        echo json_encode(['ok'=>true,'message'=>$msg,'used_seats'=>$used,'max_seats'=>$maxSeats,'remaining_seats'=>$remaining,'updated_deliveries'=>$deliveryUpdated,'notified_users'=>$notified,'notification_failed'=>$notifyFailed], JSON_UNESCAPED_UNICODE); exit;
    }


    if ($action === 'pause_sale' || $action === 'resume_sale') {
        $id=(int)($input['id'] ?? $input['credential_id'] ?? 0);
        $paused = $action === 'pause_sale' ? 1 : 0;
        $st=$pdo->prepare("UPDATE credentials SET sales_paused=?, status=IF(?=1,'paused',IF(COALESCE(used_seats,0)>=COALESCE(max_seats,1),'sold',IF(COALESCE(used_seats,0)>0,'partial','available'))), updated_at=NOW() WHERE id=?");
        $st->execute([$paused,$paused,$id]);
        if ($st->rowCount()>=0) gemaccAdminActionNotify($paused ? 'فروش اشتراک متوقف شد' : 'فروش اشتراک فعال شد', ['شناسه اشتراک'=>$id], $userId);
        echo json_encode($st->rowCount()>=0?['ok'=>true,'message'=>$paused?'فروش این اشتراک متوقف شد':'فروش این اشتراک دوباره فعال شد']:['ok'=>false,'error'=>'اشتراک پیدا نشد'], JSON_UNESCAPED_UNICODE); exit;
    }
    if ($action === 'archive_inventory' || $action === 'move_to_sold') {
        $id=(int)($input['id'] ?? $input['credential_id'] ?? 0);
        if (!$id) { echo json_encode(['ok'=>false,'error'=>'شناسه اشتراک نامعتبر است'], JSON_UNESCAPED_UNICODE); exit; }
        $pdo->beginTransaction();
        $st=$pdo->prepare("SELECT id, used_seats, max_seats, is_sold, status FROM credentials WHERE id=? FOR UPDATE");
        $st->execute([$id]);
        $row=$st->fetch(PDO::FETCH_ASSOC);
        if (!$row) { $pdo->rollBack(); echo json_encode(['ok'=>false,'error'=>'اشتراک پیدا نشد'], JSON_UNESCAPED_UNICODE); exit; }
        if ((int)($row['used_seats'] ?? 0) > 0 || (int)($row['is_sold'] ?? 0) === 1 || in_array(strtolower((string)($row['status'] ?? '')), ['sold','delivered','assigned','used'], true)) {
            $pdo->rollBack();
            echo json_encode(['ok'=>false,'error'=>'اشتراک دارای فروش/تحویل است؛ برای مدیریت آن از بخش فروخته‌شده‌ها استفاده کنید.'], JSON_UNESCAPED_UNICODE); exit;
        }
        $st=$pdo->prepare("UPDATE credentials SET sales_paused=1,is_active=0,is_sold=0,status='archived',archived_at=NOW(),archived_by=?,archive_reason='admin_inventory_archive',updated_at=NOW() WHERE id=?");
        $st->execute([$userId,$id]);
        $pdo->commit();
        gemaccAdminActionNotify('اشتراک از موجودی آرشیو شد', ['شناسه اشتراک'=>$id], $userId);
        echo json_encode(['ok'=>true,'message'=>'اشتراک از موجودی خارج و آرشیو شد'], JSON_UNESCAPED_UNICODE); exit;
    }
    if ($action === 'detach_sold') {
        $id=(int)($input['credential_id'] ?? 0);
        if (!$id) { echo json_encode(['ok'=>false,'error'=>'شناسه اشتراک نامعتبر است'], JSON_UNESCAPED_UNICODE); exit; }
        $pdo->beginTransaction();
        $st=$pdo->prepare("SELECT * FROM credentials WHERE id=? FOR UPDATE"); $st->execute([$id]); $cred=$st->fetch(PDO::FETCH_ASSOC);
        if (!$cred) { $pdo->rollBack(); echo json_encode(['ok'=>false,'error'=>'اشتراک پیدا نشد'], JSON_UNESCAPED_UNICODE); exit; }
        $pdo->prepare("DELETE FROM deliveries WHERE credential_id=?")->execute([$id]);
        if (!empty($cred['order_id'])) {
            $cnt=$pdo->prepare("SELECT COUNT(*) FROM deliveries WHERE order_id=?"); $cnt->execute([(int)$cred['order_id']]); $left=(int)$cnt->fetchColumn();
            $pdo->prepare("UPDATE orders SET delivered_accounts=?, delivery_status=IF(?=0,'queued','partial'), status=IF(?=0,'QUEUED','DELIVERED') WHERE id=?")->execute([$left,$left,$left,(int)$cred['order_id']]);
        }
        $pdo->prepare("UPDATE credentials SET used_seats=0,is_sold=0,status='available',sold_to_user_id=NULL,order_id=NULL,assigned_user_id=NULL,assigned_order_id=NULL,sold_at=NULL,delivered_at=NULL,first_connected_at=NULL,expires_at=NULL,updated_at=NOW() WHERE id=?")->execute([$id]);
        $pdo->commit();
        gemaccAdminActionNotify('اشتراک فروخته‌شده از تحویل‌ها جدا شد', ['شناسه اشتراک'=>$id], $userId);
        echo json_encode(['ok'=>true,'message'=>'اشتراک از حساب کاربر حذف و به فروخته‌نشده‌ها منتقل شد'], JSON_UNESCAPED_UNICODE); exit;
    }
    if ($action === 'extend_months') {
        $id=(int)($input['credential_id'] ?? 0); $months=max(1,(int)($input['months'] ?? 1));
        if (!$id) { echo json_encode(['ok'=>false,'error'=>'شناسه اشتراک نامعتبر است'], JSON_UNESCAPED_UNICODE); exit; }
        $st=$pdo->prepare("UPDATE credentials SET months=COALESCE(months,1)+?, expires_at=CASE WHEN expires_at IS NULL THEN NULL ELSE DATE_ADD(expires_at, INTERVAL ? MONTH) END, updated_at=NOW() WHERE id=?");
        $st->execute([$months,$months,$id]);
        gemaccAdminActionNotify('مدت اشتراک افزایش یافت', ['شناسه اشتراک'=>$id, 'ماه اضافه‌شده'=>$months], $userId);
        echo json_encode(['ok'=>true,'message'=>'مدت اشتراک افزایش یافت'], JSON_UNESCAPED_UNICODE); exit;
    }

    if ($action === 'toggle') { echo json_encode(['ok'=>false,'error'=>'دکمه خاموش/روشن برای اشتراک‌ها حذف شده است'], JSON_UNESCAPED_UNICODE); exit; }
    if ($action === 'delete') { $id=(int)($input['credential_id']??0); $st=$pdo->prepare("DELETE FROM credentials WHERE id=? AND used_seats=0"); $st->execute([$id]); if($st->rowCount()) gemaccAdminActionNotify('اشتراک فروخته‌نشده حذف شد', ['شناسه اشتراک'=>$id], $userId); echo json_encode($st->rowCount()?['ok'=>true,'message'=>'حذف شد']:['ok'=>false,'error'=>'اکانت فروخته‌شده قابل حذف نیست'], JSON_UNESCAPED_UNICODE); exit; }
    echo json_encode(['ok'=>false,'error'=>'Invalid action'], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) { http_response_code(200); error_log('[admin_credentials] '.$e->getMessage()); echo json_encode(['ok'=>false,'error'=>'خطای سرور: '.$e->getMessage()], JSON_UNESCAPED_UNICODE); }
