<?php

namespace App\Livewire\Servers;

use App\Models\AuditEvent;
use App\Models\Server;
use App\Models\ServerGroup;
use App\Support\Confirm\ConfirmToken;
use App\Support\Confirm\InvalidConfirmToken;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Component;

/**
 * Manage server groups (create / rename / recolour / delete + membership). Admin-only
 * (manage-fleet, matching server create/delete): the route mw gates it AND every mutating
 * method re-checks, since /livewire/update does not re-run route middleware. Every change is
 * audited; deletion goes through the signed, single-use ConfirmToken + the R5 confirm modal.
 */
#[Layout('layouts.app')]
class Groups extends Component
{
    /** New-group form. */
    public string $name = '';

    public string $color = 'accent';

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

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

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

    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 create(): void
    {
        $this->gate();

        $data = $this->validate([
            'name' => ['required', 'string', 'max:60', Rule::unique('server_groups', 'name')],
            'color' => ['required', Rule::in(ServerGroup::COLORS)],
        ]);

        $group = ServerGroup::create($data);
        $this->audit('group.create', $group->name);
        $this->reset('name');
        $this->color = 'accent';
        $this->dispatch('notify', message: __('groups.created', ['name' => $group->name]));
    }

    public function rename(string $uuid, string $name): void
    {
        $this->gate();
        $group = ServerGroup::where('uuid', $uuid)->firstOrFail();

        $name = trim($name);
        $validated = validator(
            ['name' => $name],
            ['name' => ['required', 'string', 'max:60', Rule::unique('server_groups', 'name')->ignore($group->id)]],
        )->validate();

        $group->update(['name' => $validated['name']]);
        $this->audit('group.update', $group->name);
    }

    public function setColor(string $uuid, string $color): void
    {
        $this->gate();
        if (! in_array($color, ServerGroup::COLORS, true)) {
            return; // saving() would coerce it anyway; refuse the bad token outright
        }
        $group = ServerGroup::where('uuid', $uuid)->firstOrFail();
        $group->update(['color' => $color]);
        $this->audit('group.update', $group->name);
    }

    /** Add or remove a server from a group (checkbox toggle in the assignment grid). */
    public function toggleMember(string $groupUuid, string $serverUuid): void
    {
        $this->gate();
        $group = ServerGroup::where('uuid', $groupUuid)->firstOrFail();
        $server = Server::where('uuid', $serverUuid)->firstOrFail();

        $group->servers()->toggle($server->id);
        $this->audit('group.members', $group->name);
    }

    public function confirmDelete(string $uuid): void
    {
        $this->gate();
        $group = ServerGroup::where('uuid', $uuid)->firstOrFail();

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('groups.delete_heading'),
                'body' => __('groups.delete_body', ['name' => $group->name]),
                'confirmLabel' => __('common.delete'),
                'danger' => true,
                'icon' => 'trash',
                'notify' => __('groups.deleted', ['name' => $group->name]),
                // The confirm modal writes the group.delete AuditEvent from this sealed descriptor,
                // so deleteGroup() must NOT audit again (avoids a double row).
                'token' => ConfirmToken::issue('groupConfirmed', ['uuid' => $group->uuid], 'group.delete', $group->name, null),
            ],
        );
    }

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

        try {
            $payload = ConfirmToken::consume($confirmToken, 'groupConfirmed');
        } catch (InvalidConfirmToken) {
            return; // forged / replayed / direct-bypass attempt — no-op
        }

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

        // NOT audited here — the confirm modal already wrote group.delete from the sealed token.
        $group->delete(); // pivot rows cascade; servers are untouched
    }

    public function render(): View
    {
        return view('livewire.servers.groups', [
            'groups' => ServerGroup::withCount('servers')->with('servers:id,uuid')->orderBy('name')->get(),
            'servers' => Server::orderBy('name')->get(['id', 'uuid', 'name']),
            'colors' => ServerGroup::COLORS,
        ])->title($this->title());
    }
}
