<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;

/**
 * SessionService
 *
 * Built on top of the `sessions` table (App\Core\DatabaseSessionHandler
 * already writes user_id/ip_address/user_agent/last_activity there for
 * every request — see Phase 1). This service is the read/management
 * layer on top: listing a user's active sessions for the Profile page's
 * "Active Sessions" panel, revoking a specific one (or "log out
 * everywhere else"), and flagging when a user has more concurrent
 * sessions than config('security.sessions.max_concurrent') — a signal
 * surfaced as a security notification, not an automatic forced logout.
 */
final class SessionService
{
    public function activeSessionsFor(int $userId, int $activeWithinMinutes = 120): array
    {
        $stmt = Database::connection()->prepare(
            'SELECT id, ip_address, user_agent, last_activity
             FROM sessions
             WHERE user_id = :id AND last_activity > :cutoff
             ORDER BY last_activity DESC'
        );
        $stmt->execute([
            'id' => $userId,
            'cutoff' => time() - ($activeWithinMinutes * 60),
        ]);

        $currentId = session_id();

        return array_map(function (array $row) use ($currentId) {
            $row['is_current'] = $row['id'] === $currentId;
            $row['last_activity_human'] = date('M j, Y g:i A', (int) $row['last_activity']);

            return $row;
        }, $stmt->fetchAll());
    }

    public function countActiveSessions(int $userId, int $activeWithinMinutes = 120): int
    {
        $stmt = Database::connection()->prepare(
            'SELECT COUNT(*) FROM sessions WHERE user_id = :id AND last_activity > :cutoff'
        );
        $stmt->execute(['id' => $userId, 'cutoff' => time() - ($activeWithinMinutes * 60)]);

        return (int) $stmt->fetchColumn();
    }

    public function hasMultipleSessions(int $userId): bool
    {
        return $this->countActiveSessions($userId) > 1;
    }

    public function exceedsConcurrentLimit(int $userId): bool
    {
        return $this->countActiveSessions($userId) > config('security.sessions.max_concurrent');
    }

    /** Revoke one specific session by id — used for "log out this device." Refuses to revoke the caller's own current session (use logout for that). */
    public function revoke(int $userId, string $sessionId): bool
    {
        if ($sessionId === session_id()) {
            return false;
        }

        $stmt = Database::connection()->prepare(
            'DELETE FROM sessions WHERE id = :session_id AND user_id = :user_id'
        );

        return $stmt->execute(['session_id' => $sessionId, 'user_id' => $userId]);
    }

    /** Revoke every session for this user EXCEPT the current one — "log out all other devices." */
    public function revokeAllExceptCurrent(int $userId): int
    {
        $stmt = Database::connection()->prepare(
            'DELETE FROM sessions WHERE user_id = :user_id AND id != :current'
        );
        $stmt->execute(['user_id' => $userId, 'current' => session_id()]);

        return $stmt->rowCount();
    }

    /** Revoke every session for a user, including the current one — used by admins deactivating an account. */
    public function revokeAll(int $userId): int
    {
        $stmt = Database::connection()->prepare('DELETE FROM sessions WHERE user_id = :user_id');
        $stmt->execute(['user_id' => $userId]);

        return $stmt->rowCount();
    }
}
