<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Service;

defined( 'ABSPATH' ) || exit;

/**
 * Session Service for managing cart sessions
 *
 * Provides session management for frontend cart functionality.
 * Automatically detects context (browser, CLI, tests, cron) and only sets cookies
 * when appropriate.
 */
class SessionService
{
    public const CART_SESSION_NAME = 'bflex_cart_session_id';

    private string $sessionId;

    /**
     * Initialize session
     *
     * Gets existing session from cookie or generates a new one.
     * Validates the session ID format to prevent injection attacks.
     * In non-browser contexts (CLI, tests, cron), session is temporary
     * and only exists for the current request.
     *
     * @return string Session ID
     */
    public function init(): string
    {
        if (!empty($_COOKIE[self::CART_SESSION_NAME])) {
            $cookieValue = sanitize_text_field(wp_unslash($_COOKIE[self::CART_SESSION_NAME]));
            if ($this->validateSessionId($cookieValue)) {
                $this->sessionId = $cookieValue;
                return $this->sessionId;
            }
        }

        $this->sessionId = $this->generateSessionId();
        return $this->sessionId;
    }

    /**
     * Generate new session ID
     *
     * Creates a cryptographically secure session ID and attempts to set a cookie if the context allows.
     * In CLI, test, or cron contexts, the session ID is generated but no cookie is set
     * (session will only be valid for the current request).
     *
     * @return string Session ID
     */
    private function generateSessionId(): string
    {
        $sessionId = 'bflex_cart_' . bin2hex(random_bytes(32));

        // Only attempt to set cookie in browser contexts
        if ($this->canSetCookies()) {
            setcookie(self::CART_SESSION_NAME, $sessionId, [
                'expires'  => time() + 3600 * 24 * 365,
                'path'     => '/',
                'secure'   => is_ssl(),
                'httponly' => true,
                'samesite' => 'Lax',
            ]);
        }

        return $sessionId;
    }

    /**
     * Validate session ID format
     *
     * Ensures the session ID matches the expected format (bflex_cart_ + 64 hex chars).
     * This prevents injection of arbitrary values via cookies.
     *
     * @param string $sessionId Session ID to validate
     * @return bool True if valid, false otherwise
     */
    private function validateSessionId(string $sessionId): bool
    {
        return preg_match('/^bflex_cart_[a-f0-9]{64}$/', $sessionId) === 1;
    }

    /**
     * Check if cookies can be set in the current context
     *
     * Cookies should only be set for browser-based frontend requests.
     * They should NOT be set in:
     * - PHPUnit tests (headers already sent)
     * - WP-CLI commands
     * - WordPress cron jobs
     * - PHP CLI mode
     * - After headers are sent
     *
     * @return bool True if cookies can be set, false otherwise
     */
    private function canSetCookies(): bool
    {
        // Headers already sent (tests, CLI with output, late code execution)
        if (headers_sent()) {
            return false;
        }

        // WP-CLI context
        if (defined('WP_CLI') && WP_CLI) {
            return false;
        }

        // WordPress Cron
        if (wp_doing_cron()) {
            return false;
        }

        // PHP CLI mode (fallback check if WP_CLI not defined)
        if (php_sapi_name() === 'cli') {
            return false;
        }

        return true;
    }

    /**
     * Get current session ID
     *
     * @return string Session ID
     */
    public function getSessionId(): string
    {
        return $this->sessionId;
    }
}
