<?php

namespace App\Livewire\Alerts;

use App\Models\AlertIncident;
use App\Models\AlertRule;
use App\Models\AuditEvent;
use App\Models\Server;
use App\Models\ServerGroup;
use App\Models\Setting;
use App\Services\AlertNotifier;
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;

/**
 * Alert rules + notification channels + active incidents. Managing rules/channels =
 * manage-panel (admin): route mw + mount() + a per-method gate() (since /livewire/update
 * skips route mw). Viewing incidents is open (read-only). Rule deletion goes through the
 * signed single-use ConfirmToken + the R5 confirm modal.
 */
#[Layout('layouts.app')]
class Index extends Component
{
    // New-rule form
    public string $name = '';

    public string $metric = 'cpu';

    public string $comparator = 'gt';

    public float $threshold = 80; // float: `load` thresholds are fractional (e.g. 1.5)

    public string $scopeType = 'all';

    public ?string $scopeUuid = null; // group OR server uuid, resolved on create

    // Channels
    public string $emailTo = '';

    public string $webhooks = '';

    public function mount(): void
    {
        abort_unless(Auth::user()?->can('manage-panel'), 403);
        $this->emailTo = (string) Setting::get('alert_email_to', '');
        $this->webhooks = (string) Setting::get('alert_webhooks', '');
    }

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

    private function gate(): void
    {
        abort_unless(Auth::user()?->can('manage-panel'), 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 createRule(): void
    {
        $this->gate();

        $data = $this->validate([
            'name' => ['required', 'string', 'max:80'],
            'metric' => ['required', Rule::in(AlertRule::METRICS)],
            'comparator' => ['required', Rule::in(AlertRule::COMPARATORS)],
            'threshold' => ['required', 'numeric', 'min:0', 'max:10000'], // numeric: `load` can be fractional
            'scopeType' => ['required', Rule::in(AlertRule::SCOPES)],
        ]);

        // Resolve the scope target (a group/server uuid) to its id — never trust a client-sent id.
        $scopeId = null;
        if ($data['scopeType'] === 'group') {
            $scopeId = ServerGroup::where('uuid', $this->scopeUuid)->firstOrFail()->id;
        } elseif ($data['scopeType'] === 'server') {
            $scopeId = Server::where('uuid', $this->scopeUuid)->firstOrFail()->id;
        }

        $rule = AlertRule::create([
            'name' => $data['name'],
            'metric' => $data['metric'],
            'comparator' => $data['comparator'],
            'threshold' => $data['metric'] === 'offline' ? 0 : $data['threshold'],
            'scope_type' => $data['scopeType'],
            'scope_id' => $scopeId,
            'enabled' => true,
        ]);

        $this->audit('alert.rule_create', $rule->name);
        $this->reset('name', 'scopeUuid');
        $this->dispatch('notify', message: __('alerts.rule_created', ['name' => $rule->name]));
    }

    public function toggleRule(string $uuid): void
    {
        $this->gate();
        $rule = AlertRule::where('uuid', $uuid)->firstOrFail();
        $rule->update(['enabled' => ! $rule->enabled]);
        $this->audit('alert.rule_update', $rule->name);
    }

    public function confirmDeleteRule(string $uuid): void
    {
        $this->gate();
        $rule = AlertRule::where('uuid', $uuid)->firstOrFail();

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('alerts.delete_heading'),
                'body' => __('alerts.delete_body', ['name' => $rule->name]),
                'confirmLabel' => __('common.delete'),
                'danger' => true,
                'icon' => 'trash',
                'notify' => __('alerts.rule_deleted', ['name' => $rule->name]),
                'token' => ConfirmToken::issue('alertRuleDeleted', ['uuid' => $rule->uuid], 'alert.rule_delete', $rule->name, null),
            ],
        );
    }

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

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

        // NOT audited here — the confirm modal wrote alert.rule_delete from the sealed token.
        AlertRule::where('uuid', $payload['params']['uuid'])->first()?->delete(); // incidents cascade
    }

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

        // SSRF guard: refuse to store a webhook whose scheme isn't http(s) or whose host resolves to
        // an internal/loopback/private address. Checked here (immediate feedback) and again at send.
        foreach (preg_split('/[\r\n]+/', trim($this->webhooks)) ?: [] as $line) {
            $line = trim($line);
            if ($line !== '' && ! AlertNotifier::isSafeWebhookUrl($line)) {
                $this->addError('webhooks', __('alerts.webhook_invalid', ['url' => $line]));

                return;
            }
        }

        Setting::put('alert_email_to', trim($this->emailTo));
        Setting::put('alert_webhooks', trim($this->webhooks));
        $this->audit('alert.channels_update', __('alerts.channels'));
        $this->dispatch('notify', message: __('alerts.channels_saved'));
    }

    public function render(): View
    {
        return view('livewire.alerts.index', [
            'rules' => AlertRule::orderBy('name')->get(),
            'incidents' => AlertIncident::with(['rule', 'server'])->where('state', 'firing')->latest('started_at')->limit(50)->get(),
            'groups' => ServerGroup::orderBy('name')->get(['uuid', 'name']),
            'servers' => Server::orderBy('name')->get(['uuid', 'name']),
            // id → name maps so a rule's scope target renders without a per-rule query (no N+1).
            'groupNames' => ServerGroup::pluck('name', 'id'),
            'serverNames' => Server::pluck('name', 'id'),
            'metrics' => AlertRule::METRICS,
            'comparators' => AlertRule::COMPARATORS,
            'scopes' => AlertRule::SCOPES,
        ])->title($this->title());
    }
}
