<?php

declare(strict_types=1);

namespace App\Core;

use PDO;
use SessionHandlerInterface;

/**
 * DatabaseSessionHandler
 *
 * Stores PHP sessions in MySQL instead of local files, so sessions
 * survive an app server restart/redeploy and work correctly if the app
 * is ever load-balanced across more than one server. Registered by
 * Session::start() when config/session.php has 'driver' => 'database'.
 */
final class DatabaseSessionHandler implements SessionHandlerInterface
{
    public function __construct(private readonly int $lifetimeMinutes)
    {
    }

    public function open(string $path, string $name): bool
    {
        return true;
    }

    public function close(): bool
    {
        return true;
    }

    public function read(string $id): string|false
    {
        $stmt = Database::connection()->prepare(
            'SELECT payload FROM sessions WHERE id = :id AND last_activity > :cutoff LIMIT 1'
        );
        $stmt->execute([
            'id' => $id,
            'cutoff' => time() - ($this->lifetimeMinutes * 60),
        ]);

        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ? base64_decode($row['payload']) : '';
    }

    public function write(string $id, string $data): bool
    {
        $userId = $_SESSION['user_id'] ?? null;

        $stmt = Database::connection()->prepare(
            'INSERT INTO sessions (id, user_id, ip_address, user_agent, payload, last_activity)
             VALUES (:id, :user_id, :ip, :ua, :payload, :activity)
             ON DUPLICATE KEY UPDATE
                user_id = VALUES(user_id),
                ip_address = VALUES(ip_address),
                user_agent = VALUES(user_agent),
                payload = VALUES(payload),
                last_activity = VALUES(last_activity)'
        );

        return $stmt->execute([
            'id' => $id,
            'user_id' => $userId,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
            'ua' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
            // Session payloads can contain arbitrary bytes; base64 keeps
            // them safe to store in a text column regardless of charset.
            'payload' => base64_encode($data),
            'activity' => time(),
        ]);
    }

    public function destroy(string $id): bool
    {
        $stmt = Database::connection()->prepare('DELETE FROM sessions WHERE id = :id');

        return $stmt->execute(['id' => $id]);
    }

    public function gc(int $max_lifetime): int|false
    {
        $stmt = Database::connection()->prepare('DELETE FROM sessions WHERE last_activity < :cutoff');
        $stmt->execute(['cutoff' => time() - $max_lifetime]);

        return $stmt->rowCount();
    }
}
