<?php

namespace App\Livewire\Admin;

use App\Livewire\Concerns\ResolvesOperator;
use App\Models\Instance;
use App\Provisioning\Jobs\IssueInstanceAdminAccess;
use App\Services\Wireguard\ConfigHandoff;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use LivewireUI\Modal\ModalComponent;

/**
 * Administrator access to a customer's Nextcloud.
 *
 * Not impersonation: that borrows a portal session. This resets our managed
 * admin account inside the customer's installation and hands the credentials
 * over once — the only thing stock Nextcloud offers, and honest about what it
 * does rather than pretending to be a passwordless jump.
 *
 * The operator's own password is required, every time. Taking control of a
 * customer's installation is not something an unattended browser should be able
 * to do on its own.
 */
class InstanceAdminAccess extends ModalComponent
{
    use ResolvesOperator;

    public string $uuid;

    public string $subdomain = '';

    public string $password = '';

    /** Opaque handle; the credentials never enter the component snapshot. */
    public ?string $token = null;

    public bool $waiting = false;

    public function mount(string $uuid): void
    {
        $this->authorize('instances.adminlogin');

        $instance = Instance::query()->where('uuid', $uuid)->firstOrFail();
        $this->uuid = $uuid;
        $this->subdomain = $instance->subdomain;
    }

    public function hydrate(): void
    {
        $this->authorize('instances.adminlogin');
    }

    public function request(): void
    {
        $this->authorize('instances.adminlogin');
        $this->resetErrorBag('password');

        if (! $operator = $this->currentOperator()) {
            return;
        }

        $key = 'instance-admin:'.$operator->id;
        if (RateLimiter::tooManyAttempts($key, 5)) {
            $this->addError('password', __('instances.too_many_attempts', ['seconds' => RateLimiter::availableIn($key)]));

            return;
        }

        if (! Hash::check($this->password, $operator->password)) {
            RateLimiter::hit($key, 300);
            $this->addError('password', __('instances.wrong_password'));

            return;
        }

        RateLimiter::clear($key);
        $this->reset('password');

        // The reset runs on the provisioning worker — it owns the tunnel. The
        // token is where it will leave the result.
        $this->token = Str::random(40);
        $this->waiting = true;

        IssueInstanceAdminAccess::dispatch($this->uuid, $this->token, $operator->id);
    }

    public function render()
    {
        $payload = null;

        if ($this->token !== null) {
            $raw = ConfigHandoff::get($this->token);

            if ($raw !== null) {
                // Consumed on the first read: leaving it in the cache would make
                // "shown once" a figure of speech — any replayed component
                // request could fetch the password again for ten minutes. The
                // payload lives in a local variable, so it reaches the view and
                // nothing else.
                ConfigHandoff::forget($this->token);
                $this->token = null;
                $this->waiting = false;
                $payload = json_decode($raw, true);
            }
        }

        return view('livewire.admin.instance-admin-access', [
            'credentials' => isset($payload['password']) ? $payload : null,
            'failed' => isset($payload['error']) ? $payload['error'] : null,
        ]);
    }
}
