<?php

namespace App\Services;

use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;

/**
 * Database-session logic for the session-management UI (no Livewire here).
 *
 * The `database` session driver makes every login row in the `sessions` table
 * enumerable per user, so we can list devices and revoke them. Revoking a user
 * "everywhere" also rotates their `remember_token` — otherwise a stolen
 * remember-me cookie would silently re-create a session.
 */
class SessionService
{
    /**
     * The user's sessions, newest activity first, decorated for the UI.
     *
     * Only id/ip/user-agent/last-activity are exposed — never the payload (it
     * holds the serialized session, including other users' data is impossible
     * here but the payload still contains CSRF tokens etc.).
     *
     * @return array<int, array{id: string, ip: ?string, userAgentShort: string, lastActivity: Carbon, isCurrent: bool}>
     */
    public function forUser(User $user): array
    {
        $currentId = Session::getId();

        return DB::table('sessions')
            ->where('user_id', $user->id)
            ->orderByDesc('last_activity')
            ->get(['id', 'ip_address', 'user_agent', 'last_activity'])
            ->map(fn ($row) => [
                'id' => $row->id,
                'ip' => $row->ip_address,
                'userAgentShort' => $this->shortenUserAgent($row->user_agent),
                'lastActivity' => Carbon::createFromTimestamp((int) $row->last_activity),
                'isCurrent' => $row->id === $currentId,
            ])
            ->all();
    }

    /**
     * Sign the user out of every device EXCEPT the current one. The current
     * session stays valid (nothing rotated for self), so the operator keeps
     * working uninterrupted.
     */
    public function logoutOtherDevices(User $user): void
    {
        DB::table('sessions')
            ->where('user_id', $user->id)
            ->where('id', '!=', Session::getId())
            ->delete();
    }

    /**
     * Kill ALL of this user's sessions and rotate their remember_token, so a
     * stolen remember-me cookie can no longer resurrect a session. Used by the
     * (separate) multi-user admin feature; not wired to a per-row button here.
     */
    public function logoutUserEverywhere(User $user): void
    {
        DB::table('sessions')->where('user_id', $user->id)->delete();

        $user->forceFill(['remember_token' => Str::random(60)])->save();
    }

    /**
     * Global logout: truncate every session and rotate every user's
     * remember_token. Destructive — logs out the current operator too. The
     * caller (Livewire) is responsible for the R5 confirm, audit, and the
     * redirect to login that must follow.
     */
    public function logoutEveryone(): void
    {
        DB::table('sessions')->truncate();

        User::query()->eachById(function (User $user) {
            $user->forceFill(['remember_token' => Str::random(60)])->save();
        });
    }

    /**
     * Collapse a raw User-Agent header into a short "Browser · OS" label for the
     * list. Best-effort string sniffing (no extra dependency); falls back to
     * "unknown" copy when the header is empty or unrecognized.
     */
    private function shortenUserAgent(?string $userAgent): string
    {
        if ($userAgent === null || trim($userAgent) === '') {
            return __('sessions.ua_unknown');
        }

        $browser = match (true) {
            str_contains($userAgent, 'Edg/') => 'Edge',
            str_contains($userAgent, 'OPR/') || str_contains($userAgent, 'Opera') => 'Opera',
            str_contains($userAgent, 'Firefox') => 'Firefox',
            str_contains($userAgent, 'Chrome') => 'Chrome',
            str_contains($userAgent, 'Safari') => 'Safari',
            default => null,
        };

        $os = match (true) {
            str_contains($userAgent, 'Windows') => 'Windows',
            str_contains($userAgent, 'Android') => 'Android',
            str_contains($userAgent, 'iPhone') || str_contains($userAgent, 'iPad') => 'iOS',
            str_contains($userAgent, 'Mac OS') || str_contains($userAgent, 'Macintosh') => 'macOS',
            str_contains($userAgent, 'Linux') => 'Linux',
            default => null,
        };

        $label = trim(implode(' · ', array_filter([$browser, $os])));

        // Nothing matched → show a trimmed prefix of the raw header so the row is
        // still distinguishable, rather than a useless "unknown".
        return $label !== '' ? $label : Str::limit($userAgent, 40);
    }
}
