<?php

namespace App\Support;

/**
 * The one place a guest command that runs Nextcloud's `occ` is built.
 *
 * `docker compose exec` runs as **root** unless it is told otherwise, and the
 * official Nextcloud image will not have it: `console.php` compares the calling
 * uid against the owner of `config/config.php` — www-data — and exits 1 with
 * "Console has to be executed with the user that owns the file config/config.php"
 * before a single command is dispatched. Every occ call in this product was
 * therefore failing on every instance, and the ones that go through
 * CustomerStep::guest() turned that exit code into a retry and then into a failed
 * run for a machine that was otherwise finished.
 *
 * The same default already cost this project days on its OWN container, where
 * `artisan optimize` ran as root and left storage/logs/laravel.log unwritable —
 * see deploy/update.sh's in_app() and tests/Feature/DeploymentRunsAsTheAppUserTest.
 * That lesson was written down for the deployment and not for the guests.
 *
 * It lives here, in one place, because the prefix was pasted into five files and
 * a sixth call site would have got it wrong again. Nothing else in app/ may spell
 * `docker compose exec` out by hand; the test above refuses it.
 */
final class NextcloudOcc
{
    /** Where the compose project sits inside every guest this product builds. */
    public const DIRECTORY = '/opt/nextcloud';

    /**
     * The account that owns the Nextcloud installation inside the image.
     *
     * Named rather than defaulted: the rule is that somebody chose the user, so
     * a reader can tell a deliberate root from a forgotten one.
     */
    public const USER = 'www-data';

    /**
     * A guest shell command that runs `occ <arguments>` as that account.
     *
     * Environment values are passed on the docker invocation itself and handed
     * through with `-e`, because an assignment placed before the `cd` would not
     * survive the `&&` — this is how OC_PASS reaches `user:add
     * --password-from-env` without the password ever appearing in an occ
     * argument (and so in the guest's process list).
     *
     * @param  array<string, string>  $env
     */
    public static function command(string $arguments, array $env = []): string
    {
        $assignments = '';
        $forwards = '';

        foreach ($env as $name => $value) {
            $assignments .= $name.'='.escapeshellarg($value).' ';
            $forwards .= '-e '.$name.' ';
        }

        return 'cd '.self::DIRECTORY.' && '.$assignments
            .'docker compose exec -T -u '.self::USER.' '.$forwards.'app php occ '.$arguments;
    }

    /**
     * A guest shell command inside the SAME container, for anything that is not
     * `occ` — FailedLoginReader's `stat`/`tail` on Nextcloud's own log file, for
     * instance. Same account, same "one place" rule as command() above: nothing
     * else in app/ may spell `docker compose exec` out by hand, and the test
     * next to that rule only exempts this file.
     */
    public static function exec(string $arguments): string
    {
        return 'cd '.self::DIRECTORY.' && docker compose exec -T -u '.self::USER.' app '.$arguments;
    }
}
