<?php
/**
 * _script functions for the emailSync report: OAuth2 device code authorization from the browser.
 * No shell access needed - the whole flow runs via two buttons on the emailSync page.
 *
 * Report usage (see Resources/Private/Report/emailSync.qfqr):
 *   'F:typo3conf/ext/qfq/Classes/External/emailSyncOAuth2.php|call:emailSyncOAuth2Start|arg:settingId=...' AS _script
 *   'F:typo3conf/ext/qfq/Classes/External/emailSyncOAuth2.php|call:emailSyncOAuth2Poll|arg:settingId=...' AS _script
 *
 * The functions echo their HTML output (rendered by ColumnScript). The account must be configured
 * for OAuth2: password in the emailSync form = 'oauth2token:<path to token file>'.
 */

use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\EncryptDecrypt;
use IMATHUZH\Qfq\Core\Imap\OAuth2TokenManager;
use IMATHUZH\Qfq\Core\Store\Store;

/**
 * Step 1: request a device code and show the login instructions.
 *
 * @param array $param - 'settingId' = Setting.id of the email account
 * @param object $qfq - ScriptFunctions instance (unused)
 * @return void
 */
function emailSyncOAuth2Start(array $param, $qfq): void {

    try {
        list($pathFileNameToken, $email) = emailSyncOAuth2Account((int)($param['settingId'] ?? 0));

        $clientId = trim((string)Store::getInstance()->getVar(SYSTEM_IMAP_OAUTH2_CLIENT_ID, STORE_SYSTEM));
        $start = (new OAuth2TokenManager($clientId))->startDeviceAuthorization($pathFileNameToken, $email);

        $uri = htmlspecialchars($start['verification_uri']);
        $minutes = max(1, (int)floor($start['expires_in'] / 60));

        if (!empty($start['was_authorized'])) {
            echo '<div class="alert alert-warning">This account is already authorized - the existing tokens keep '
                . 'working until this new login is completed. Clicked by accident? Simply ignore this page.</div>';
        }

        echo '<div class="alert alert-info">'
            . '<p><b>Authorize ' . htmlspecialchars($email) . ':</b></p>'
            . '<ol>'
            . '<li>Open <a href="' . $uri . '" target="_blank" rel="noopener">' . $uri . '</a></li>'
            . '<li>Enter the code: <b><code>' . htmlspecialchars($start['user_code']) . '</code></b></li>'
            . '<li>Log in with the account (MFA works), then come back and click <i>Check login</i>.</li>'
            . '</ol>'
            . '<p>The code is valid for ' . $minutes . ' minutes.</p>'
            . '</div>';

    } catch (\Exception $e) {
        echo '<div class="alert alert-danger">' . htmlspecialchars($e->getMessage()) . '</div>';
    }
}

/**
 * Step 2: one poll per click - fetches the tokens once the browser login is completed.
 *
 * @param array $param - 'settingId' = Setting.id of the email account
 * @param object $qfq - ScriptFunctions instance (unused)
 * @return void
 */
function emailSyncOAuth2Poll(array $param, $qfq): void {

    try {
        list($pathFileNameToken, $email) = emailSyncOAuth2Account((int)($param['settingId'] ?? 0));

        $clientId = trim((string)Store::getInstance()->getVar(SYSTEM_IMAP_OAUTH2_CLIENT_ID, STORE_SYSTEM));
        $result = (new OAuth2TokenManager($clientId))->pollDeviceAuthorization($pathFileNameToken);

        $cssClass = ['ok' => 'alert-success', 'pending' => 'alert-warning', 'error' => 'alert-danger'][$result['status']] ?? 'alert-info';
        echo '<div class="alert ' . $cssClass . '">' . htmlspecialchars($email . ': ' . $result['message']) . '</div>';

    } catch (\Exception $e) {
        echo '<div class="alert alert-danger">' . htmlspecialchars($e->getMessage()) . '</div>';
    }
}

/**
 * Loads the account (Setting row), decrypts the password and extracts the token file path.
 *
 * @param int $settingId
 * @return array - [pathFileNameToken, email]
 * @throws \UserReportException
 */
function emailSyncOAuth2Account(int $settingId): array {

    if ($settingId <= 0) {
        throw new \UserReportException('Missing or invalid settingId.', ERROR_IMAP);
    }

    $store = Store::getInstance();
    $db = new Database($store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM));

    $row = $db->sql("SELECT `feUser`, `view` FROM `Setting` WHERE `id` = ? AND `type` = ?", ROW_EXPECT_1,
        [$settingId, SETTING_TABLE_COLUMN_TYPE_CREDENTIALS], "Email account not found: Setting.id=$settingId");

    $encryptionKey = $store->getVar(SYSTEM_ENCRYPTION_KEY, STORE_SYSTEM, SANITIZE_ALLOW_ALL);
    $password = EncryptDecrypt::getPlaintext($row['view'] ?? '', $encryptionKey);

    if (strpos($password, IMAP_OAUTH2_TOKEN_PREFIX) !== 0) {
        throw new \UserReportException("Account '" . $row['feUser'] . "' is not configured for OAuth2: "
            . "set the password in the emailSync form to '" . IMAP_OAUTH2_TOKEN_PREFIX . "<path to token file>' first.", ERROR_IMAP);
    }

    $pathFileNameToken = trim(substr($password, strlen(IMAP_OAUTH2_TOKEN_PREFIX)));
    if ($pathFileNameToken === '') {
        throw new \UserReportException("Account '" . $row['feUser'] . "': the password is '" . IMAP_OAUTH2_TOKEN_PREFIX
            . "' without a path. Set it to '" . IMAP_OAUTH2_TOKEN_PREFIX . "<path to token file>', e.g. '"
            . IMAP_OAUTH2_TOKEN_PREFIX . "/var/qfq-token/" . $row['feUser'] . ".token' "
            . "(directory must exist and be writable for the webserver user).", ERROR_IMAP);
    }

    return [$pathFileNameToken, $row['feUser']];
}
