<?php

declare(strict_types=1);

namespace App\Models;

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


class UserSession implements \SessionHandlerInterface
{
    protected $db;
    private const SESSION_LIFETIME = 7; // 7 days
    private ?string $tableName;
    private ?string $sessionName;
    private string $savePath = '';
    private string $token;

    public function __construct(string $sessionName = 'customer')
    {
        $this->db = Database::connectDB();
        $this->sessionName = \SESSION[$sessionName];
        $this->savePath = \APPROOT . '/Temp';
        $this->tableName = 'zoop_session';

        $this->register();
    }

    private function getSessionFilePath($sessionId): string
    {
        return $this->savePath . '/sess_' . $sessionId;
    }

    private function removeSession($sessionId): void
    {
        $sessionFile = $this->savePath . '/sess_' . $sessionId;
        if (file_exists($sessionFile)) {
            // delete session log from the database
            $this->db->query("DELETE FROM {$this->tableName} WHERE id = ?", $sessionId);
            unlink($sessionFile); // Remove the session file
        }
    }

    public function open($savePath, $sessionName): bool
    {
        // Create the session directory if it doesn't exist
        if (!is_dir($this->savePath)) {
            mkdir($this->savePath, 0700, true);
        }

        return true;
    }

    public function close(): bool
    {
        // Nothing to do here since we're using PDO
        return true;
    }

    public function read($sessionId): string
    {
        $sessionFile = $this->getSessionFilePath($sessionId);

        // Read the session data from the file
        return file_exists($sessionFile) ? (string) file_get_contents($sessionFile) : '';
    }

    public function write($sessionId, string $data): bool
    {
        $expiry = $this->calculateExpiry();
        $sessionFile = $this->getSessionFilePath($sessionId);
        $status = true;
        if(!empty($data)){
            $status = file_put_contents($sessionFile, $data, LOCK_EX) !== false;

            // Update the session data in the database
            $sessionData = $this->get($this->sessionName);
            if (!empty($sessionData)) {
                
                $this->db->query(
                    "INSERT IGNORE INTO {$this->tableName}",
                    [
                        'id' => $sessionId,
                        'userId' => $sessionData['id'] ?? null,
                        'userType' => $sessionData['type'],
                        'deviceId' => $this->getDeviceId(),
                        'user_agent' => $_SERVER['HTTP_USER_AGENT'],
                        'expiry' => $expiry,
                    ]
                    
                );
            }
        }

        

        return $status;
    }

    public function destroy($sessionId): bool
    {
        $sessionFile = $this->getSessionFilePath($sessionId);

        // Delete the session file
        if (file_exists($sessionFile)) {
            $this->removeSession($sessionId);
        }

        return true;
    }

    public function gc($maxLifetime): int|false
    {
        foreach (glob($this->savePath . '/sess_*') as $file) {
            if (filemtime($file) + $maxLifetime < time() && file_exists($file)) {
                $this->db->query("DELETE FROM {$this->tableName} WHERE expiry < NOW() - INTERVAL ? SECOND", $maxLifetime);
                unlink($file);
            }
        }

        return $maxLifetime;
    }

    public function calculateExpiry(): string
    {
        return DateTime::from(\DATENOW)->modifyClone('+' . self::SESSION_LIFETIME . ' days')->format('Y-m-d H:i:s');
    }

    /**
     * @param string $key
     * @return mixed
     */
    public function get(string $key)
    {
        if ($this->has($key)) {
            return $_SESSION[$key];
        }

        return null;
    }

    /**
     * @param string $key
     * @param mixed $value
     * @return SessionManager
     */
    public function set(string $key, $value)
    {
        $_SESSION[$key] = $value;
        return $this;
    }

    public function setValue(string $identifier, string $key, $value)
    {
        $_SESSION[$identifier][$key] = $value;
        return $this;
    }

    public function remove(string $key)
    {
        if ($this->has($key)) {
            unset($_SESSION[$key]);
        }
    }

    public function clear(): void
    {
        session_unset();
    }

    public function has(string $key): bool
    {
        return array_key_exists($key, $_SESSION);
    }

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

    public static function getSessionId(): string
    {
        return session_id();
    }

    public function logoutDevice($sessionId)
    {
        $this->removeSession($sessionId); // Remove the specific session
        $this->regenerateID(true); // Regenerate the session ID
    }

    public function logoutAllDevices(string $userId, string $userType)
    {
        $sessions = $this->getSessions("userId = ? AND userType = ?", $userId, $userType); // Fetch all sessions

        foreach ($sessions as $session) {
            $this->removeSession($session->id); // Remove sessions for the specific user
        }

        return true;
    }

    protected function deleteSessions($where, ...$params)
    {
        $stmt = $this->db->query("DELETE FROM {$this->tableName} WHERE {$where}", ...$params);
        return $stmt->rowCount();
    }

    public function getSessions($where, ...$params)
    {
        $stmt = $this->db->query("SELECT * FROM {$this->tableName} WHERE {$where}", ...$params);
        return $stmt->fetchAll();
    }

    public static function regenerateID(bool $deleteOldSession = false): bool
    {
        return session_regenerate_id($deleteOldSession);
    }

    private function register(): void
    {
        session_write_close();
        session_set_save_handler($this, true);

        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }
    }

    private function generateCsrf()
    {
        if (empty($this->get('csrf_token'))) {
            $this->set('csrf_token', bin2hex(random_bytes(32)));
        }

        return $this->get('csrf_token');
    }

    public function generateCSRFToken(): string
    {
        return $this->generateCsrf();
    }

    public function destroyToken(): void
    {
        $this->remove('csrf_token');
    }

    public function verifyCSRFToken(string $token): bool
    {
        if (empty($token)) {
            return false;
        }

        // Compare the tokens
        $csrfToken = $this->get('csrf_token');
        $compareTokens = isset($csrfToken) && hash_equals($csrfToken, $token);
        return $compareTokens;
    }

    public function generateCSRFTokenField(): string
    {
        $token = $this->generateCsrf();
        return '<input class="d-none" type="hidden" name="csrf_token" value="' . $token . '">';
    }
}
