<?php

namespace App\Livewire\Patch;

use App\Models\Server;
use App\Services\MaintenanceService;
use App\Support\Confirm\ConfirmToken;
use App\Support\Confirm\InvalidConfirmToken;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Component;
use Throwable;

/**
 * Fleet patch view: pending + security update counts per server, one-click patch. `operate`-gated
 * (route + mount + per-method). Counts scan is lazy + per-server guarded. Applying upgrades is a
 * stateful action → a signed ConfirmToken + R5 modal (the modal audits patch.apply from the sealed
 * token; the handler does not re-audit). Targets resolve a client uuid → the sealed server id.
 */
#[Layout('layouts.app')]
class Index extends Component
{
    /** @var array<string, array{pending:?int, security:?int}|array{error:string}> uuid → counts */
    public array $counts = [];

    /** @var array<string, array{ok:bool, output:string}> uuid → last patch result */
    public array $results = [];

    public bool $ready = false;

    public function mount(): void
    {
        abort_unless(Auth::user()?->can('operate'), 403);
    }

    public function title(): string
    {
        return __('patch.title');
    }

    private function gate(): void
    {
        abort_unless(Auth::user()?->can('operate'), 403);
    }

    public function scan(MaintenanceService $maint): void
    {
        $this->gate();
        $this->counts = [];

        foreach (Server::withActiveCredential()->orderBy('name')->get() as $server) {
            try {
                $this->counts[$server->uuid] = $maint->updateCounts($server);
            } catch (Throwable $e) {
                $this->counts[$server->uuid] = ['error' => $e->getMessage()];
            }
        }

        $this->ready = true;
    }

    public function patch(string $uuid): void
    {
        $this->gate();
        $server = Server::withActiveCredential()->where('uuid', $uuid)->firstOrFail();
        $c = $this->counts[$uuid] ?? [];
        $pending = $c['pending'] ?? '?';

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('patch.confirm_heading'),
                'body' => __('patch.confirm_body', ['server' => $server->name, 'count' => $pending]),
                'confirmLabel' => __('patch.apply'),
                'danger' => true,
                'icon' => 'rotate',
                'notify' => '', // the handler reports the outcome
                'token' => ConfirmToken::issue('patchApply', ['uuid' => $server->uuid], 'patch.apply', $server->name, $server->id),
            ],
        );
    }

    #[On('patchApply')]
    public function applyPatch(string $confirmToken, MaintenanceService $maint): void
    {
        $this->gate();

        try {
            $payload = ConfirmToken::consume($confirmToken, 'patchApply');
        } catch (InvalidConfirmToken) {
            return; // forged / replayed — the modal already audited patch.apply
        }

        $server = Server::where('uuid', $payload['params']['uuid'])->first();
        if (! $server) {
            return;
        }

        $res = $maint->applyUpgrades($server);
        $res['output'] = mb_scrub($res['output'], 'UTF-8'); // remote output into a Livewire prop → scrub invalid bytes
        $this->results[$server->uuid] = $res;

        // Refresh this server's counts so the badge reflects the post-patch state.
        try {
            $this->counts[$server->uuid] = $maint->updateCounts($server);
        } catch (Throwable) {
            // keep the old counts if the re-scan fails
        }

        $this->dispatch('notify', message: $res['ok']
            ? __('patch.patched', ['server' => $server->name])
            : __('patch.patch_failed', ['error' => $res['output']]));
    }

    public function render(): View
    {
        // Fleet roll-up (only over successfully-scanned servers).
        $scored = array_filter($this->counts, fn ($c) => isset($c['pending']) && $c['pending'] !== null);
        $totalPending = array_sum(array_map(fn ($c) => $c['pending'], $scored));
        $totalSecurity = array_sum(array_map(fn ($c) => $c['security'] ?? 0, $scored));

        return view('livewire.patch.index', [
            'servers' => Server::withActiveCredential()->orderBy('name')->get(['uuid', 'name']),
            'totalPending' => (int) $totalPending,
            'totalSecurity' => (int) $totalSecurity,
        ])->title($this->title());
    }
}
