<?php
require_once __DIR__ . '/_common.php';
require_csrf_unless_get();
list($action, $body) = read_action();

// VPN rows store the buyer's actual VPN credentials and the .ovpn config
// (which embeds connection details). Hide those from viewers.
function vpns_redact_for_viewer(array $row) {
    $row['username'] = '<hidden>';
    $row['password'] = '<hidden>';
    $row['ovpn']     = '<hidden>';
    return $row;
}

// Fetch an .ovpn from a remote URL. Returns the config text on success, or
// throws with a human-readable message. Used when the operator enters a URL
// in the Add/Edit form and leaves the OVPN textarea empty. Locked to
// http(s) so a compromised input can't force file:// / gopher:// SSRF.
function _vpns_fetch_ovpn_from_url(string $url): string {
    $url = trim($url);
    if (!preg_match('#^https?://#i', $url)) {
        throw new RuntimeException('URL must start with http:// or https://');
    }
    // Cloud-host share URLs return an HTML preview, not the raw file.
    // Rewrite the common ones so a paste-and-go link Just Works.
    // - Dropbox: append/force ?dl=1 (works for both /s/ and /scl/ links)
    // - Google Drive share links: convert /file/d/ID/view → /uc?export=download&id=ID
    if (preg_match('#^https?://(www\.)?dropbox\.com/#i', $url)) {
        $u = parse_url($url);
        $qs = [];
        if (!empty($u['query'])) parse_str($u['query'], $qs);
        $qs['dl'] = '1';
        $url = ($u['scheme'] ?? 'https') . '://' . ($u['host'] ?? 'www.dropbox.com')
             . ($u['path'] ?? '/') . '?' . http_build_query($qs);
    } elseif (preg_match('#^https?://drive\.google\.com/file/d/([^/]+)/#i', $url, $m)) {
        $url = 'https://drive.google.com/uc?export=download&id=' . $m[1];
    }
    $proto_mask = (defined('CURLPROTO_HTTPS') ? CURLPROTO_HTTPS : 0)
                | (defined('CURLPROTO_HTTP')  ? CURLPROTO_HTTP  : 0);
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER  => true,
        CURLOPT_FOLLOWLOCATION  => true,
        CURLOPT_MAXREDIRS       => 3,
        CURLOPT_CONNECTTIMEOUT  => 5,
        CURLOPT_TIMEOUT         => 15,
        CURLOPT_USERAGENT       => 'IBOPanel/1.0 (+vpn-fetch)',
        CURLOPT_PROTOCOLS       => $proto_mask,
        CURLOPT_REDIR_PROTOCOLS => $proto_mask,
    ]);
    $body = curl_exec($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err  = curl_error($ch);
    curl_close($ch);

    if ($body === false || $body === '') {
        throw new RuntimeException('Could not download the OVPN — ' . ($err ?: "no response from $url"));
    }
    if ($code !== 200) {
        throw new RuntimeException("The URL returned HTTP $code — is the file public?");
    }
    if (strlen($body) > 512 * 1024) {
        throw new RuntimeException('That file is over 512 KB — too large to be an OVPN config.');
    }
    // Sanity check: an OVPN config must have at least one `remote` directive
    // (line-start, whitespace-tolerant). Prevents silently storing HTML error
    // pages, login walls, or garbage that would then crash the app.
    if (!preg_match('/^\s*remote\s+\S+/m', $body)) {
        throw new RuntimeException('The downloaded file does not look like an OVPN config (no `remote` directive).');
    }
    return $body;
}

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    if (!empty($_GET['id'])) {
        $row = db_one('SELECT * FROM vpn_profiles WHERE id = ?', [(int)$_GET['id']]);
        if (!$row) json_error('vpn profile not found', 404);
        if (!auth_is_admin()) $row = vpns_redact_for_viewer($row);
        json_response(['vpn' => $row]);
    }
    $rows = db_all('SELECT * FROM vpn_profiles ORDER BY sort_order ASC, id ASC');
    if (!auth_is_admin()) $rows = array_map('vpns_redact_for_viewer', $rows);
    json_response(['vpns' => $rows]);
}

if ($action === 'create') {
    // username + password are OPTIONAL at create — the UI has them in the
    // separate Credentials tab so the same pair can be bulk-applied to many
    // configs at once. Only the OVPN config + name are mandatory — but the
    // OVPN can come either as pasted text OR as a URL that we fetch below.
    if (!isset($body['name']) || trim((string)$body['name']) === '') {
        json_error('Please enter a name.');
    }
    $ovpn = trim((string)($body['ovpn'] ?? ''));
    $url  = trim((string)($body['url']  ?? ''));
    if ($ovpn === '') {
        if ($url === '') {
            json_error('Please paste the OVPN config, or enter a URL to fetch it from.');
        }
        try {
            $ovpn = _vpns_fetch_ovpn_from_url($url);
        } catch (RuntimeException $e) {
            json_error($e->getMessage());
        }
    }
    try {
        $id = db_insert(
            'INSERT INTO vpn_profiles (name, provider, country, state, url,
                                       ovpn, username, password, sort_order, enabled)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
            [trim((string)$body['name']),
             $body['provider'] ?? 'Other',
             $body['country']  ?? '',
             $body['state']    ?? '',
             $url,
             $ovpn,
             (string)($body['username'] ?? ''),
             (string)($body['password'] ?? ''),
             (int)($body['sort_order'] ?? 0),
             truthy($body['enabled'] ?? 1)]
        );
        json_response(['ok' => true, 'id' => $id]);
    } catch (Exception $e) {
        panel_log_exception($e, ['context' => 'vpns.create']);
        json_error('Could not save the VPN profile. Try again.');
    }
}

if ($action === 'update') {
    if (empty($body['id'])) json_error('missing id');
    $row = db_one('SELECT * FROM vpn_profiles WHERE id = ?', [(int)$body['id']]);
    if (!$row) json_error('vpn profile not found', 404);

    // Same fetch-from-URL behaviour as create — if the form cleared out the
    // ovpn textarea and a URL is present, re-fetch. `refetch_from_url` is a
    // future opt-in signal; today an empty ovpn + non-empty url is enough.
    $ovpn = array_key_exists('ovpn', $body) ? trim((string)$body['ovpn']) : (string)$row['ovpn'];
    $url  = array_key_exists('url',  $body) ? trim((string)$body['url'])  : (string)$row['url'];
    if ($ovpn === '' && $url !== '') {
        try {
            $ovpn = _vpns_fetch_ovpn_from_url($url);
        } catch (RuntimeException $e) {
            json_error($e->getMessage());
        }
    }
    db_exec(
        'UPDATE vpn_profiles SET name = ?, provider = ?, country = ?, state = ?, url = ?,
                                 ovpn = ?, username = ?, password = ?, sort_order = ?, enabled = ?
         WHERE id = ?',
        [$body['name']     ?? $row['name'],
         $body['provider'] ?? $row['provider'],
         $body['country']  ?? $row['country'],
         $body['state']    ?? $row['state'],
         $url,
         $ovpn,
         $body['username'] ?? $row['username'],
         $body['password'] ?? $row['password'],
         (int)($body['sort_order'] ?? $row['sort_order']),
         truthy($body['enabled'] ?? $row['enabled']),
         (int)$body['id']]
    );
    json_response(['ok' => true]);
}

if ($action === 'delete') {
    if (empty($body['id'])) json_error('missing id');
    db_exec('DELETE FROM vpn_profiles WHERE id = ?', [(int)$body['id']]);
    json_response(['ok' => true]);
}

json_error('unknown action', 400);
