<?php

namespace App\Livewire\Health;

use App\Models\AuditEvent;
use App\Models\HealthCheck;
use App\Services\HealthService;
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;
use Throwable;

/**
 * Uptime / health status board. `operate`-gated (route + mount + per-method). Probes run NATIVELY
 * (HealthService — Laravel HTTP client / a PHP TCP stream, no SSH/shell). The lazy scan is
 * per-check guarded. Deleting a check goes through a signed ConfirmToken + R5 modal.
 */
#[Layout('layouts.app')]
class Index extends Component
{
    // Add-check form
    public string $label = '';

    public string $type = 'http';

    public string $target = '';

    public ?int $port = null;

    /** @var array<string, array{ok:bool, latency:?int, detail:string}> check uuid → probe result */
    public array $results = [];

    public bool $ready = false;

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

    public function title(): string
    {
        return __('health.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(HealthService $health): void
    {
        $this->gate();
        $this->results = [];

        foreach (HealthCheck::orderBy('label')->orderBy('target')->get() as $check) {
            try {
                $this->results[$check->uuid] = $health->probe($check);
            } catch (Throwable $e) {
                $this->results[$check->uuid] = ['ok' => false, 'latency' => null, 'detail' => $e->getMessage()];
            }
        }

        $this->ready = true;
    }

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

        // HTTP → a real http(s) URL; TCP → a hostname/IP + a port. Neither is ever shelled.
        $rules = [
            'label' => ['nullable', 'string', 'max:60'],
            'type' => ['required', Rule::in(HealthCheck::TYPES)],
        ];
        if ($this->type === 'tcp') {
            $rules['target'] = ['required', 'string', 'max:253', 'regex:/^[A-Za-z0-9]([A-Za-z0-9.\-]*[A-Za-z0-9])?$/'];
            $rules['port'] = ['required', 'integer', 'min:1', 'max:65535'];
        } else {
            $rules['target'] = ['required', 'url:http,https', 'max:2048'];
            $rules['port'] = ['nullable'];
        }

        $data = $this->validate($rules);

        HealthCheck::create([
            'label' => $data['label'] ?? null,
            'type' => $data['type'],
            'target' => $data['target'],
            'port' => $this->type === 'tcp' ? $data['port'] : null,
        ]);

        $this->audit('health.check_add', $data['target']);
        $this->reset('label', 'target', 'port');
        $this->type = 'http';
        $this->dispatch('notify', message: __('health.added'));
    }

    public function confirmDeleteCheck(string $uuid): void
    {
        $this->gate();
        $check = HealthCheck::where('uuid', $uuid)->firstOrFail();

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('health.delete_heading'),
                'body' => __('health.delete_body', ['target' => $check->target]),
                'confirmLabel' => __('common.delete'),
                'danger' => true,
                'icon' => 'trash',
                'notify' => __('health.deleted'),
                'token' => ConfirmToken::issue('healthCheckDeleted', ['uuid' => $check->uuid], 'health.check_delete', $check->target, null),
            ],
        );
    }

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

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

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

    public function render(): View
    {
        $checks = HealthCheck::orderBy('label')->orderBy('target')->get();
        $up = count(array_filter($this->results, fn ($r) => $r['ok'] ?? false));

        return view('livewire.health.index', [
            'checks' => $checks,
            'up' => $up,
            'total' => count($this->results),
        ])->title($this->title());
    }
}
