<?php
/**
 * cPanel UAPI PROVISIONING TEST — SAFE / TEMPORARY
 * Superadmin Multi-Tenant
 *
 * PURPOSE
 *   Prove that the cPanel account can:
 *   1) create a temporary MySQL/MariaDB database
 *   2) create a temporary database user
 *   3) grant privileges
 *   4) connect using the new credentials
 *   5) clean everything up
 *
 * IMPORTANT
 *   - This does NOT touch the production Superadmin DB.
 *   - This does NOT touch existing tenant DBs.
 *   - Test objects use a random suffix.
 *   - API token is accepted via POST and never stored.
 *   - SSL verification remains ON.
 *   - CURLOPT_RESOLVE forces cPanel hostname to the known server IP.
 *
 * TEMPORARY TEST TOOL.
 * DELETE THIS FILE FROM public_html AFTER TESTING.
 */

declare(strict_types=1);

header_remove('X-Powered-By');

$cpanelHost = trim((string)($_POST['cpanel_host'] ?? 'cpanel.karangpilang.my.id'));
$cpanelIp   = trim((string)($_POST['cpanel_ip'] ?? '194.233.65.45'));
$cpanelUser = trim((string)($_POST['cpanel_user'] ?? 'karangpilang'));
$apiToken   = trim((string)($_POST['api_token'] ?? ''));

$result = null;
$error = null;

function h(string $v): string {
    return htmlspecialchars($v, ENT_QUOTES, 'UTF-8');
}

function cleanHost(string $host): string {
    $host = preg_replace('#^https?://#i', '', trim($host));
    $host = preg_replace('#/.*$#', '', $host);
    $host = preg_replace('#:\d+$#', '', $host);
    return trim($host);
}

function uapi(
    string $host,
    string $ip,
    string $user,
    string $token,
    string $module,
    string $function,
    array $params = []
): array {
    $host = cleanHost($host);

    $query = $params ? ('?' . http_build_query($params)) : '';

    $url = 'https://' . $host . ':2083/execute/'
         . rawurlencode($module) . '/'
         . rawurlencode($function) . $query;

    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_RESOLVE => [$host . ':2083:' . $ip],
        CURLOPT_HTTPHEADER => [
            'Authorization: cpanel ' . $user . ':' . $token,
            'Accept: application/json',
            'User-Agent: Superadmin-UAPI-Provision-Test/1.0'
        ],
    ]);

    $started = microtime(true);
    $body = curl_exec($ch);
    $duration = (int)round((microtime(true) - $started) * 1000);

    $curlError = curl_error($ch);
    $curlNo = curl_errno($ch);
    $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $primaryIp = (string)curl_getinfo($ch, CURLINFO_PRIMARY_IP);

    curl_close($ch);

    if ($body === false) {
        throw new RuntimeException(
            'cURL #' . $curlNo . ': ' . ($curlError ?: 'unknown error')
        );
    }

    $json = json_decode($body, true);

    if (!is_array($json)) {
        throw new RuntimeException(
            'Response UAPI bukan JSON. HTTP ' . $httpCode .
            '. Response: ' . substr($body, 0, 1000)
        );
    }

    $status = (int)($json['status'] ?? 0);

    return [
        'ok' => ($status === 1),
        'status' => $status,
        'http_code' => $httpCode,
        'primary_ip' => $primaryIp,
        'duration_ms' => $duration,
        'url' => $url,
        'json' => $json,
    ];
}

function dbConnect(string $host, string $database, string $user, string $password): array {
    $start = microtime(true);

    try {
        $pdo = new PDO(
            'mysql:host=' . $host . ';dbname=' . $database . ';charset=utf8mb4',
            $user,
            $password,
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_TIMEOUT => 10,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]
        );

        $version = (string)$pdo->query('SELECT VERSION()')->fetchColumn();

        return [
            'ok' => true,
            'duration_ms' => (int)round((microtime(true) - $start) * 1000),
            'version' => $version,
        ];
    } catch (Throwable $e) {
        return [
            'ok' => false,
            'duration_ms' => (int)round((microtime(true) - $start) * 1000),
            'error' => $e->getMessage(),
        ];
    }
}

function randomSuffix(): string {
    return date('His') . '_' . bin2hex(random_bytes(3));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    if ($cpanelHost === '' || $cpanelIp === '' || $cpanelUser === '' || $apiToken === '') {
        $error = 'Host, IP, username cPanel, dan API token wajib diisi.';
    } elseif (filter_var($cpanelIp, FILTER_VALIDATE_IP) === false) {
        $error = 'IP server tidak valid.';
    } elseif (strlen($apiToken) < 10) {
        $error = 'API token terlihat terlalu pendek.';
    } else {

        $suffix = randomSuffix();

        /*
         * cPanel prefixes database/user names with the cPanel account name.
         * We intentionally keep the object name short.
         *
         * Example:
         *   karangpilang_mgtest_083012_a1b2c3
         */
        $shortName = 'mgtest_' . $suffix;

        $testDb = $cpanelUser . '_' . $shortName;
        $testDbUser = $cpanelUser . '_' . $shortName;

        // Random temporary password. Never displayed.
        $testPassword = 'T!' . bin2hex(random_bytes(18)) . 'x9';

        $steps = [];
        $cleanupNeeded = false;

        try {

            // ---------------------------------------------------------
            // STEP 1 — CREATE DATABASE
            // ---------------------------------------------------------
            $r = uapi(
                $cpanelHost,
                $cpanelIp,
                $cpanelUser,
                $apiToken,
                'Mysql',
                'create_database',
                ['name' => $testDb]
            );

            $steps[] = [
                'name' => '1. CREATE DATABASE',
                'ok' => $r['ok'],
                'detail' => $r,
            ];

            if (!$r['ok']) {
                throw new RuntimeException(
                    'CREATE DATABASE gagal. Periksa response UAPI pada langkah 1.'
                );
            }

            $cleanupNeeded = true;

            // ---------------------------------------------------------
            // STEP 2 — CREATE USER
            // ---------------------------------------------------------
            $r = uapi(
                $cpanelHost,
                $cpanelIp,
                $cpanelUser,
                $apiToken,
                'Mysql',
                'create_user',
                [
                    'name' => $testDbUser,
                    'password' => $testPassword,
                ]
            );

            $steps[] = [
                'name' => '2. CREATE DATABASE USER',
                'ok' => $r['ok'],
                'detail' => $r,
            ];

            if (!$r['ok']) {
                throw new RuntimeException(
                    'CREATE USER gagal. Database test akan dicoba dihapus pada cleanup.'
                );
            }

            // ---------------------------------------------------------
            // STEP 3 — GRANT PRIVILEGES
            // ---------------------------------------------------------
            $r = uapi(
                $cpanelHost,
                $cpanelIp,
                $cpanelUser,
                $apiToken,
                'Mysql',
                'set_privileges_on_database',
                [
                    'user' => $testDbUser,
                    'database' => $testDb,
                    'privileges' => 'ALL PRIVILEGES',
                ]
            );

            $steps[] = [
                'name' => '3. GRANT ALL PRIVILEGES',
                'ok' => $r['ok'],
                'detail' => $r,
            ];

            if (!$r['ok']) {
                throw new RuntimeException(
                    'GRANT PRIVILEGES gagal.'
                );
            }

            // ---------------------------------------------------------
            // STEP 4 — DIRECT DATABASE CONNECTION
            // ---------------------------------------------------------
            $conn = dbConnect(
                'localhost',
                $testDb,
                $testDbUser,
                $testPassword
            );

            $steps[] = [
                'name' => '4. TEST DIRECT DB CONNECTION',
                'ok' => $conn['ok'],
                'detail' => $conn,
            ];

            if (!$conn['ok']) {
                throw new RuntimeException(
                    'Database/user berhasil dibuat, tetapi koneksi langsung gagal.'
                );
            }

            // ---------------------------------------------------------
            // STEP 5 — CREATE TEST TABLE + QUERY
            // ---------------------------------------------------------
            try {
                $pdo = new PDO(
                    'mysql:host=localhost;dbname=' . $testDb . ';charset=utf8mb4',
                    $testDbUser,
                    $testPassword,
                    [
                        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    ]
                );

                $pdo->exec(
                    'CREATE TABLE uapi_provision_test (
                        id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                        test_value VARCHAR(100) NOT NULL,
                        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
                );

                $stmt = $pdo->prepare(
                    'INSERT INTO uapi_provision_test (test_value) VALUES (?)'
                );
                $stmt->execute(['PROVISIONING_OK']);

                $value = (string)$pdo
                    ->query('SELECT test_value FROM uapi_provision_test WHERE id = 1')
                    ->fetchColumn();

                $steps[] = [
                    'name' => '5. CREATE TABLE + INSERT + SELECT',
                    'ok' => ($value === 'PROVISIONING_OK'),
                    'detail' => [
                        'result' => $value,
                        'message' => 'Schema write/read berhasil.'
                    ],
                ];

                if ($value !== 'PROVISIONING_OK') {
                    throw new RuntimeException('Query verification gagal.');
                }

            } catch (Throwable $e) {
                $steps[] = [
                    'name' => '5. CREATE TABLE + INSERT + SELECT',
                    'ok' => false,
                    'detail' => ['error' => $e->getMessage()],
                ];
                throw new RuntimeException(
                    'Operasi database berhasil dibuat tetapi test SQL gagal.'
                );
            }

            // ---------------------------------------------------------
            // STEP 6 — CLEANUP DATABASE
            // ---------------------------------------------------------
            $r = uapi(
                $cpanelHost,
                $cpanelIp,
                $cpanelUser,
                $apiToken,
                'Mysql',
                'delete_database',
                ['name' => $testDb]
            );

            $steps[] = [
                'name' => '6. DELETE TEST DATABASE',
                'ok' => $r['ok'],
                'detail' => $r,
            ];

            if (!$r['ok']) {
                throw new RuntimeException(
                    'TEST PASS, tetapi cleanup database gagal. Hapus database test secara manual melalui cPanel.'
                );
            }

            $cleanupNeeded = false;

            /*
             * cPanel's delete_database may also remove the associated
             * database user depending on cPanel behavior/configuration.
             * We therefore verify whether the temporary DB still exists.
             */
            $check = uapi(
                $cpanelHost,
                $cpanelIp,
                $cpanelUser,
                $apiToken,
                'Mysql',
                'list_databases'
            );

            $stillExists = false;
            $rows = $check['json']['data'] ?? [];

            if (is_array($rows)) {
                foreach ($rows as $row) {
                    if (($row['database'] ?? '') === $testDb) {
                        $stillExists = true;
                        break;
                    }
                }
            }

            $steps[] = [
                'name' => '7. VERIFY CLEANUP',
                'ok' => !$stillExists,
                'detail' => [
                    'database' => $testDb,
                    'exists_after_cleanup' => $stillExists,
                    'message' => $stillExists
                        ? 'Database test masih terdeteksi.'
                        : 'Database test sudah tidak terdeteksi.'
                ],
            ];

            if ($stillExists) {
                throw new RuntimeException(
                    'Cleanup belum lengkap: database test masih terdeteksi.'
                );
            }

            $result = [
                'overall' => 'PASS',
                'test_database' => $testDb,
                'test_user' => $testDbUser,
                'steps' => $steps,
                'message' => 'Provisioning database test berhasil dari awal sampai cleanup.'
            ];

        } catch (Throwable $e) {

            /*
             * Best-effort cleanup if we failed after creating the database.
             * We never display the temporary password.
             */
            if ($cleanupNeeded) {
                try {
                    $cleanup = uapi(
                        $cpanelHost,
                        $cpanelIp,
                        $cpanelUser,
                        $apiToken,
                        'Mysql',
                        'delete_database',
                        ['name' => $testDb]
                    );

                    $steps[] = [
                        'name' => 'CLEANUP AFTER ERROR',
                        'ok' => $cleanup['ok'],
                        'detail' => $cleanup,
                    ];
                } catch (Throwable $cleanupError) {
                    $steps[] = [
                        'name' => 'CLEANUP AFTER ERROR',
                        'ok' => false,
                        'detail' => [
                            'error' => $cleanupError->getMessage()
                        ],
                    ];
                }
            }

            $result = [
                'overall' => 'FAIL',
                'test_database' => $testDb,
                'test_user' => $testDbUser,
                'steps' => $steps,
                'message' => $e->getMessage(),
            ];
        }

        // Token is no longer needed.
        $apiToken = '';
    }
}
?>
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>UAPI Provisioning Test</title>
<style>
:root{
 --bg:#f4f7fb;--card:#fff;--text:#172033;--muted:#667085;
 --border:#dfe5ee;--primary:#1456d9;--success:#087443;
 --success-bg:#e9f8ef;--danger:#b42318;--danger-bg:#fff0ef;
 --code:#101828
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font-family:Arial,Helvetica,sans-serif}
.wrap{width:min(100% - 30px,980px);margin:30px auto}
.hero,.card{background:var(--card);border:1px solid var(--border);border-radius:18px;box-shadow:0 8px 28px rgba(16,24,40,.06)}
.hero{padding:28px;margin-bottom:18px}.card{padding:24px;margin-bottom:18px}
h1{margin:0 0 8px;font-size:28px}h2{margin:0 0 15px;font-size:20px}
p{line-height:1.6}.muted{color:var(--muted)}
.badge{display:inline-block;padding:5px 9px;border-radius:999px;background:#fff3cd;color:#8a5b00;font-size:12px;font-weight:700;margin-bottom:10px}
.notice{padding:15px 17px;border-radius:12px;margin-bottom:18px}
.success{background:var(--success-bg);color:var(--success)}
.danger{background:var(--danger-bg);color:var(--danger)}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:15px}
label{display:block;margin:14px 0 7px;font-weight:700}
input{width:100%;padding:13px 14px;border:1px solid var(--border);border-radius:10px;font-size:15px;outline:none}
input:focus{border-color:var(--primary)}
button{width:100%;margin-top:20px;border:0;border-radius:11px;padding:14px;background:var(--primary);color:#fff;font-size:16px;font-weight:700;cursor:pointer}
.step{border:1px solid var(--border);border-radius:13px;margin:12px 0;overflow:hidden}
.stephead{padding:13px 15px;font-weight:800;background:#f8fafc}
.pass{color:var(--success)}.fail{color:var(--danger)}
.stepbody{padding:15px}
.code{background:var(--code);color:#e6edf3;border-radius:10px;padding:14px;overflow:auto;white-space:pre-wrap;word-break:break-word;font:12px/1.5 Consolas,monospace}
.small{font-size:13px}
@media(max-width:700px){.grid{grid-template-columns:1fr}.wrap{width:min(100% - 20px,980px);margin:15px auto}.hero,.card{padding:18px}}
</style>
</head>
<body>
<div class="wrap">

<section class="hero">
<span class="badge">TEMPORARY • PROVISIONING TEST</span>
<h1>cPanel UAPI Provisioning Test</h1>
<p class="muted">
Tool ini membuktikan kemampuan server untuk membuat database tenant secara otomatis.
Semua object yang dibuat adalah <strong>object TEST sementara</strong>.
</p>
<p><strong>Database produksi dan tenant yang sudah ada tidak disentuh.</strong></p>
<p class="muted small">Nama test: <code><?= h($testDb ?? 'dibuat saat test') ?></code></p>
<p class="muted small">
Akun cPanel ini menggunakan database prefixing. Pada server ini
<code>create_database</code> membutuhkan nama database lengkap ber-prefix,
dan <code>create_user</code> juga membutuhkan nama lengkap ber-prefix.
Operasi privilege menggunakan nama lengkap ber-prefix.
</p>
</section>

<?php if($error): ?>
<div class="notice danger">
<strong>INPUT / SYSTEM ERROR</strong><br><?= h($error) ?>
</div>
<?php endif; ?>

<?php if($result): ?>
<div class="notice <?= $result['overall']==='PASS'?'success':'danger' ?>">
<strong>
<?= $result['overall']==='PASS'
    ? 'PROVISIONING TEST PASS 🎉'
    : 'PROVISIONING TEST GAGAL' ?>
</strong><br>
<?= h((string)$result['message']) ?>
</div>

<section class="card">
<h2>Hasil Setiap Tahap</h2>

<?php foreach($result['steps'] as $step): ?>
<div class="step">
    <div class="stephead <?= $step['ok']?'pass':'fail' ?>">
        <?= $step['ok']?'✅':'❌' ?>
        <?= h((string)$step['name']) ?>
    </div>
    <div class="stepbody">
        <div class="code"><?= h(json_encode($step['detail'],JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)) ?></div>
    </div>
</div>
<?php endforeach; ?>

<p class="small muted">
Test Database: <strong><?= h((string)$result['test_database']) ?></strong><br>
Test User: <strong><?= h((string)$result['test_user']) ?></strong>
</p>
</section>
<?php endif; ?>

<section class="card">
<h2>Jalankan Test</h2>

<form method="post" autocomplete="off">
<div class="grid">
<div>
<label for="cpanel_host">cPanel Host</label>
<input id="cpanel_host" name="cpanel_host" type="text"
value="<?= h($cpanelHost) ?>" required>
</div>

<div>
<label for="cpanel_ip">Server IP</label>
<input id="cpanel_ip" name="cpanel_ip" type="text"
value="<?= h($cpanelIp) ?>" required>
</div>
</div>

<label for="cpanel_user">cPanel Username</label>
<input id="cpanel_user" name="cpanel_user" type="text"
value="<?= h($cpanelUser) ?>" required>

<label for="api_token">cPanel API Token</label>
<input id="api_token" name="api_token" type="password"
placeholder="Paste API Token manajemenjasa-test" required autocomplete="new-password">

<button type="submit">🚀 Jalankan Provisioning Test</button>
</form>

<p class="muted small">
Token tidak disimpan. Password database test dibuat secara acak dan tidak ditampilkan.
Setelah selesai, hapus file PHP ini dari public_html.
</p>
</section>

<section class="card">
<h2>Flow Test</h2>
<p>
<strong>Create DB → Create User → Grant → Connect → Create Table →
Insert → Select → Delete DB → Verify Cleanup</strong>
</p>
</section>

</div>
</body>
</html>
