<?php

namespace App\Livewire\Certs;

use App\Models\AuditEvent;
use App\Models\CertEndpoint;
use App\Services\CertService;
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;

/**
 * TLS certificate-expiry monitoring for configured endpoints. `operate`-gated (route + mount +
 * per-method). The expiry check is native (a PHP SSL stream in CertService — no SSH/shell, so the
 * host is never interpolated into a command); the lazy scan is per-endpoint guarded. Deleting an
 * endpoint goes through a signed ConfirmToken + R5 modal.
 */
#[Layout('layouts.app')]
class Index extends Component
{
    // Add-endpoint form
    public string $label = '';

    public string $host = '';

    public int $port = 443;

    /** @var array<string, array<string, mixed>> endpoint uuid → check result (+ 'status') */
    public array $checks = [];

    public bool $ready = false;

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

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

    private function gate(): void
    {
        abort_unless(Auth::user()?->can('operate'), 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 scan(CertService $certs): void
    {
        $this->gate();
        $this->checks = [];

        foreach (CertEndpoint::orderBy('host')->get() as $ep) {
            try {
                $res = $certs->check($ep->host, $ep->port);
                $res['status'] = $res['ok'] ? $certs->status($res['daysLeft'] ?? null) : 'error';
                $this->checks[$ep->uuid] = $res;
            } catch (Throwable $e) {
                $this->checks[$ep->uuid] = ['ok' => false, 'status' => 'error', 'error' => $e->getMessage()];
            }
        }

        $this->ready = true;
    }

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

        $data = $this->validate([
            'label' => ['nullable', 'string', 'max:60'],
            // A hostname or an IP — never a scheme/path/shell char (it's used in ssl://host:port).
            'host' => ['required', 'string', 'max:253', 'regex:/^[A-Za-z0-9]([A-Za-z0-9.\-]*[A-Za-z0-9])?$/'],
            'port' => ['required', 'integer', 'min:1', 'max:65535'],
        ]);

        CertEndpoint::create($data);
        $this->audit('cert.endpoint_add', $data['host'].':'.$data['port']);
        $this->reset('label', 'host');
        $this->port = 443;
        $this->dispatch('notify', message: __('certs.added', ['host' => $data['host']]));
    }

    public function confirmDeleteEndpoint(string $uuid): void
    {
        $this->gate();
        $ep = CertEndpoint::where('uuid', $uuid)->firstOrFail();

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('certs.delete_heading'),
                'body' => __('certs.delete_body', ['host' => $ep->host]),
                'confirmLabel' => __('common.delete'),
                'danger' => true,
                'icon' => 'trash',
                'notify' => __('certs.deleted', ['host' => $ep->host]),
                'token' => ConfirmToken::issue('certEndpointDeleted', ['uuid' => $ep->uuid], 'cert.endpoint_delete', $ep->host.':'.$ep->port, null),
            ],
        );
    }

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

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

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

    public function render(): View
    {
        return view('livewire.certs.index', [
            'endpoints' => CertEndpoint::orderBy('host')->get(),
        ])->title($this->title());
    }
}
