<?php

namespace App\Provisioning\Jobs;

use App\Models\Instance;
use App\Services\Proxmox\ProxmoxClient;
use App\Services\Wireguard\ConfigHandoff;
use App\Support\NextcloudOcc;
use App\Support\ProvisioningSettings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Throwable;

/**
 * Hands an operator working administrator credentials for a customer's
 * Nextcloud.
 *
 * Nextcloud has no "log in as admin without a password", so this does the only
 * honest thing stock Nextcloud allows: it rotates OUR managed admin account's
 * password and hands the new one over once. The customer's own accounts are
 * untouched.
 *
 * Runs on the provisioning queue because that worker owns the tunnel and the
 * Proxmox credentials; the console has neither.
 */
class IssueInstanceAdminAccess implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 1;

    public function __construct(
        public string $instanceUuid,
        /** Where to leave the credentials for the console to pick up once. */
        public string $handoffToken,
        public int $requestedBy,
    ) {
        $this->onConnection('provisioning');
        $this->onQueue('provisioning');
    }

    /** Anything that escapes handle() still has to reach the waiting console. */
    public function failed(?Throwable $e): void
    {
        ConfigHandoff::put(json_encode(['error' => 'failed']), $this->handoffToken);
    }

    public function handle(ProxmoxClient $proxmox): void
    {
        $instance = Instance::query()->with('host')->where('uuid', $this->instanceUuid)->first();

        // Only a live instance: a closed one's VMID can have been reused on the
        // same host, and the reset would then land on a stranger's VM.
        if ($instance === null || $instance->status !== 'active'
            || $instance->host === null || blank($instance->vmid) || blank($instance->nc_admin_ref)) {
            ConfigHandoff::put(json_encode(['error' => 'unavailable']), $this->handoffToken);

            return;
        }

        $username = $instance->nc_admin_ref;
        $password = Str::password(24, symbols: false);

        try {
            $result = $proxmox->forHost($instance->host)->guestExec(
                $instance->host->node ?? 'pve',
                (int) $instance->vmid,
                NextcloudOcc::command(
                    'user:resetpassword --password-from-env '.escapeshellarg($username),
                    ['OC_PASS' => $password],
                ),
            );
        } catch (Throwable $e) {
            // An unreachable Proxmox or guest agent throws rather than
            // returning a code. Without this the console would poll forever
            // instead of saying what happened.
            Log::warning('instance admin access errored', [
                'instance' => $instance->uuid, 'error' => $e->getMessage(),
            ]);
            ConfigHandoff::put(json_encode(['error' => 'failed']), $this->handoffToken);

            return;
        }

        if ((int) ($result['exitcode'] ?? 1) !== 0) {
            Log::warning('instance admin access failed', [
                'instance' => $instance->uuid, 'user' => $this->requestedBy,
            ]);
            ConfigHandoff::put(json_encode(['error' => 'failed']), $this->handoffToken);

            return;
        }

        // Auditable on its own: who asked, for which instance, when. The
        // credentials themselves are never logged.
        Log::info('instance admin access issued', [
            'instance' => $instance->uuid, 'user' => $this->requestedBy,
        ]);

        ConfigHandoff::put(json_encode([
            // Through address(), which serves the customer's own domain only
            // once its DNS proof has been read. A link to an unverified
            // domain would not resolve to this instance anyway.
            'url' => 'https://'.$instance->address(ProvisioningSettings::dnsZone()),
            'username' => $username,
            'password' => $password,
        ]), $this->handoffToken);
    }
}
