<?php

namespace App\Livewire\Audit;

use App\Models\AuditEvent;
use App\Models\Setting;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Livewire\WithPagination;

#[Layout('layouts.app')]
class Index extends Component
{
    use WithPagination;

    /** Audit entries per page — keeps a long history navigable (page is deep-linked via ?page=). */
    private const PER_PAGE = 25;

    /** Freitext-Filter über Akteur / Aktion / Ziel / Server. */
    public string $q = '';

    /**
     * Aufbewahrungsdauer in Tagen (Formularfeld). Leer/0 = unbegrenzt; ein
     * positiver Wert N löscht beim täglichen Prune Einträge älter als N Tage.
     */
    public ?string $retentionDays = null;

    public function mount(): void
    {
        // Empty/0 means "keep forever" — surface that as a blank field, not "0".
        $stored = (int) Setting::get('audit_retention_days', '0');
        $this->retentionDays = $stored > 0 ? (string) $stored : null;
    }

    /** A changed search must restart at page 1 — never land on an out-of-range page. */
    public function updatedQ(): void
    {
        $this->resetPage();
    }

    /** Persist the retention policy, audit the change and notify the operator. */
    public function saveRetention(): void
    {
        // Panel-wide policy mutation (a short retention prunes audit history) — admin only. Viewing
        // the audit log stays open to everyone; only changing the retention window is gated.
        abort_unless(auth()->user()?->can('manage-panel'), 403);

        $validated = $this->validate([
            'retentionDays' => ['nullable', 'integer', 'min:1', 'max:3650'],
        ]);

        $days = $validated['retentionDays'] !== null && $validated['retentionDays'] !== ''
            ? (int) $validated['retentionDays']
            : 0;

        Setting::put('audit_retention_days', $days > 0 ? (string) $days : null);
        $this->retentionDays = $days > 0 ? (string) $days : null;

        AuditEvent::create([
            'user_id' => Auth::id(),
            'actor' => Auth::user()?->name ?? 'system',
            'action' => 'audit.retention_set',
            'target' => 'audit_retention_days='.$days,
            'ip' => request()->ip(),
        ]);

        $this->dispatch('notify', message: __('audit.retention_saved_notify'));
    }

    /** Effective retention in days (0 = unlimited) for the policy summary. */
    protected function retentionPolicy(): int
    {
        return $this->retentionDays !== null && $this->retentionDays !== ''
            ? (int) $this->retentionDays
            : 0;
    }

    /**
     * Audit events, newest first, optionally filtered. The filter runs in SQL (so it spans the whole
     * history, not just one page) and matches actor / action code / target / server name — plus the
     * READABLE action labels: a term is translated to the action codes whose localized label contains
     * it, so searching "Endpoint geändert" still finds wg.set-endpoint entries.
     */
    protected function eventsQuery(): Builder
    {
        $query = AuditEvent::with('server')->latest();

        $needle = trim($this->q);
        if ($needle === '') {
            return $query;
        }

        $lower = mb_strtolower($needle);
        $like = '%'.$lower.'%';
        $codes = array_keys(array_filter(
            (array) trans('audit.actions'),
            fn ($label): bool => is_string($label) && str_contains(mb_strtolower($label), $lower),
        ));

        return $query->where(function (Builder $w) use ($like, $codes): void {
            $w->whereRaw('LOWER(actor) LIKE ?', [$like])
                ->orWhereRaw('LOWER(action) LIKE ?', [$like])
                ->orWhereRaw('LOWER(target) LIKE ?', [$like])
                ->orWhereHas('server', fn (Builder $s) => $s->whereRaw('LOWER(name) LIKE ?', [$like]));
            if ($codes !== []) {
                $w->orWhereIn('action', $codes);
            }
        });
    }

    /**
     * Page numbers to render, with "…" gaps for long histories (1 … 4 5 6 … 20).
     *
     * @return array<int, int|string>
     */
    protected function pageWindow(int $current, int $total): array
    {
        if ($total <= 7) {
            return range(1, max(1, $total));
        }

        $pages = [1];
        $from = max(2, $current - 1);
        $to = min($total - 1, $current + 1);
        if ($from > 2) {
            $pages[] = '…';
        }
        for ($i = $from; $i <= $to; $i++) {
            $pages[] = $i;
        }
        if ($to < $total - 1) {
            $pages[] = '…';
        }
        $pages[] = $total;

        return $pages;
    }

    public function render()
    {
        /** @var LengthAwarePaginator $events */
        $events = $this->eventsQuery()->paginate(self::PER_PAGE);

        return view('livewire.audit.index', [
            'events' => $events,
            'pageWindow' => $this->pageWindow($events->currentPage(), $events->lastPage()),
            'retentionPolicy' => $this->retentionPolicy(),
        ])->title(__('audit.title'));
    }
}
