<?php

namespace Storeplugin\PDFInvoice\Framework\Utils;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\Config;

/**
 * Content Security Policy
 *
 * Manages Content Security Policy (CSP) headers for enhanced security.
 */
class ContentSecurityPolicy
{
    /**
     * Whether CSP is enabled
     *
     * @var bool
     */
    private static $enabled = true;

    /**
     * CSP directives
     *
     * @var array
     */
    private static $directives = [];

    /**
     * Whether to use report-only mode
     *
     * @var bool
     */
    private static $reportOnly = false;

    /**
     * Initialize CSP
     */
    public static function init(): void
    {
        // Check if CSP is disabled
        if (Config::isCspDisabled()) {
            self::$enabled = false;
            return;
        }

        // Set default directives for WordPress admin
        self::setDefaultDirectives();

        // Add action to send CSP headers
        \add_action('admin_init', [__CLASS__, 'sendHeaders']);
    }

    /**
     * Set default CSP directives for WordPress admin
     */
    private static function setDefaultDirectives(): void
    {
        self::$directives = [
            // Default fallback
            'default-src' => ["'self'"],

            // Scripts: Allow only local plugin/admin scripts plus inline/eval required by WordPress.
            'script-src' => [
                "'self'",
                "'unsafe-inline'", // Required for WordPress inline scripts
                "'unsafe-eval'",   // Required for some WordPress features
            ],

            // Styles: Allow only local/admin styles and inline CSS required by WordPress.
            'style-src' => [
                "'self'",
                "'unsafe-inline'", // Required for WordPress admin styles
            ],

            // Images: Allow local/data images plus remote images already used by WordPress admin.
            'img-src' => [
                "'self'",
                'data:',
                'https:',
                'http:', // Some WP features may load http images
            ],

            // Fonts: Allow local and data URI fonts used by WordPress admin.
            'font-src' => [
                "'self'",
                'data:',
            ],

            // AJAX and fetch requests
            'connect-src' => [
                "'self'",
            ],

            // Media (audio/video)
            'media-src' => [
                "'self'",
            ],

            // Frames (iframes)
            'frame-src' => [
                "'self'",
            ],

            // Object, embed, applet
            'object-src' => [
                "'none'",
            ],

            // Base URI restriction
            'base-uri' => [
                "'self'",
            ],

            // Form actions
            'form-action' => [
                "'self'",
            ],

            // Frame ancestors (clickjacking protection)
            'frame-ancestors' => [
                "'self'",
            ],

            // Upgrade insecure requests (optional, may break some features)
            // 'upgrade-insecure-requests' => true,
        ];

        // Allow filtering of directives
        self::$directives = \apply_filters('nxtpdf_csp_directives', self::$directives);
    }

    /**
     * Send CSP headers
     */
    public static function sendHeaders(): void
    {
        if (!self::$enabled) {
            return;
        }

        // Only send CSP headers in admin area for plugin pages
        if (!\is_admin()) {
            return;
        }

        // Check if this is a plugin admin page
        if (!self::isPluginPage()) {
            return;
        }

        // Don't send headers if already sent
        if (headers_sent()) {
            return;
        }

        $headerName = self::$reportOnly
            ? 'Content-Security-Policy-Report-Only'
            : 'Content-Security-Policy';

        $headerValue = self::buildHeaderValue();

        if (!empty($headerValue)) {
            header($headerName . ': ' . $headerValue);
        }
    }

    /**
     * Build CSP header value from directives
     *
     * @return string
     */
    private static function buildHeaderValue(): string
    {
        $policies = [];

        foreach (self::$directives as $directive => $values) {
            if ($values === true) {
                // Boolean directive (e.g., upgrade-insecure-requests)
                $policies[] = $directive;
            } elseif (is_array($values) && !empty($values)) {
                // Directive with sources
                $policies[] = $directive . ' ' . implode(' ', $values);
            } elseif (is_string($values)) {
                // String directive
                $policies[] = $directive . ' ' . $values;
            }
        }

        return implode('; ', $policies);
    }

    /**
     * Check if current page is a plugin page
     *
     * @return bool
     */
    private static function isPluginPage(): bool
    {
        global $plugin_page;

        // Check if it's a registered plugin page
        if (!empty($plugin_page) && strpos($plugin_page, Config::prefix()) !== false) {
            return true;
        }

        // Check $_GET parameter
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading current admin page slug from the URL to determine if this is a plugin page; no state change.
        if (isset($_GET['page']) && strpos(\sanitize_text_field(\wp_unslash($_GET['page'])), Config::prefix()) !== false) {
            return true;
        }

        return false;
    }

    /**
     * Add a source to a directive
     *
     * @param string $directive The directive name
     * @param string $source    The source to add
     */
    public static function addSource(string $directive, string $source): void
    {
        if (!isset(self::$directives[$directive])) {
            self::$directives[$directive] = [];
        }

        if (!is_array(self::$directives[$directive])) {
            return;
        }

        if (!in_array($source, self::$directives[$directive])) {
            self::$directives[$directive][] = $source;
        }
    }

    /**
     * Remove a source from a directive
     *
     * @param string $directive The directive name
     * @param string $source    The source to remove
     */
    public static function removeSource(string $directive, string $source): void
    {
        if (!isset(self::$directives[$directive]) || !is_array(self::$directives[$directive])) {
            return;
        }

        $key = array_search($source, self::$directives[$directive]);
        if ($key !== false) {
            unset(self::$directives[$directive][$key]);
            self::$directives[$directive] = array_values(self::$directives[$directive]);
        }
    }

    /**
     * Set a directive
     *
     * @param string       $directive The directive name
     * @param array|string $value     The directive value
     */
    public static function setDirective(string $directive, $value): void
    {
        self::$directives[$directive] = $value;
    }

    /**
     * Remove a directive
     *
     * @param string $directive The directive name
     */
    public static function removeDirective(string $directive): void
    {
        unset(self::$directives[$directive]);
    }

    /**
     * Enable report-only mode
     *
     * In this mode, violations are reported but not enforced
     *
     * @param string $reportUri Optional URI to send violation reports to
     */
    public static function enableReportOnly(string $reportUri = ''): void
    {
        self::$reportOnly = true;

        if (!empty($reportUri)) {
            self::$directives['report-uri'] = $reportUri;
        }
    }

    /**
     * Disable report-only mode (enforce CSP)
     */
    public static function disableReportOnly(): void
    {
        self::$reportOnly = false;
        unset(self::$directives['report-uri']);
    }

    /**
     * Enable CSP
     */
    public static function enable(): void
    {
        self::$enabled = true;
    }

    /**
     * Disable CSP
     */
    public static function disable(): void
    {
        self::$enabled = false;
    }

    /**
     * Get current CSP configuration
     *
     * @return array
     */
    public static function getConfiguration(): array
    {
        return [
            'enabled' => self::$enabled,
            'report_only' => self::$reportOnly,
            'directives' => self::$directives,
            'header_value' => self::buildHeaderValue(),
        ];
    }

    /**
     * Generate a nonce for inline scripts
     *
     * Note: This should be used sparingly. Prefer external scripts when possible.
     *
     * @return string
     */
    public static function generateNonce(): string
    {
        static $nonce = null;

        if ($nonce === null) {
            $nonce = base64_encode(\wp_generate_password(32, false));
        }

        return $nonce;
    }

    /**
     * Add nonce to script-src directive
     *
     * @param string $nonce The nonce value
     */
    public static function addScriptNonce(string $nonce): void
    {
        self::addSource('script-src', "'nonce-{$nonce}'");
    }
}
