<?php

namespace App\Models;

use App\Models\Concerns\HasUuid;
use App\Services\Security\HostFirewall;
use Database\Factories\SecurityBlockFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;

/**
 * Eine Sperre gegen eine Adresse — an genau einem Subjekt, einer Instanz oder
 * einem Host (nie beiden). Entsteht in `BlockAddress` auch dann, wenn
 * `HostFirewall::block()` selbst scheitert: der Datensatz IST die Sperre, die
 * Firewall-Menge ist nur ihr aktueller Abdruck (siehe HostFirewall).
 */
class SecurityBlock extends Model
{
    /** @use HasFactory<SecurityBlockFactory> */
    use HasFactory, HasUuid;

    protected $fillable = [
        'host_id', 'instance_id', 'ip', 'reason', 'attempts', 'strikes',
        'blocked_at', 'expires_at', 'released_at', 'released_by_type', 'released_by_id',
    ];

    protected function casts(): array
    {
        return [
            'attempts' => 'integer',
            'strikes' => 'integer',
            'blocked_at' => 'datetime',
            'expires_at' => 'datetime',
            'released_at' => 'datetime',
        ];
    }

    public function host(): BelongsTo
    {
        return $this->belongsTo(Host::class);
    }

    public function instance(): BelongsTo
    {
        return $this->belongsTo(Instance::class);
    }

    /** Wer vorzeitig aufgehoben hat — Operator oder Customer, wenn jemand. */
    public function releasedBy(): MorphTo
    {
        return $this->morphTo();
    }

    /** Was gerade gilt: noch nicht aufgehoben und noch nicht abgelaufen. */
    public function scopeActive(Builder $query): Builder
    {
        return $query->whereNull('released_at')->where('expires_at', '>', now());
    }

    /**
     * Hebt die Sperre vorzeitig auf — am Datensatz UND, wenn das Subjekt einen
     * erreichbaren Host hat, in dessen Firewall. `$by` ist null, wenn der
     * Kernel sie ohnehin schon fallen ließ (Ablauf) und niemand sie manuell
     * aufgehoben hat; sonst der Operator oder Customer, der den Knopf gedrückt
     * hat (R23-Modal auf der aufrufenden Seite, nicht hier).
     */
    /**
     * Hebt die Sperre auf — und sagt, ob die Regel wirklich gefallen ist.
     *
     * Der Datensatz wird IMMER zuerst gespeichert, auch wenn der Host gerade
     * nicht erreichbar ist: er ist die Sperre, die Firewall-Menge nur ihr
     * Abdruck (siehe Klassenkopf), und `reapplyActiveBlocks()` traegt beim
     * naechsten Lauf nur noch ein, was hier aktiv steht.
     *
     * Der Rueckgabewert ist deshalb NICHT „hat es geklappt", sondern „steht
     * die Firewall schon nach". Er wird gebraucht, weil `HostFirewall::apply()`
     * jeden Fehlschlag schluckt und nur ins Log meldet — ohne ihn faende der
     * Mensch am Bildschirm „Sperre aufgehoben." vor, waehrend die Adresse
     * weiter ausgesperrt ist und er sich das nicht erklaeren kann.
     */
    public function release(?Model $by): bool
    {
        $this->releasedBy()->associate($by);
        $this->released_at = now();
        $this->save();

        $host = $this->host ?? $this->instance?->host;

        if ($host === null) {
            return true; // Kein Host, keine Regel — nichts, was nachhinken koennte.
        }

        return app(HostFirewall::class)->release($host, $this->ip);
    }
}
