<?php
/**
 * ============================================================================
 *  BentBox API — Integration Demo :: LIBRARY
 * ============================================================================
 *  Shared helpers used by the demo pages. You usually don't need to edit this
 *  for basic setup — put your settings in config.php. Read top to bottom to
 *  understand exactly how the integration works.
 *
 *  Design notes:
 *   - The token exchange and every API call happen SERVER-SIDE (cURL), so the
 *     client_secret and the access token are never exposed to the browser.
 *   - There is NO server-side session. The connection is kept in one encrypted
 *     cookie, so the demo is stateless and works on a single host or behind a
 *     load balancer without any shared storage.
 * ----------------------------------------------------------------------------
 */

// ── API endpoints ────────────────────────────────────────────────────────────
define('BB_AUTHORIZE_URL', BB_API_BASE . '/oauth/authorize');   // consent screen
define('BB_TOKEN_URL',     BB_API_BASE . '/oauth/token');       // code -> tokens
define('BB_CREATE_VIDEO',  BB_API_BASE . '/v1/content/video');  // step 1: metadata
define('BB_UPLOAD_URL',    BB_API_BASE . '/v1/content/upload-url'); // step 2: presigned URL

const BB_CONN_COOKIE  = 'bb_conn';         // encrypted connection (tokens)
const BB_STATE_COOKIE = 'bb_oauth_state';  // CSRF state during the OAuth round trip
const BB_CONN_TTL     = 2592000;           // 30 days

// ── Cookie option helper ─────────────────────────────────────────────────────
function bb_cookie_opts(int $expires): array {
    $o = [
        'expires'  => $expires,
        'path'     => '/',
        'secure'   => true,     // HTTPS only
        'httponly' => true,     // not readable from JavaScript
        'samesite' => 'Lax',    // sent on the top-level redirect back from BentBox
    ];
    if (BB_COOKIE_DOMAIN !== '') $o['domain'] = BB_COOKIE_DOMAIN;
    return $o;
}

// ── HTTP (server-side JSON POST) ─────────────────────────────────────────────
function bb_post_json(string $url, array $body, ?string $bearer = null): array {
    $headers = ['Content-Type: application/json', 'Accept: application/json'];
    if ($bearer) $headers[] = 'Authorization: Bearer ' . $bearer;

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode($body),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => $headers,
    ]);
    $raw  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err  = curl_error($ch);
    curl_close($ch);

    return ['http' => $code, 'json' => json_decode($raw, true), 'raw' => $raw, 'error' => $err];
}

/* ============================================================================
 *  OAUTH
 * ==========================================================================*/

// Build the URL that starts the OAuth flow (the BentBox consent screen).
function bb_authorize_url(string $state): string {
    return BB_AUTHORIZE_URL . '?' . http_build_query([
        'client_id'     => BB_CLIENT_ID,
        'redirect_uri'  => BB_REDIRECT_URI,
        'scope'         => BB_SCOPES,
        'state'         => $state,
        'response_type' => 'code',
    ]);
}

// Exchange the authorization code for tokens. Runs server-side; the secret
// never leaves your server.
function bb_exchange_code(string $code): array {
    return bb_post_json(BB_TOKEN_URL, [
        'grant_type'    => 'authorization_code',
        'code'          => $code,
        'client_id'     => BB_CLIENT_ID,
        'client_secret' => BB_CLIENT_SECRET,
        'redirect_uri'  => BB_REDIRECT_URI,
    ]);
}

/* ============================================================================
 *  CONNECTION STORE  (one encrypted cookie — no sessions, no database)
 *  The blob holds: access_token, refresh_token, connection_id, user_id,
 *  scopes, expires_at. It's AES-256-GCM encrypted with BB_COOKIE_KEY, so the
 *  browser can neither read nor tamper with it.
 * ==========================================================================*/

// In-request cache so a write is visible to a later read in the same request
// (e.g. right after a token refresh), since $_COOKIE isn't updated mid-request.
function &bb_conn_cache(): array {
    static $c = ['loaded' => false, 'data' => null];
    return $c;
}

function bb_store_connection(array $tokens): void {
    $existing = bb_get_connection() ?? [];
    $data = [
        'access_token'  => $tokens['access_token']  ?? $existing['access_token']  ?? null,
        'refresh_token' => $tokens['refresh_token'] ?? $existing['refresh_token'] ?? null,
        'connection_id' => $tokens['connection_id'] ?? $existing['connection_id'] ?? null,
        'user_id'       => $tokens['user_id']       ?? $existing['user_id']       ?? null,
        'scopes'        => $tokens['scopes']        ?? $existing['scopes']        ?? [],
        'expires_at'    => isset($tokens['expires_in'])
                            ? time() + (int)$tokens['expires_in']
                            : ($existing['expires_at'] ?? 0),
    ];
    setcookie(BB_CONN_COOKIE, bb_encrypt($data), bb_cookie_opts(time() + BB_CONN_TTL));

    $c = &bb_conn_cache(); $c['loaded'] = true; $c['data'] = $data;
}

function bb_get_connection(): ?array {
    $c = &bb_conn_cache();
    if (!$c['loaded']) {
        $c['data']   = bb_decrypt($_COOKIE[BB_CONN_COOKIE] ?? '');
        $c['loaded'] = true;
    }
    return $c['data'];
}

function bb_is_connected(): bool {
    $c = bb_get_connection();
    return $c && !empty($c['access_token']) && !empty($c['connection_id']);
}

function bb_clear_connection(): void {
    setcookie(BB_CONN_COOKIE, '', bb_cookie_opts(time() - 3600));
    $c = &bb_conn_cache(); $c['loaded'] = true; $c['data'] = null;
}

/* ============================================================================
 *  COOKIE ENCRYPTION  (AES-256-GCM, url-safe base64)
 * ==========================================================================*/
function bb_key(): string {
    $k = base64_decode(BB_COOKIE_KEY, true);
    if ($k === false || strlen($k) !== 32) {
        throw new RuntimeException(
            'BB_COOKIE_KEY must be a base64-encoded 32-byte key. Generate one with: ' .
            'php -r "echo base64_encode(random_bytes(32)).PHP_EOL;"'
        );
    }
    return $k;
}
function bb_b64url(string $b): string      { return rtrim(strtr(base64_encode($b), '+/', '-_'), '='); }
function bb_b64url_decode(string $s)        { return base64_decode(strtr($s, '-_', '+/')); }

function bb_encrypt(array $data): string {
    $iv = random_bytes(12); $tag = '';
    $ct = openssl_encrypt(json_encode($data), 'aes-256-gcm', bb_key(), OPENSSL_RAW_DATA, $iv, $tag);
    if ($ct === false) throw new RuntimeException('Cookie encryption failed');
    return bb_b64url($iv . $tag . $ct);          // iv(12) | tag(16) | ciphertext
}
function bb_decrypt(string $raw): ?array {
    if ($raw === '') return null;
    $bin = bb_b64url_decode($raw);
    if ($bin === false || strlen($bin) < 28) return null;
    $pt = openssl_decrypt(substr($bin, 28), 'aes-256-gcm', bb_key(),
                          OPENSSL_RAW_DATA, substr($bin, 0, 12), substr($bin, 12, 16));
    if ($pt === false) return null;              // wrong key / tampered / corrupt
    $d = json_decode($pt, true);
    return is_array($d) ? $d : null;
}

/* ============================================================================
 *  CONTENT UPLOAD
 *  Two API calls, then a direct upload to storage:
 *    1) POST /v1/content/video      -> reserves a video_id
 *    2) POST /v1/content/upload-url -> returns a short-lived presigned URL
 *    3) PUT  <presigned url>        -> the file bytes (done by the browser or,
 *                                      in proxy mode, by this server)
 *  Both API calls send the access token in the body AND as a Bearer header.
 *  Retries once after refreshing the token on a 403 (expired).
 * ==========================================================================*/
function bb_create_and_sign(array $m, bool $allowRefresh = true): array {
    $conn  = bb_get_connection();
    $token = $conn['access_token'];

    $create = bb_post_json(BB_CREATE_VIDEO, [
        'access_token'  => $token,
        'connection_id' => $conn['connection_id'],
        'title'         => $m['title'],
        'filename'      => $m['filename'],
        'filesize'      => $m['filesize'],
        'duration'      => $m['duration'],
        'description'   => $m['description'],
        'tags'          => $m['tags'],
        'price'         => $m['price'],
        'is_premium'    => $m['is_premium'],
        'sole_performer'   => $m['sole_performer'],
        'release_form_id'  => $m['release_form_id'],
        'publish_on_ready' => $m['publish_on_ready'],
    ], $token);

    if ($create['http'] === 403 && $allowRefresh && bb_refresh($conn)) {
        return bb_create_and_sign($m, false);
    }
    if (empty($create['json']['success']) || empty($create['json']['video_id'])) {
        return ['ok' => false, 'stage' => 'create_metadata', 'http' => $create['http'],
                'error' => $create['json']['error_description'] ?? 'Failed to create metadata'];
    }
    $videoId = $create['json']['video_id'];

    $sign = bb_post_json(BB_UPLOAD_URL, [
        'access_token'  => $token,
        'connection_id' => $conn['connection_id'],
        'video_id'      => $videoId,
        'title'         => $m['title'],
        'price'         => $m['price'],
        'duration'      => $m['duration'],
        'description'   => $m['description'],
        'tags'          => $m['tags'],
        'adult_content' => $m['adult_content'],
    ], $token);

    if (empty($sign['json']['success']) || empty($sign['json']['upload_url'])) {
        return ['ok' => false, 'stage' => 'get_upload_url', 'http' => $sign['http'],
                'video_id' => $videoId,
                'error' => $sign['json']['error_description'] ?? 'Failed to get upload URL'];
    }

    return ['ok' => true, 'video_id' => $videoId,
            'upload_url' => $sign['json']['upload_url'],
            'content_type' => $sign['json']['content_type'] ?? 'video/mp4'];
}

// Refresh the access token using the stored refresh token (standard OAuth
// refresh grant). Updates the stored connection on success.
function bb_refresh(array &$conn): bool {
    if (empty($conn['refresh_token'])) return false;
    $r = bb_post_json(BB_TOKEN_URL, [
        'grant_type'    => 'refresh_token',
        'refresh_token' => $conn['refresh_token'],
        'client_id'     => BB_CLIENT_ID,
        'client_secret' => BB_CLIENT_SECRET,
    ]);
    if (!empty($r['json']['access_token'])) {
        bb_store_connection(array_merge($conn, $r['json']));
        $conn = bb_get_connection();
        return true;
    }
    return false;
}

// ── Small helpers ────────────────────────────────────────────────────────────
function bb_json($payload): void {
    header('Content-Type: application/json');
    echo json_encode($payload);
    exit;
}
function bb_read_meta(array $src): array {
    $tags = $src['tags'] ?? [];
    if (is_string($tags)) $tags = array_filter(array_map('trim', explode(',', $tags)));
    return [
        'title'         => trim($src['title'] ?? ''),
        'filename'      => trim($src['filename'] ?? ''),
        'filesize'      => (int)($src['filesize'] ?? 0),
        'duration'      => (float)($src['duration'] ?? 0),
        'description'   => trim($src['description'] ?? ''),
        'price'         => (float)($src['price'] ?? 0),
        'is_premium'    => filter_var($src['is_premium'] ?? false, FILTER_VALIDATE_BOOLEAN),
        'adult_content' => filter_var($src['adult_content'] ?? true, FILTER_VALIDATE_BOOLEAN),
        'sole_performer'   => filter_var($src['sole_performer'] ?? false, FILTER_VALIDATE_BOOLEAN),
        'release_form_id'  => trim($src['release_form_id'] ?? ''),
        'publish_on_ready' => filter_var($src['publish_on_ready'] ?? true, FILTER_VALIDATE_BOOLEAN),
        'tags'          => array_values($tags),
    ];
}
function bb_validate_meta(array $m): ?string {
    if ($m['title'] === '')    return 'A title is required.';
    if ($m['filename'] === '') return 'A filename is required.';
    if ($m['filesize'] <= 0)   return 'File size could not be determined.';
    if ($m['duration'] <= 0)   return 'Video duration could not be determined.';
    if (!$m['sole_performer'] && $m['release_form_id'] === '')
        return 'Provide a ProntoID release form ID, or confirm you are the only performer.';
    return null;
}
