<?php

namespace App\Models;

use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Traits\HasRoles;

/**
 * An operator: the owner of CluPilot, or a member of staff.
 *
 * Extends Authenticatable rather than App\Models\User — sharing the class would
 * put both groups back in one table, which is the thing this exists to end.
 *
 * `$guard_name` is what makes Spatie resolve roles against the `operator` guard.
 * Without it every role assignment would be checked against `web` and fail,
 * which is the failure mode to expect if roles suddenly stop matching.
 */
class Operator extends Authenticatable
{
    use HasFactory, HasRoles, HasUuid, Notifiable, TwoFactorAuthenticatable;

    /** Tells spatie/laravel-permission which guard this model's roles live under. */
    protected string $guard_name = 'operator';

    /** The six roles that grant access to the console. */
    /**
     * Die Rollen dieser Installation — aus der Datenbank, nicht aus einer
     * Liste im Code.
     *
     * Vorher stand sie hier als Konstante UND zweimal als
     * `in:Owner,Admin,…`-Regel in Admin\Settings. `Developer` war in der
     * Konstanten und in keiner der beiden Regeln: die Rolle existierte, und
     * das Einladen-Formular bot sie nie an. Seit Admin\Roles Rollen anlegen
     * kann, wäre jede feste Liste ohnehin schon beim ersten Anlegen falsch.
     *
     * @return array<int, string>
     */
    public static function operatorRoles(): array
    {
        return Role::query()
            ->where('guard_name', 'operator')
            ->orderBy('name')
            ->pluck('name')
            ->all();
    }

    protected $fillable = ['name', 'email', 'password', 'last_login_at', 'disabled_at'];

    protected $hidden = ['password', 'remember_token', 'two_factor_secret', 'two_factor_recovery_codes'];

    protected function casts(): array
    {
        return [
            'password' => 'hashed',
            'last_login_at' => 'datetime',
            'disabled_at' => 'datetime',
            'two_factor_confirmed_at' => 'datetime',
        ];
    }

    /**
     * Für Betreiber gibt es keinen Selbstbedienungs-Weg zum Passwort.
     *
     * Der Broker `operators` beherrscht `sendResetLink()`, und die Vorgabe aus
     * `CanResetPassword` verschickt dann Laravels eigene Benachrichtigung — mit
     * einem Link auf `password.reset`, die Seite des PORTALS, die gegen den
     * KUNDEN-Broker einlöst. Ein Betreiber-Token landete damit auf der
     * Kundenseite und liefe dort ins Leere: R21 an der unauffälligsten Stelle,
     * an der er brechen kann, und zwar still.
     *
     * Deshalb laut statt still. Die Einladung geht über
     * App\Livewire\Admin\Settings::inviteStaff(), die den Token selbst zieht
     * und ihre eigene Mail verschickt; eine Seite „Passwort vergessen" für
     * Betreiber gibt es (noch) nicht. Wer sie baut, überschreibt diese Methode
     * mit einer Mail, die auf `admin.*` zeigt — und löscht diesen Kommentar.
     */
    public function sendPasswordResetNotification(#[\SensitiveParameter] $token): void
    {
        throw new \RuntimeException(
            'Betreiber haben keinen Selbstbedienungs-Weg zum Passwort: Laravels Vorgabe verlinkt auf die Portal-Seite password.reset (R21).'
        );
    }

    /** True when this operator holds a role that reaches the console. */
    public function isOperator(): bool
    {
        return $this->hasAnyRole(self::operatorRoles());
    }

    /** A disabled operator keeps their record and loses their access. */
    public function isActive(): bool
    {
        return $this->disabled_at === null;
    }
}
