<?php

namespace App\Livewire\Commands;

use App\Models\AuditEvent;
use App\Models\Runbook;
use App\Models\Server;
use App\Models\ServerGroup;
use App\Services\CommandRunner;
use App\Support\Confirm\ConfirmToken;
use App\Support\Confirm\InvalidConfirmToken;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Component;

/**
 * Ad-hoc fleet command / runbook runner. SECURITY-CRITICAL: arbitrary remote execution, so the whole
 * page is `operate`-gated (route + mount + per-method), every run goes through a signed ConfirmToken +
 * R5 confirm modal, and EVERY run is audited (command + target count). Targets resolve client UUIDs →
 * ids server-side; only active-credential servers are run. Output is byte-capped + scrubbed by
 * CommandRunner. The command runs as the credential login user (runPlain) — no extra sudo escalation.
 */
#[Layout('layouts.app')]
class Index extends Component
{
    public string $command = '';

    public string $scopeType = 'all'; // all | group | servers

    public ?string $scopeGroupUuid = null;

    /** @var array<int, string> selected server uuids (scopeType=servers) */
    public array $selectedServers = [];

    // New-runbook form
    public string $runbookName = '';

    public string $runbookCommand = '';

    /** @var array<int, array{server:string, ok:bool, output:string}> last run's results */
    public array $results = [];

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

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

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

    /** Resolve the current target selection to active-credential servers (uuids → models). */
    private function targetServers(): Collection
    {
        $q = Server::withActiveCredential();

        if ($this->scopeType === 'group' && $this->scopeGroupUuid) {
            $group = ServerGroup::where('uuid', $this->scopeGroupUuid)->first();

            return $group ? $q->inGroup($group->id)->get() : new Collection;
        }

        if ($this->scopeType === 'servers') {
            return $q->whereIn('uuid', $this->selectedServers)->get();
        }

        return $q->get(); // 'all'
    }

    private function issueRun(string $command): void
    {
        $command = trim($command);
        if ($command === '') {
            $this->addError('command', __('commands.command_required'));

            return;
        }

        $servers = $this->targetServers();
        if ($servers->isEmpty()) {
            $this->addError('command', __('commands.no_targets'));

            return;
        }

        $ids = $servers->pluck('id')->all();
        $target = Str::limit($command, 60).' · '.__('commands.n_servers', ['count' => count($ids)]);

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('commands.confirm_heading'),
                'body' => __('commands.confirm_body', ['count' => count($ids), 'command' => Str::limit($command, 120)]),
                'confirmLabel' => __('commands.run'),
                'danger' => true,
                'icon' => 'command',
                'notify' => '', // the handler reports the real outcome
                'token' => ConfirmToken::issue('commandRun', ['command' => $command, 'serverIds' => $ids], 'command.run', $target, null),
            ],
        );
    }

    /** Ad-hoc run from the textarea. */
    public function run(): void
    {
        $this->gate();
        $this->issueRun($this->command);
    }

    /** Run a saved runbook (its command + the current target selection). */
    public function runRunbook(string $uuid): void
    {
        $this->gate();
        $runbook = Runbook::where('uuid', $uuid)->firstOrFail();
        $this->issueRun($runbook->command);
    }

    #[On('commandRun')]
    public function execute(string $confirmToken, CommandRunner $runner): void
    {
        $this->gate();

        try {
            $payload = ConfirmToken::consume($confirmToken, 'commandRun');
        } catch (InvalidConfirmToken) {
            return; // forged / replayed / direct-bypass — no-op (the modal already audited command.run)
        }

        $servers = Server::whereIn('id', $payload['params']['serverIds'])->get();
        $this->results = $runner->run($payload['params']['command'], $servers);

        $ok = count(array_filter($this->results, fn ($r) => $r['ok']));
        $this->dispatch('notify', message: __('commands.ran', ['ok' => $ok, 'total' => count($this->results)]));
    }

    public function createRunbook(): void
    {
        $this->gate();

        $data = $this->validate([
            'runbookName' => ['required', 'string', 'max:80', Rule::unique('runbooks', 'name')],
            'runbookCommand' => ['required', 'string', 'max:4000'],
        ]);

        $runbook = Runbook::create(['name' => $data['runbookName'], 'command' => $data['runbookCommand']]);
        $this->audit('runbook.create', $runbook->name);
        $this->reset('runbookName', 'runbookCommand');
        $this->dispatch('notify', message: __('commands.runbook_created', ['name' => $runbook->name]));
    }

    public function confirmDeleteRunbook(string $uuid): void
    {
        $this->gate();
        $runbook = Runbook::where('uuid', $uuid)->firstOrFail();

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('commands.delete_heading'),
                'body' => __('commands.delete_body', ['name' => $runbook->name]),
                'confirmLabel' => __('common.delete'),
                'danger' => true,
                'icon' => 'trash',
                'notify' => __('commands.runbook_deleted', ['name' => $runbook->name]),
                'token' => ConfirmToken::issue('runbookDeleted', ['uuid' => $runbook->uuid], 'runbook.delete', $runbook->name, null),
            ],
        );
    }

    #[On('runbookDeleted')]
    public function deleteRunbook(string $confirmToken): void
    {
        $this->gate();

        try {
            $payload = ConfirmToken::consume($confirmToken, 'runbookDeleted');
        } catch (InvalidConfirmToken) {
            return;
        }

        Runbook::where('uuid', $payload['params']['uuid'])->first()?->delete();
    }

    private function audit(string $action, string $target): void
    {
        AuditEvent::create([
            'user_id' => Auth::id(),
            'actor' => Auth::user()?->name ?? 'system',
            'action' => $action,
            'target' => $target,
            'ip' => request()->ip(),
        ]);
    }

    public function render(): View
    {
        return view('livewire.commands.index', [
            'runbooks' => Runbook::orderBy('name')->get(),
            'groups' => ServerGroup::orderBy('name')->get(['uuid', 'name']),
            'servers' => Server::orderBy('name')->get(['uuid', 'name']),
        ])->title($this->title());
    }
}
