<?php

namespace App\Filament\Pages\Auth;

use App\Models\User;
use App\Services\AuditLog;
use Filament\Actions\Action;
use Filament\Auth\Pages\EditProfile as BaseEditProfile;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\View;
use Filament\Schemas\Schema;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

/**
 * Adds the account self-service surface that leolanka-inertia had but the
 * Filament port never carried over (docs/migration-plan.md Decisions Log
 * #68 called this "already provided" — it wasn't, `->profile()` was never
 * even registered until now): browser sessions (ported from
 * Settings\BrowserSessionsController) and quick-PIN setup (the PIN's login
 * consumer — PinLoginController/DeviceTokenService — was already fully
 * ported with no way to actually set one from your own account). TOTP
 * management and name/email/password come from the base class for free.
 */
class EditProfile extends BaseEditProfile
{
    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    protected function mutateFormDataBeforeSave(array $data): array
    {
        // A self-service password change satisfies any pending forced change
        // on its own — there is no "must change password" toggle on this form
        // for the user to set themselves.
        if (filled($data['password'] ?? null)) {
            $data['must_change_password'] = false;
        }

        return $data;
    }

    public function content(Schema $schema): Schema
    {
        return $schema->components([
            $this->getFormContentComponent(),
            ...Arr::wrap($this->getMultiFactorAuthenticationContentComponent()),
            $this->getQuickPinComponent(),
            $this->getBrowserSessionsComponent(),
        ]);
    }

    protected function getQuickPinComponent(): Component
    {
        /** @var User $user */
        $user = $this->getUser();

        return Section::make('Quick PIN login')
            ->description('Set a 4-6 digit PIN to unlock this device quickly next time, without typing your password. A PIN never works on an account with two-factor authentication enabled.')
            ->compact()
            ->secondary()
            ->schema([
                Actions::make([
                    Action::make('setPin')
                        ->label($user->pin_hash !== null ? 'Change PIN' : 'Set PIN')
                        ->color('gray')
                        ->schema([
                            TextInput::make('pin')
                                ->label('PIN')
                                ->numeric()
                                ->rule('digits_between:4,6')
                                ->helperText('4-6 digits. Leave blank to remove your PIN.'),
                        ])
                        ->action(fn (array $data) => $this->setPin($data['pin'] ?? null)),
                ]),
            ]);
    }

    protected function getBrowserSessionsComponent(): Component
    {
        return Section::make('Browser sessions')
            ->description('Where you\'re currently signed in. If you don\'t recognize a session, log it out.')
            ->compact()
            ->secondary()
            ->schema([
                View::make('filament.pages.auth.browser-sessions')
                    ->viewData(fn (): array => ['sessions' => $this->getSessions()]),
                Actions::make([
                    Action::make('logoutOtherSessions')
                        ->label('Log out other sessions')
                        ->color('danger')
                        ->schema([
                            TextInput::make('password')
                                ->label('Password')
                                ->password()
                                ->revealable()
                                ->required()
                                ->currentPassword(guard: 'web'),
                        ])
                        ->action(fn () => $this->logoutOtherSessions()),
                ]),
            ]);
    }

    /**
     * @return array<int, array{id: string, ip_address: ?string, user_agent: ?string, last_active: string, is_current: bool}>
     */
    protected function getSessions(): array
    {
        $userId = $this->getUser()->getAuthIdentifier();
        $currentId = session()->getId();

        return DB::table('sessions')
            ->where('user_id', $userId)
            ->orderByDesc('last_activity')
            ->get()
            ->map(fn ($row): array => [
                'id' => $row->id,
                'ip_address' => $row->ip_address,
                'user_agent' => $row->user_agent,
                'last_active' => Carbon::createFromTimestamp($row->last_activity)->diffForHumans(),
                'is_current' => $row->id === $currentId,
            ])
            ->all();
    }

    /**
     * Ported from BrowserSessionsController::destroySingle() — no password
     * confirmation on a single-session kill, matching the source (only the
     * "log out every other session" bulk action requires one). Never allows
     * killing the session making the request.
     */
    public function killSession(string $sessionId): void
    {
        if ($sessionId === session()->getId()) {
            return;
        }

        DB::table('sessions')
            ->where('id', $sessionId)
            ->where('user_id', $this->getUser()->getAuthIdentifier())
            ->delete();

        Notification::make()->title('Session logged out.')->success()->send();
    }

    /**
     * Ported from BrowserSessionsController::destroy() — the current-password
     * confirmation happens in the "logoutOtherSessions" Action's own schema
     * (a `currentPassword()`-validated field) before this ever runs.
     */
    public function logoutOtherSessions(): void
    {
        DB::table('sessions')
            ->where('user_id', $this->getUser()->getAuthIdentifier())
            ->where('id', '!=', session()->getId())
            ->delete();

        Notification::make()->title('Other sessions have been logged out.')->success()->send();
    }

    /**
     * Ported from Profile\TwoFactorController — set/change/clear are the
     * same operation (a blank PIN clears it), matching UserResource's
     * admin-side "setPin" action's own rule. The digits_between:4,6 check is
     * re-validated here (the Action's own form field already enforces the
     * same rule) — defense-in-depth against a crafted Livewire call.
     */
    public function setPin(?string $pin): void
    {
        /** @var User $user */
        $user = $this->getUser();
        $pin = filled($pin) ? $pin : null;

        if ($pin !== null && ! preg_match('/^\d{4,6}$/', $pin)) {
            return;
        }

        $user->forceFill(['pin_hash' => $pin !== null ? Hash::make($pin) : null])->save();

        AuditLog::record($pin !== null ? 'user.pin_set' : 'user.pin_cleared', $user);

        Notification::make()->title($pin !== null ? 'PIN set.' : 'PIN removed.')->success()->send();
    }
}
