<?php
declare(strict_types=1);

namespace App\Util;

use App\Lib\Database;
use App\Helpers\DateTime;

/**
 * Class SessionManager
 * Manages sessions in the application.
 *
 * @package App\Models
 */
class DeviceManager
{
    protected $db;
    private const SESSION_LIFETIME = \JWT_EXPIRE; // 7 days
    private static $tableName = '_session';
    /**
     * Constructor for the class.
     *
     * Initializes the database connection by calling the `connectDB()` method of the `Database` class.
     *
     * @return void
     */
    public function __construct()
    {
        $this->db = Database::connectDB();
    }

    /**
     * Starts a PHP session if it is not already started.
     *
     * This function checks the current session status using the `session_status()`
     * function and starts a new session if the status is `PHP_SESSION_NONE`.
     *
     * @throws None
     * @return void
     */
    public function startSession(): void
    {
        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }
    }

    /**
     * Ends the current PHP session and optionally removes it from the session storage.
     *
     * @param bool $forgetSession Whether to remove the session from the session storage. Default is false.
     * @return void
     */
    public function endSession(bool $forgetSession = false): void
    {
        $sessionId = $this->sessionId();

        if ($forgetSession) {
            $this->removeSession($sessionId);
        }

        //session_destroy();
    }

    /**
     * Returns the ID of the current PHP session.
     *
     * @return string The session ID.
     */
    public function sessionId()
    {
        return session_id();
    }

    /**
     * Checks if the session for a given user is active.
     *
     * @param mixed $user The user for whom to check the session. If null, checks the session for the current user.
     * @return bool Returns true if the session is active, false otherwise.
     */
    public function isSessionActive($user = null)
    {
        return isset($_SESSION[SESSION[$user]]);
    }

    // public function blockSession($sessionId)
    // {

    // }

    // public function isSessionBlocked() : bool
    // {
    //     return true;
    // }

    /**
     * Checks if the current session has expired.
     *
     * @return bool Returns true if the session has expired, false otherwise.
     */
    public function isSessionExpired() : bool
    {
        $sessionId = $this->sessionId();
        $data = $this->getSessionById($sessionId);
        return DateTime::hasPassed($data->expiry);
    }

    /**
     * Deletes all expired sessions.
     *
     * This function retrieves all sessions that have expired and deletes them from the session storage.
     *
     * @return void
     */
    public function deleteExpiredSessions(): void
    {
        $sessionArray = $this->getAllSession("expiry < ? ", \DATENOW);
        foreach ($sessionArray as $session) {
            $this->removeSession($session->sessionId);
        }
    }

    /**
     * Deletes a device from the session storage.
     *
     * @param string $id The ID of the device to delete.
     * @return void
     */
    public function deleteDevice(string $id): void
    {
        $data = $this->getSessionByDevice($id);
        $this->removeSession($data->sessionId);
    }

    /**
     * Logs out all devices associated with a user.
     *
     * This function retrieves all sessions associated with a user and deletes them from the session storage.
     *
     * @param string $userId The ID of the user.
     * @param string $userType The type of the user.
     * @return void
     */
    public function logoutAllDevices(string $userId, string $userType): void
    {
        $deviceArray = $this->getAllSession("userId = ? AND userType = ?", $userId, $userType);
        foreach ($deviceArray as $device) {
            $this->deleteDevice($device->deviceId);
        }
    }

    /**
     * Adds a device to the session storage.
     *
     * This function inserts a new device into the session storage with the provided user ID, user type, device ID,
     * user agent, user IP, and expiry time. If the device ID already exists in the session storage, the function
     * will ignore the insertion and not update the existing record.
     *
     * @param int $userId The ID of the user.
     * @param string $userType The type of the user.
     * @param string $deviceId The ID of the device.
     * @param mixed $expiry The expiry time of the session in days. Default is the value of the SESSION_LIFETIME constant.
     * @throws None
     * @return void
     */
    public function addDevice(int $userId, string $userType, $expiry = null): void
    {
        $sessionId = $this->sessionId();
        $expire = $expiry ?? $this::SESSION_LIFETIME + \time();
        $expireTime = DateTime::from($expire)->format('Y-m-d H:i:s');

        if(empty($this->getSessionById($sessionId)) && !empty($userType) && !empty($sessionId)){
            $this->db->query(
                "INSERT INTO {$this::$tableName}",
                [
                    'sessionId' => $sessionId,
                    'userId' => $userId,
                    'userType' => $userType,
                    'deviceId' => $this->getDeviceId(),
                    'userAgent' => $_SERVER['HTTP_USER_AGENT'],
                    'userIp' => $_SERVER['REMOTE_ADDR'],
                    'expiry' => $expireTime,
                ]
            );
        }

    }

    public static function getDeviceId(): string
    {
        return hash('sha256', $_SERVER['HTTP_USER_AGENT']);
    }

    /**
     * Returns the path to the session file for the given session ID.
     *
     * @param string $sessionId The ID of the session.
     * @return string The path to the session file.
     */
    private function getSessionPath(string $sessionId): string
    {
        return \APPROOT . '/Temp/sess_' . $sessionId;
    }

    /**
     * Removes a session from the session storage and the database.
     *
     * This function deletes the session file and removes the corresponding session log from the database.
     *
     * @param string $sessionId The ID of the session to be removed.
     * @throws None
     * @return void
     */
    public function removeSession($sessionId): void
    {
        $sessionFile = $this->getSessionPath($sessionId);

        if (file_exists($sessionFile)) {
            unlink($sessionFile); // Remove the session file
        }
        // delete session log from the database
        $this->db->query("DELETE FROM {$this::$tableName} WHERE sessionId = ? LIMIT 1", $sessionId);
    }

    /**
     * Retrieves all sessions from the database that match the given condition.
     *
     * @param string $where The condition to filter the sessions by.
     * @param mixed ...$params The parameters to bind to the query.
     * @return array An array of session data.
     */
    private function getAllSession($where, ...$params)
    {
        $stmt = $this->db->query("SELECT * FROM {$this::$tableName} WHERE {$where}", ...$params);
        return $stmt->fetchAll();
    }

    private function getSessionByDevice(string $device)
    {
        $stmt = $this->db->query("SELECT * FROM {$this::$tableName} WHERE deviceId = ? LIMIT 1", $device);
        return $stmt->fetch();
    }

    private function getSessionById(string $sessionId)
    {
        $stmt = $this->db->query("SELECT * FROM {$this::$tableName} WHERE sessionId = ? LIMIT 1", $sessionId);
        return $stmt->fetch();
    }
}
