<?php

namespace App\Livewire\Modals;

use App\Models\AuditEvent;
use App\Models\Server;
use App\Services\Fail2banService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Livewire\Attributes\Locked;
use LivewireUI\Modal\ModalComponent;
use Throwable;

/**
 * Scrollable list of fail2ban jails + their currently-banned IPs, lifted out of the
 * servers/show panel so a long ban list never stretches the page. mount() only stores
 * the serverId so the modal opens INSTANTLY; load() (fired by wire:init, behind a
 * skeleton) does the live jail-status read via Fail2banService (the SAME service the
 * page uses). unban() calls Fail2banService::unban (the SAME path as Show::unbanIp),
 * audits the success, refreshes this modal's list, and dispatches `fail2banChanged`
 * so the page panel re-reads too.
 */
class Fail2banBans extends ModalComponent
{
    #[Locked]
    public int $serverId;

    /** Jail status rows (name/currentlyBanned/currentlyFailed/bannedIps) for the list. */
    public array $jails = [];

    /** True once the live status was read (distinguishes "no bans" from "not loaded"). */
    public bool $loaded = false;

    public ?string $error = null;

    public function mount(int $serverId): void
    {
        abort_unless(auth()->user()?->can('manage-network'), 403);

        // Store the id ONLY so the modal opens instantly; the SSH read happens in load().
        $this->serverId = $serverId;
    }

    public static function modalMaxWidth(): string
    {
        return 'lg';
    }

    /** Lazy: the SSH read runs after the modal renders (wire:init), behind a skeleton. */
    public function load(Fail2banService $fail2ban): void
    {
        $this->loadJails($fail2ban);
    }

    /** Read the live jail/banned-IP status (same Fail2banService::status the page calls). */
    private function loadJails(Fail2banService $fail2ban): void
    {
        $server = Server::find($this->serverId);
        if (! $server) {
            $this->error = __('common.server_not_found');

            return;
        }

        try {
            $status = $fail2ban->status($server);
            $this->jails = $status['jails'] ?? [];
            $this->error = ! empty($status['readError']) ? __('servers.fail2ban_read_error') : null;
            $this->loaded = true;
        } catch (Throwable $e) {
            $this->error = Str::limit($e->getMessage(), 120);
        }
    }

    /**
     * Unban one IP — mirrors Show::unbanIp: call Fail2banService::unban, audit the
     * success, refresh this list, and dispatch `fail2banChanged` so the page panel
     * re-reads its own fail2ban state.
     */
    public function unbanIp(string $jail, string $ip, Fail2banService $fail2ban): void
    {
        abort_unless(auth()->user()?->can('manage-network'), 403);

        $server = Server::find($this->serverId);
        if (! $server) {
            $this->error = __('common.server_not_found');

            return;
        }

        try {
            $res = $fail2ban->unban($server, $jail, $ip);
        } catch (Throwable $e) {
            $this->dispatch('notify', message: __('servers.notify_unban_failed', ['error' => Str::limit($e->getMessage(), 90)]));

            return;
        }
        if (! $res['ok']) {
            $this->dispatch('notify', message: __('servers.notify_unban_failed', ['error' => Str::limit($res['output'] ?: __('servers.error_unknown'), 90)]));

            return;
        }

        AuditEvent::create([
            'user_id' => Auth::id(),
            'server_id' => $server->id,
            'actor' => Auth::user()?->name ?? 'system',
            'action' => 'fail2ban.unban',
            'target' => $ip.' · '.$jail.' · '.$server->name,
            'ip' => request()->ip(),
        ]);

        $this->dispatch('notify', message: __('servers.notify_ip_unbanned', ['ip' => $ip]));

        // Re-read this modal's list, then tell the page panel to re-pull its own state.
        $this->loadJails($fail2ban);
        $this->dispatch('fail2banChanged');
    }

    public function render()
    {
        return view('livewire.modals.fail2ban-bans');
    }
}
