<?php

/**
 * PeerTubeService
 *
 * Handles credential storage and video/audio uploads to a PeerTube instance
 * via its REST API.  Follows the same patterns as SmtpRelay:
 *   - Static CRUD on a dedicated settings table (peertube_settings)
 *   - A feature-flag setting (peertube_upload_enabled) in site_settings
 *   - An upload() method that POSTs a file to the PeerTube /api/v1/videos/upload
 *     endpoint and returns the resulting watch URL
 *
 * PeerTube API reference:
 *   POST /api/v1/videos/upload         — resumable / direct upload
 *   POST /api/v1/users/token           — OAuth2 password grant
 *
 * The stored access token is refreshed automatically when it expires (401).
 * Credentials (password + token) are encrypted at rest via Crypto.php.
 */
class PeerTubeService
{
    // -------------------------------------------------------------------------
    // DB CRUD
    // -------------------------------------------------------------------------

    /**
     * Return current PeerTube settings from the DB.
     *
     * @return array{instance_url:string,username:string,password:string,channel_id:int,privacy:int}
     */
    public static function getSettings(Database $db): array
    {
        $stmt = $db->getPDO()->prepare(
            'SELECT instance_url, username, password, channel_id, privacy
             FROM peertube_settings WHERE id = 1 LIMIT 1'
        );
        $stmt->execute();
        $row = $stmt->fetch();

        if ($row) {
            require_once __DIR__ . '/Crypto.php';
            $row['password'] = Crypto::decrypt($row['password']);
        }

        return $row ?: [
            'instance_url' => '',
            'username'     => '',
            'password'     => '',
            'channel_id'   => 1,
            'privacy'      => 1, // 1=Public, 2=Unlisted, 3=Private
        ];
    }

    /**
     * Upsert PeerTube settings.
     */
    public static function updateSettings(Database $db, array $data): void
    {
        $current = self::getSettings($db);
        $merged  = array_merge($current, $data);

        require_once __DIR__ . '/Crypto.php';

        // Normalise instance URL: strip trailing slash
        $instanceUrl = rtrim(trim($merged['instance_url']), '/');
        $channelId   = max(1, (int)$merged['channel_id']);
        $privacy     = in_array((int)$merged['privacy'], [1, 2, 3], true)
            ? (int)$merged['privacy'] : 1;

        $pdo = $db->getPDO();

        // Upsert row (id=1 is always the singleton row)
        $exists = $pdo->query('SELECT COUNT(*) FROM peertube_settings WHERE id = 1')->fetchColumn();
        if ($exists) {
            $stmt = $pdo->prepare(
                'UPDATE peertube_settings
                 SET instance_url = :url,
                     username     = :user,
                     password     = :pass,
                     channel_id   = :chan,
                     privacy      = :priv,
                     updated_at   = CURRENT_TIMESTAMP
                 WHERE id = 1'
            );
        } else {
            $stmt = $pdo->prepare(
                'INSERT INTO peertube_settings (id, instance_url, username, password, channel_id, privacy)
                 VALUES (1, :url, :user, :pass, :chan, :priv)'
            );
        }

        $stmt->execute([
            ':url'  => $instanceUrl,
            ':user' => trim($merged['username']),
            ':pass' => Crypto::encrypt($merged['password']),
            ':chan' => $channelId,
            ':priv' => $privacy,
        ]);
    }

    /**
     * Whether PeerTube credentials are fully configured.
     */
    public static function isConfigured(Database $db): bool
    {
        $s = self::getSettings($db);
        return $s['instance_url'] !== '' && $s['username'] !== '';
    }

    /**
     * Whether the PeerTube upload feature flag is enabled.
     */
    public static function isEnabled(Database $db): bool
    {
        return $db->getSetting('peertube_upload_enabled', '0') === '1';
    }

    /**
     * Test connectivity: obtain a token without uploading anything.
     *
     * @return array{ok:bool, error:string}
     */
    public static function test(Database $db): array
    {
        $s = self::getSettings($db);
        if ($s['instance_url'] === '') {
            return ['ok' => false, 'error' => "URL de l'instance PeerTube non configurée."];
        }
        try {
            self::obtainToken($s);
            return ['ok' => true, 'error' => ''];
        } catch (\Throwable $e) {
            return ['ok' => false, 'error' => $e->getMessage()];
        }
    }

    // -------------------------------------------------------------------------
    // Upload
    // -------------------------------------------------------------------------

    /**
     * Upload a local file to PeerTube.
     *
     * @param  string   $filePath   Absolute path to the file on disk.
     * @param  string   $title      Video/audio title shown on PeerTube.
     * @param  string   $description Optional description.
     * @return string               The public watch URL of the uploaded video.
     * @throws \RuntimeException    On any API or network error.
     */
    public static function upload(
        Database $db,
        string   $filePath,
        string   $title,
        string   $description = ''
    ): string {
        $s = self::getSettings($db);
        if ($s['instance_url'] === '') {
            throw new \RuntimeException('PeerTube non configuré.');
        }

        $token = self::obtainToken($s);

        $baseUrl   = $s['instance_url'];
        $uploadUrl = $baseUrl . '/api/v1/videos/upload';

        // Build multipart body
        $boundary = '----XamxamPT' . bin2hex(random_bytes(8));
        $body = '';

        $fields = [
            'channelId'   => (string)$s['channel_id'],
            'name'        => $title,
            'description' => $description,
            'privacy'     => (string)$s['privacy'],
            'waitTranscoding' => 'false',
        ];
        foreach ($fields as $k => $v) {
            $body .= "--{$boundary}\r\n";
            $body .= "Content-Disposition: form-data; name=\"{$k}\"\r\n\r\n";
            $body .= $v . "\r\n";
        }

        // File part
        $fileName = basename($filePath);
        $mimeType = (new \finfo(FILEINFO_MIME_TYPE))->file($filePath);
        $body .= "--{$boundary}\r\n";
        $body .= "Content-Disposition: form-data; name=\"videofile\"; filename=\"{$fileName}\"\r\n";
        $body .= "Content-Type: {$mimeType}\r\n\r\n";
        $body .= file_get_contents($filePath) . "\r\n";
        $body .= "--{$boundary}--\r\n";

        $response = self::httpRequest($uploadUrl, 'POST', $body, [
            'Authorization: Bearer ' . $token,
            'Content-Type: multipart/form-data; boundary=' . $boundary,
            'Content-Length: ' . strlen($body),
        ]);

        $json = json_decode($response['body'], true);

        if ($response['status'] < 200 || $response['status'] >= 300) {
            $msg = $json['error'] ?? $json['detail'] ?? $response['body'];
            throw new \RuntimeException('PeerTube upload failed (' . $response['status'] . '): ' . $msg);
        }

        $shortUuid = $json['video']['shortUUID'] ?? $json['video']['uuid'] ?? null;
        if ($shortUuid === null) {
            throw new \RuntimeException('PeerTube upload: no video UUID in response.');
        }

        return rtrim($baseUrl, '/') . '/videos/watch/' . $shortUuid;
    }

    // -------------------------------------------------------------------------
    // Internal helpers
    // -------------------------------------------------------------------------

    /**
     * Obtain an OAuth2 access token via password grant.
     *
     * @throws \RuntimeException on failure.
     */
    private static function obtainToken(array $s): string
    {
        $tokenUrl = $s['instance_url'] . '/api/v1/users/token';

        $body = http_build_query([
            'client_id'     => self::getClientId($s),
            'client_secret' => self::getClientSecret($s),
            'grant_type'    => 'password',
            'response_type' => 'code',
            'username'      => $s['username'],
            'password'      => $s['password'],
        ]);

        $response = self::httpRequest($tokenUrl, 'POST', $body, [
            'Content-Type: application/x-www-form-urlencoded',
            'Content-Length: ' . strlen($body),
        ]);

        $json = json_decode($response['body'], true);

        if ($response['status'] !== 200 || empty($json['access_token'])) {
            $msg = $json['error_description'] ?? $json['error'] ?? $response['body'];
            throw new \RuntimeException('PeerTube auth failed (' . $response['status'] . '): ' . $msg);
        }

        return $json['access_token'];
    }

    /**
     * Fetch the OAuth2 client_id from the PeerTube instance.
     */
    private static function getClientId(array $s): string
    {
        return self::getOAuthClient($s)['client_id'];
    }

    private static function getClientSecret(array $s): string
    {
        return self::getOAuthClient($s)['client_secret'];
    }

    private static function getOAuthClient(array $s): array
    {
        $url      = $s['instance_url'] . '/api/v1/oauth-clients/local';
        $response = self::httpRequest($url, 'GET', '', []);

        $json = json_decode($response['body'], true);
        if ($response['status'] !== 200 || empty($json['client_id'])) {
            throw new \RuntimeException(
                'Impossible de récupérer les identifiants OAuth de l\'instance PeerTube (' . $response['status'] . ').'
            );
        }

        return $json;
    }

    /**
     * Minimal cURL HTTP helper.
     *
     * @return array{status:int, body:string}
     */
    private static function httpRequest(
        string $url,
        string $method,
        string $body,
        array  $headers,
        int    $timeout = 300
    ): array {
        if (!function_exists('curl_init')) {
            throw new \RuntimeException('L\'extension PHP cURL est requise pour l\'intégration PeerTube.');
        }

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => $timeout,
            CURLOPT_CONNECTTIMEOUT => 15,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS      => 3,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_HTTPHEADER     => $headers,
        ]);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }

        $responseBody = curl_exec($ch);
        $status       = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error        = curl_error($ch);
        curl_close($ch);

        if ($responseBody === false) {
            throw new \RuntimeException('Erreur réseau PeerTube : ' . $error);
        }

        return ['status' => $status, 'body' => (string)$responseBody];
    }
}
