<?php
// ─── Admin API ────────────────────────────────────────────────────────────

function adminServices() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT * FROM h_order WHERE status!='4' ORDER BY id DESC LIMIT 100");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

function adminOrders() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT o.*, u.name AS owner_name, u.email AS owner_email
        FROM h_order o LEFT JOIN h_user u ON u.id=o.uid WHERE o.status!='4' ORDER BY o.id DESC LIMIT 100");
    $items = [];
    while ($row = mysqli_fetch_assoc($rss)) {
        $pid = (int)substr($row['package'] ?? '', 1);
        $prod = mysqli_fetch_assoc(mysqli_query($conn, "SELECT pkname FROM h_product WHERE id='$pid'"));
        $dom = explode(',', decode($row['domain'] ?? '', KEY_ONE));
        $items[] = [
            'id'          => $row['id'],
            'id_enc'      => encode($row['id'], KEY_ONE),
            'owner_name'  => $row['owner_name'],
            'owner_email' => $row['owner_email'],
            'package'     => $prod['pkname'] ?? $row['package'],
            'domain'      => $dom[0] ?? '',
            'status'      => (int)$row['status'],
            'date'        => $row['date']
        ];
    }
    success_response(['items' => $items]);
}

function adminOrderStatus() { require_admin(); global $conn;
    $oid = (int)decode($_GET['oid'] ?? '', KEY_ONE);
    $status = (int)($_GET['status'] ?? 0);
    $cycleMap = [1=>2, 2=>3, 3=>4, 4=>0];
    $status = $cycleMap[$status] ?? 1;
    mysqli_query($conn, "UPDATE h_order SET status='$status' WHERE id='$oid'");
    success_response(['status' => $status], 'Order status updated.');
}

function adminInvoices() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT i.*, u.email AS owner_email
        FROM h_invoice i LEFT JOIN h_user u ON u.id=i.uid ORDER BY i.id DESC LIMIT 100");
    $items = [];
    while ($row = mysqli_fetch_assoc($rss)) {
        $items[] = [
            'id'          => $row['id'],
            'iid_enc'     => encode($row['id'], KEY_ONE),
            'oid'         => $row['oid'],
            'oid_enc'     => encode($row['oid'], KEY_ONE),
            'owner_email' => $row['owner_email'],
            'price'       => $row['price'],
            'date'        => $row['date'],
            'exdate'      => $row['exdate'],
            'status'      => (int)$row['status']
        ];
    }
    success_response(['items' => $items]);
}

function adminInvoiceAction() { require_admin(); global $conn;
    $iid = (int)decode($_GET['iid'] ?? '', KEY_ONE);
    $oid = (int)decode($_GET['oid'] ?? '', KEY_ONE);
    $status = (int)($_GET['status'] ?? -1);
    if ($status < 0 || $status > 4) error_response('Invalid status.');

    mysqli_query($conn, "UPDATE h_invoice SET status='$status' WHERE id='$iid'");
    // Mirror the invoice state onto its order (Paid -> Active, everything else 1:1)
    $orderStatus = ($status === 1) ? 1 : $status;
    mysqli_query($conn, "UPDATE h_order SET status='$orderStatus' WHERE id='$oid'");
    success_response([], 'Invoice updated.');
}

function adminRevenue() { require_admin(); global $conn;
    $ord = $_GET['ord'] ?? '';
    $report = '';
    if ($ord==='today') $report="AND date>CURDATE()";
    elseif ($ord==='yest') $report="AND date>=DATE(NOW())-INTERVAL 1 DAY AND date<DATE(NOW())";
    elseif ($ord==='zsweek') { $wn=date('N')-1; $report="AND date>=DATE(NOW())-INTERVAL $wn DAY"; }
    elseif ($ord==='mnz') { $mn=date('d')-1; $report="AND date>=DATE(NOW())-INTERVAL $mn DAY"; }
    elseif ($ord==='3'||$ord==='6') $report="AND date>=DATE(NOW())-INTERVAL $ord MONTH";
    elseif ($ord==='yr') $report="AND date>=DATE(NOW())-INTERVAL 1 YEAR";
    
    $rf = mysqli_query($conn, "SELECT SUM(price) s FROM h_invoice WHERE status='1' $report");
    $sum = (float)(mysqli_fetch_assoc($rf)['s'] ?? 0);
    success_response(['total' => $sum, 'fee' => $sum*0.18, 'net' => $sum*0.82]);
}

function adminUsers() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT id, name, email, phone, status, date FROM h_user ORDER BY id DESC LIMIT 100");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

function adminProducts() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT * FROM h_product ORDER BY id ASC");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

function adminProductToggle() { require_admin(); global $conn;
    $pid = (int)($_GET['pid'] ?? 0);
    $row = mysqli_fetch_assoc(mysqli_query($conn, "SELECT status FROM h_product WHERE id='$pid'"));
    if (!$row) error_response('Product not found', 404);
    $newStatus = ($row['status'] === '0') ? '1' : '0';
    mysqli_query($conn, "UPDATE h_product SET status='$newStatus' WHERE id='$pid'");
    success_response(['status' => $newStatus], 'Product status updated.');
}

function adminServers() { require_admin(); global $conn;
    // Never send WHM tokens to the browser, even to an authenticated admin.
    $rss = mysqli_query($conn, "SELECT id, status, category_ids, server_url, whm_user, nameserver,
        (token IS NOT NULL AND token != '') AS has_token FROM h_server ORDER BY id");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

// ─── Free-domain TLD allow-list ─────────────────────────────────────────────
// This is just a curated list of which TLDs appear in the "free domain"
// picker at checkout — actual pricing/availability always comes live from
// core/registrars.php@shop_domain(), compared against the threshold setting.
function adminTlds() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT * FROM h_tld ORDER BY sort_order ASC, tld ASC");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    require_once __DIR__ . '/../config/domains.php';
    success_response(['items' => $items, 'free_domain_threshold' => free_domain_threshold()]);
}

function adminTldSave() { require_admin(); global $conn;
    $id     = (int)($_POST['id'] ?? 0);
    $status = !empty($_POST['status']) ? 1 : 0;
    $raw    = trim($_POST['tld'] ?? '');

    if ($raw === '') error_response('Please enter at least one extension.');

    if ($id) {
        // Editing a single existing row — only one TLD allowed here.
        $tld = strtolower(ltrim($raw, '.'));
        if (!preg_match('/^[a-z0-9-]+$/', $tld)) error_response('Please enter a valid TLD (e.g. com, net, xyz — no leading dot).');

        $tldSafe = mysqli_real_escape_string($conn, $tld);
        $existing = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id FROM h_tld WHERE tld='$tldSafe' AND id!='$id'"));
        if ($existing) error_response('That TLD already exists — edit it instead.');
        mysqli_query($conn, "UPDATE h_tld SET tld='$tldSafe', status='$status' WHERE id='$id'");
        success_response([], 'TLD saved.');
        return;
    }

    // Adding — accept a comma/space/newline separated list so several extensions
    // can be added in one go (e.g. "shop, top, buzz, xyz, site, io"). New ones are
    // appended after whatever the admin has already ordered.
    $nextOrder = (int) (mysqli_fetch_assoc(mysqli_query($conn, "SELECT COALESCE(MAX(sort_order),0) m FROM h_tld"))['m'] ?? 0);

    $parts = preg_split('/[\s,]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
    $added = 0;
    $skipped = [];
    foreach ($parts as $part) {
        $tld = strtolower(ltrim(trim($part), '.'));
        if ($tld === '' || !preg_match('/^[a-z0-9-]+$/', $tld)) { $skipped[] = $part; continue; }

        $tldSafe = mysqli_real_escape_string($conn, $tld);
        $existing = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id FROM h_tld WHERE tld='$tldSafe'"));
        if ($existing) { $skipped[] = $tld; continue; }

        $nextOrder++;
        mysqli_query($conn, "INSERT INTO h_tld (tld,status,sort_order) VALUES ('$tldSafe','$status','$nextOrder')");
        $added++;
    }

    if ($added === 0) {
        error_response('No new TLDs were added' . ($skipped ? ' — already existed or invalid: ' . implode(', ', $skipped) : '') . '.');
    }

    $msg = "Added $added TLD" . ($added === 1 ? '' : 's') . '.';
    if ($skipped) $msg .= ' Skipped (already existed or invalid): ' . implode(', ', $skipped) . '.';
    success_response([], $msg);
}

function adminTldDelete() { require_admin(); global $conn;
    $id = (int)($_GET['id'] ?? 0);
    mysqli_query($conn, "DELETE FROM h_tld WHERE id='$id'");
    success_response([], 'TLD deleted.');
}

function adminTldMove() { require_admin(); global $conn;
    $id  = (int)($_GET['id'] ?? 0);
    $dir = ($_GET['dir'] ?? '') === 'down' ? 'down' : 'up';

    $current = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id, sort_order FROM h_tld WHERE id='$id'"));
    if (!$current) error_response('TLD not found', 404);

    $op = $dir === 'up' ? '<' : '>';
    $order = $dir === 'up' ? 'DESC' : 'ASC';
    $neighbor = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id, sort_order FROM h_tld WHERE sort_order $op '{$current['sort_order']}' ORDER BY sort_order $order LIMIT 1"));
    if (!$neighbor) success_response([], 'Already at the ' . ($dir === 'up' ? 'top' : 'bottom') . '.');

    // Swap sort_order values with the neighbor
    mysqli_query($conn, "UPDATE h_tld SET sort_order='{$neighbor['sort_order']}' WHERE id='{$current['id']}'");
    mysqli_query($conn, "UPDATE h_tld SET sort_order='{$current['sort_order']}' WHERE id='{$neighbor['id']}'");

    success_response([], 'Reordered.');
}

function adminSettingsGet() { require_admin();
    require_once __DIR__ . '/../config/domains.php';
    success_response(['free_domain_threshold' => free_domain_threshold()]);
}

function adminSettingsSave() { require_admin();
    require_once __DIR__ . '/../config/domains.php';
    if (isset($_POST['free_domain_threshold']) && $_POST['free_domain_threshold'] !== '') {
        $threshold = (float)$_POST['free_domain_threshold'];
        if ($threshold < 0) error_response('Threshold cannot be negative.');
        save_setting('free_domain_threshold', (string)$threshold);
    }
    success_response([], 'Settings saved.');
}

// ─── Domain registrars (pluggable — see core/registrars.php) ────────────────
function adminRegistrars() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT id, name, driver, api_endpoint,
        (api_key IS NOT NULL AND api_key != '') AS has_key,
        priority, status, config FROM h_registrar ORDER BY priority ASC, id ASC");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

function adminRegistrarSave() { require_admin(); global $conn, $now;
    $id       = (int)($_POST['id'] ?? 0);
    $name     = trim($_POST['name'] ?? '');
    $driver   = in_array($_POST['driver'] ?? '', ['namesilo', 'dynadot', 'generic']) ? $_POST['driver'] : 'generic';
    $endpoint = trim($_POST['api_endpoint'] ?? '');
    $apiKey   = trim($_POST['api_key'] ?? '');
    $apiSecret = trim($_POST['api_secret'] ?? '');
    $config   = trim($_POST['config'] ?? '');
    $priority = (int)($_POST['priority'] ?? 100);
    $status   = !empty($_POST['status']) ? 1 : 0;

    if ($name === '') error_response('Please enter a registrar name.');
    if ($config !== '' && json_decode($config) === null && strtolower($config) !== 'null') {
        error_response('Config must be valid JSON (or left blank).');
    }

    $nameSafe = mysqli_real_escape_string($conn, $name);
    $endpointSafe = mysqli_real_escape_string($conn, $endpoint);
    $configSafe = mysqli_real_escape_string($conn, $config);

    if ($id) {
        $sql = "UPDATE h_registrar SET name='$nameSafe', driver='$driver', api_endpoint='$endpointSafe',
            config='$configSafe', priority='$priority', status='$status'";
        // Credentials only overwritten when a new value is provided, so the form never
        // needs to display (or re-submit) the existing secret to keep it unchanged.
        if ($apiKey !== '') $sql .= ", api_key='" . mysqli_real_escape_string($conn, $apiKey) . "'";
        if ($apiSecret !== '') $sql .= ", api_secret='" . mysqli_real_escape_string($conn, $apiSecret) . "'";
        $sql .= " WHERE id='$id'";
        mysqli_query($conn, $sql);
    } else {
        $apiKeySafe = mysqli_real_escape_string($conn, $apiKey);
        $apiSecretSafe = mysqli_real_escape_string($conn, $apiSecret);
        mysqli_query($conn, "INSERT INTO h_registrar (name,driver,api_endpoint,api_key,api_secret,config,priority,status,date)
            VALUES ('$nameSafe','$driver','$endpointSafe','$apiKeySafe','$apiSecretSafe','$configSafe','$priority','$status','$now')");
    }
    success_response([], 'Registrar saved.');
}

function adminRegistrarDelete() { require_admin(); global $conn;
    $id = (int)($_GET['id'] ?? 0);
    mysqli_query($conn, "DELETE FROM h_registrar WHERE id='$id'");
    mysqli_query($conn, "DELETE FROM h_registrar_tld WHERE registrar_id='$id'");
    success_response([], 'Registrar deleted.');
}

function adminRegistrarTlds() { require_admin(); global $conn;
    $registrarId = (int)($_GET['registrar_id'] ?? 0);
    $rss = mysqli_query($conn, "SELECT * FROM h_registrar_tld WHERE registrar_id='$registrarId' ORDER BY tld ASC");
    require_once __DIR__ . '/../core/registrars.php';
    $items = [];
    while ($row = mysqli_fetch_assoc($rss)) {
        // Estimated final price if this registrar's own API doesn't return a live price —
        // the real charged price may differ when it does.
        $row['final_price_est'] = apply_markup($row['base_price'], $row['markup_type'], $row['markup']);
        $items[] = $row;
    }
    success_response(['items' => $items]);
}

function adminRegistrarTldSave() { require_admin(); global $conn;
    $id          = (int)($_POST['id'] ?? 0);
    $registrarId = (int)($_POST['registrar_id'] ?? 0);
    $tld         = strtolower(ltrim(trim($_POST['tld'] ?? ''), '.'));
    $basePrice   = (float)($_POST['base_price'] ?? 0);
    $markupType  = ($_POST['markup_type'] ?? 'fixed') === 'percent' ? 'percent' : 'fixed';
    $markup      = (float)($_POST['markup'] ?? 0); // can be negative — a discount off the base/live price
    $status      = !empty($_POST['status']) ? 1 : 0;

    if (!$registrarId) error_response('Missing registrar.');
    if ($tld === '' || !preg_match('/^[a-z0-9-]+$/', $tld)) error_response('Please enter a valid TLD (e.g. com, net, xyz — no leading dot).');
    if ($basePrice < 0) error_response('Base price cannot be negative.');
    if ($markupType === 'percent' && $markup < -100) error_response('A percentage discount cannot exceed -100%.');

    $tldSafe = mysqli_real_escape_string($conn, $tld);
    if ($id) {
        mysqli_query($conn, "UPDATE h_registrar_tld SET tld='$tldSafe', base_price='$basePrice', markup_type='$markupType', markup='$markup', status='$status' WHERE id='$id'");
    } else {
        $existing = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id FROM h_registrar_tld WHERE registrar_id='$registrarId' AND tld='$tldSafe'"));
        if ($existing) error_response('That TLD is already priced for this registrar — edit it instead.');
        mysqli_query($conn, "INSERT INTO h_registrar_tld (registrar_id,tld,base_price,markup_type,markup,status) VALUES ('$registrarId','$tldSafe','$basePrice','$markupType','$markup','$status')");
    }
    success_response([], 'TLD pricing saved.');
}

function adminRegistrarTldDelete() { require_admin(); global $conn;
    $id = (int)($_GET['id'] ?? 0);
    mysqli_query($conn, "DELETE FROM h_registrar_tld WHERE id='$id'");
    success_response([], 'TLD pricing deleted.');
}

// ─── Coupons ─────────────────────────────────────────────────────────────────
function adminCoupons() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT c.*, (SELECT COUNT(*) FROM h_coupon_usage u WHERE u.coupon_id=c.id) AS used_count
        FROM h_coupon c ORDER BY c.id DESC");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

function adminCouponSave() { require_admin(); global $conn, $now;
    $id             = (int)($_POST['id'] ?? 0);
    $code           = strtoupper(trim($_POST['code'] ?? ''));
    $type           = ($_POST['type'] ?? 'percent') === 'fixed' ? 'fixed' : 'percent';
    $value          = (float)($_POST['value'] ?? 0);
    $startDate      = trim($_POST['start_date'] ?? '') ?: null;
    $endDate        = trim($_POST['end_date'] ?? '') ?: null;
    $maxUses        = ($_POST['max_uses'] ?? '') !== '' ? (int)$_POST['max_uses'] : null;
    $perUserLimit   = ($_POST['per_user_limit'] ?? '') !== '' ? (int)$_POST['per_user_limit'] : null;
    $minOrderAmount = (float)($_POST['min_order_amount'] ?? 0);
    $products       = trim($_POST['applicable_products'] ?? ''); // comma-separated product ids, '' = all
    $status         = !empty($_POST['status']) ? 1 : 0;

    if ($code === '' || !preg_match('/^[A-Z0-9_-]+$/', $code)) error_response('Please enter a valid coupon code (letters, numbers, - and _ only).');
    if ($value <= 0) error_response('Discount value must be greater than 0.');
    if ($type === 'percent' && $value > 100) error_response('Percentage discount cannot exceed 100.');
    if ($minOrderAmount < 0) error_response('Minimum order amount cannot be negative.');

    $codeSafe = mysqli_real_escape_string($conn, $code);
    $existing = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id FROM h_coupon WHERE code='$codeSafe'" . ($id ? " AND id!='$id'" : '')));
    if ($existing) error_response('That coupon code is already in use.');

    $productsSafe = mysqli_real_escape_string($conn, $products);
    $startSql = $startDate ? "'" . mysqli_real_escape_string($conn, $startDate) . "'" : 'NULL';
    $endSql = $endDate ? "'" . mysqli_real_escape_string($conn, $endDate) . "'" : 'NULL';
    $maxUsesSql = $maxUses !== null ? (int)$maxUses : 'NULL';
    $perUserSql = $perUserLimit !== null ? (int)$perUserLimit : 'NULL';

    if ($id) {
        mysqli_query($conn, "UPDATE h_coupon SET code='$codeSafe', type='$type', value='$value',
            start_date=$startSql, end_date=$endSql, max_uses=$maxUsesSql, per_user_limit=$perUserSql,
            min_order_amount='$minOrderAmount', applicable_products='$productsSafe', status='$status' WHERE id='$id'");
    } else {
        mysqli_query($conn, "INSERT INTO h_coupon (code,type,value,start_date,end_date,max_uses,per_user_limit,min_order_amount,applicable_products,status,date)
            VALUES ('$codeSafe','$type','$value',$startSql,$endSql,$maxUsesSql,$perUserSql,'$minOrderAmount','$productsSafe','$status','$now')");
    }
    success_response([], 'Coupon saved.');
}

function adminCouponDelete() { require_admin(); global $conn;
    $id = (int)($_GET['id'] ?? 0);
    mysqli_query($conn, "DELETE FROM h_coupon WHERE id='$id'");
    mysqli_query($conn, "DELETE FROM h_coupon_usage WHERE coupon_id='$id'");
    success_response([], 'Coupon deleted.');
}

function adminNews() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT id, title, body, date, pinned FROM h_news ORDER BY pinned DESC, id DESC LIMIT 100");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

function adminNewsCreate() { require_admin(); global $conn, $now;
    $title = mysqli_real_escape_string($conn, trim($_POST['title'] ?? ''));
    $body  = mysqli_real_escape_string($conn, trim($_POST['message'] ?? ''));
    $pinned = !empty($_POST['pinned']) ? 1 : 0;
    if ($title === '' || $body === '') error_response('Title and message are required.');

    mysqli_query($conn, "INSERT INTO h_news (title, body, date, pinned) VALUES ('$title','$body','$now','$pinned')");
    success_response(['id' => mysqli_insert_id($conn)], 'News added.');
}

function adminNewsDelete() { require_admin(); global $conn;
    $id = (int)($_GET['id'] ?? 0);
    mysqli_query($conn, "DELETE FROM h_news WHERE id='$id'");
    success_response([], 'News deleted.');
}

function adminTickets() { require_admin(); global $conn;
    $rss = mysqli_query($conn, "SELECT DISTINCT h_support.rid,h_support.subject,h_support.date,h_support.status FROM h_support JOIN (SELECT rid,MAX(id) mid FROM h_support GROUP BY rid) l ON h_support.rid=l.rid AND h_support.id=l.mid ORDER BY h_support.date DESC LIMIT 100");
    $items = []; while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    success_response(['items' => $items]);
}

function adminBulkClose() { require_admin(); global $conn;
    $result = mysqli_query($conn, "SELECT latest.rid FROM (SELECT rid, MAX(id) as max_id FROM h_support GROUP BY rid) as latest JOIN h_support ON latest.rid = h_support.rid AND latest.max_id = h_support.id WHERE h_support.status = 1 AND h_support.date < DATE_SUB(NOW(), INTERVAL 7 DAY)");
    $closed = 0;
    while ($t = mysqli_fetch_assoc($result)) {
        $rid = (int)$t['rid'];
        $subj = mysqli_fetch_assoc(mysqli_query($conn, "SELECT subject FROM h_support WHERE rid=$rid ORDER BY id ASC LIMIT 1"));
        $subject = mysqli_real_escape_string($conn, $subj['subject']);
        mysqli_query($conn, "INSERT INTO h_support (rid,uid,subject,message,date,status) VALUES ('$rid','1','$subject','Ticket auto-closed due to inactivity (no reply for >7 days)',NOW(),'2')");
        $closed++;
    }
    success_response(['closed' => $closed], "$closed old tickets closed.");
}

function adminBulkReminders() { require_admin(); global $conn;
    require_once __DIR__ . '/../config/mail.php';
    $rss = mysqli_query($conn, "SELECT i.id, i.price, i.exdate, u.email, u.name FROM h_invoice i
        JOIN h_user u ON u.id=i.uid WHERE i.status='0'");
    $sent = 0;
    while ($row = mysqli_fetch_assoc($rss)) {
        $body = "Dear {$row['name']}<br><br>"
              . "This is a reminder that invoice #{$row['id']} for \${$row['price']} is due on {$row['exdate']}.<br><br>"
              . "Please log in to your account to pay.<br><br>Host25Cent.com";
        try {
            $mail = make_mailer('info@host25cent.com', 'Host25Cent', 'info@host25cent.com');
            $mail->AddAddress($row['email']);
            $mail->Subject = "Payment Reminder - Invoice #{$row['id']}";
            $mail->MsgHTML($body);
            $mail->IsHTML(true);
            $mail->Send();
            $sent++;
        } catch (Exception $e) { /* skip individual send failures, keep processing the rest */ }
    }
    success_response(['sent' => $sent], "$sent payment reminders sent.");
}

function adminBulkInvoices() { require_admin(); global $conn, $now;
    // Renew any order whose current invoice is already paid and expires within 10 days,
    // as long as it doesn't already have an unpaid invoice pending.
    $rss = mysqli_query($conn, "SELECT o.id AS oid, o.uid, o.package, i.exdate FROM h_order o
        JOIN h_invoice i ON i.id=o.iid
        WHERE o.status='1' AND i.status='1' AND i.exdate <= DATE_ADD(CURDATE(), INTERVAL 10 DAY)");
    $created = 0;
    while ($row = mysqli_fetch_assoc($rss)) {
        $existing = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id FROM h_invoice WHERE oid='{$row['oid']}' AND status='0'"));
        if ($existing) continue;

        $pid = (int)substr($row['package'], 1);
        $bt  = substr($row['package'], 0, 1);
        $prod = mysqli_fetch_assoc(mysqli_query($conn, "SELECT fprice, yprice, fmonth FROM h_product WHERE id='$pid'"));
        if (!$prod) continue;

        $price  = ($bt === 'm') ? $prod['fprice'] : $prod['yprice'];
        $months = ($bt === 'm') ? $prod['fmonth'] : 12;
        $newExDate = date('Y-m-d', strtotime("{$row['exdate']} +$months months"));

        mysqli_query($conn, "INSERT INTO h_invoice (uid,oid,item,price,date,exdate,status)
            VALUES ('{$row['uid']}','{$row['oid']}','{$row['package']}','$price','$now','$newExDate','0')");
        $created++;
    }
    success_response(['created' => $created], "$created renewal invoices generated.");
}