<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;

class VpnPeer extends Model
{
    use HasFactory;
    use HasUuids;
    use SoftDeletes;

    /** A tunnel is considered live if the last handshake is recent. WireGuard
     *  re-handshakes about every two minutes, so three is the first value that
     *  does not flap for an idle-but-connected peer. */
    public const ONLINE_AFTER_MINUTES = 3;

    /** A person's access. Requires an owner. */
    public const KIND_STAFF = 'staff';

    /** Registered by the host pipeline for a Proxmox host. Requires host_id. */
    public const KIND_HOST = 'host';

    /** Found on the interface without a match — adopted so it stays visible. */
    public const KIND_SYSTEM = 'system';

    protected $guarded = [];

    protected function casts(): array
    {
        return [
            'enabled' => 'boolean',
            'present' => 'boolean',
            'last_handshake_at' => 'datetime',
            'secret_purged_at' => 'datetime',
            'last_downloaded_at' => 'datetime',
            'observed_at' => 'datetime',
            'rx_bytes' => 'integer',
            'tx_bytes' => 'integer',
        ];
    }

    public function uniqueIds(): array
    {
        return ['uuid'];
    }

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

    public function creator(): BelongsTo
    {
        return $this->belongsTo(Operator::class, 'created_by');
    }

    /** The person this access belongs to (staff peers only). */
    public function owner(): BelongsTo
    {
        return $this->belongsTo(Operator::class, 'user_id');
    }

    public function hasStoredConfig(): bool
    {
        return $this->config_secret !== null;
    }

    /**
     * Overwrite the stored credential. Called on revocation: a tombstone that
     * still carries a usable private key is a liability, not a record.
     */
    public function purgeSecret(): void
    {
        if ($this->config_secret === null) {
            return;
        }

        $this->forceFill(['config_secret' => null, 'secret_purged_at' => now()])->saveQuietly();
    }

    public function isOnline(): bool
    {
        return $this->enabled
            && $this->present
            && $this->last_handshake_at !== null
            && $this->last_handshake_at->gt(now()->subMinutes(self::ONLINE_AFTER_MINUTES));
    }

    /**
     * blocked  — operator revoked it; it must not be on the hub
     * pending  — desired and observed state disagree; a job is still applying it
     * online   — configured and handshaking
     * idle     — configured, but no recent handshake
     */
    public function status(): string
    {
        if (! $this->enabled) {
            return $this->present ? 'pending' : 'blocked';
        }
        if (! $this->present) {
            return 'pending';
        }

        return $this->isOnline() ? 'online' : 'idle';
    }
}
