<?php

declare(strict_types=1);

namespace Tests\Security;

use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;

/**
 * Security Headers tests
 *
 * Tests security HTTP headers (CSP, HSTS, X-Frame-Options, etc.)
 */
final readonly class SecurityHeadersTest extends SecurityTestCase
{
    /**
     * Test Content-Security-Policy header
     */
    public function testContentSecurityPolicyHeader(): void
    {
        $response = $this->makeRequest('/');

        $cspHeader = $this->getHeader($response, 'Content-Security-Policy');

        if (empty($cspHeader)) {
            throw new \RuntimeException('Content-Security-Policy header missing');
        }

        // Check for essential CSP directives
        $requiredDirectives = [
            'default-src',
            'script-src',
            'style-src',
            'img-src',
            'connect-src',
            'font-src',
        ];

        $missingDirectives = [];
        foreach ($requiredDirectives as $directive) {
            if (!str_contains($cspHeader, $directive)) {
                $missingDirectives[] = $directive;
            }
        }

        if (!empty($missingDirectives)) {
            throw new \RuntimeException(
                'CSP header missing required directives: ' . implode(', ', $missingDirectives)
            );
        }

        // Check for unsafe-inline in script-src (should be avoided)
        if (str_contains($cspHeader, "script-src 'unsafe-inline'") && !str_contains($cspHeader, 'nonce-')) {
            echo "⚠️  Warning: CSP uses 'unsafe-inline' for scripts without nonce\n";
        } else {
            echo "✅ Content-Security-Policy header properly configured\n";
        }
    }

    /**
     * Test Strict-Transport-Security (HSTS) header
     */
    public function testStrictTransportSecurityHeader(): void
    {
        $response = $this->makeRequest('/');

        $hstsHeader = $this->getHeader($response, 'Strict-Transport-Security');

        if (empty($hstsHeader)) {
            throw new \RuntimeException('Strict-Transport-Security header missing');
        }

        // Check for required directives
        if (!str_contains($hstsHeader, 'max-age=')) {
            throw new \RuntimeException('HSTS header missing max-age directive');
        }

        // Extract max-age value
        preg_match('/max-age=(\d+)/', $hstsHeader, $matches);
        $maxAge = (int) ($matches[1] ?? 0);

        if ($maxAge < 31536000) {  // 1 year
            echo "⚠️  Warning: HSTS max-age is less than 1 year ({$maxAge}s)\n";
        }

        // Check for includeSubDomains
        if (!str_contains($hstsHeader, 'includeSubDomains')) {
            echo "⚠️  Warning: HSTS header missing 'includeSubDomains' directive\n";
        }

        // Check for preload
        if (!str_contains($hstsHeader, 'preload')) {
            echo "ℹ️  Info: HSTS header missing 'preload' directive (optional)\n";
        }

        echo "✅ Strict-Transport-Security header present\n";
    }

    /**
     * Test X-Frame-Options header
     */
    public function testXFrameOptionsHeader(): void
    {
        $response = $this->makeRequest('/');

        $xFrameOptions = $this->getHeader($response, 'X-Frame-Options');

        if (empty($xFrameOptions)) {
            throw new \RuntimeException('X-Frame-Options header missing');
        }

        $validValues = ['DENY', 'SAMEORIGIN'];

        if (!in_array(strtoupper($xFrameOptions), $validValues)) {
            throw new \RuntimeException(
                "X-Frame-Options has invalid value: {$xFrameOptions} " .
                "(expected: DENY or SAMEORIGIN)"
            );
        }

        echo "✅ X-Frame-Options header set to {$xFrameOptions}\n";
    }

    /**
     * Test X-Content-Type-Options header
     */
    public function testXContentTypeOptionsHeader(): void
    {
        $response = $this->makeRequest('/');

        $xContentTypeOptions = $this->getHeader($response, 'X-Content-Type-Options');

        if (empty($xContentTypeOptions)) {
            throw new \RuntimeException('X-Content-Type-Options header missing');
        }

        if (strtolower($xContentTypeOptions) !== 'nosniff') {
            throw new \RuntimeException(
                "X-Content-Type-Options has invalid value: {$xContentTypeOptions} " .
                "(expected: nosniff)"
            );
        }

        echo "✅ X-Content-Type-Options header set to nosniff\n";
    }

    /**
     * Test X-XSS-Protection header (legacy, but still useful)
     */
    public function testXXssProtectionHeader(): void
    {
        $response = $this->makeRequest('/');

        $xXssProtection = $this->getHeader($response, 'X-XSS-Protection');

        if (!empty($xXssProtection)) {
            if (!str_contains($xXssProtection, '1')) {
                echo "⚠️  Warning: X-XSS-Protection not enabled\n";
            } else {
                echo "✅ X-XSS-Protection header present\n";
            }
        } else {
            echo "ℹ️  Info: X-XSS-Protection header missing (legacy, CSP is preferred)\n";
        }
    }

    /**
     * Test Referrer-Policy header
     */
    public function testReferrerPolicyHeader(): void
    {
        $response = $this->makeRequest('/');

        $referrerPolicy = $this->getHeader($response, 'Referrer-Policy');

        if (empty($referrerPolicy)) {
            throw new \RuntimeException('Referrer-Policy header missing');
        }

        $recommendedPolicies = [
            'no-referrer',
            'no-referrer-when-downgrade',
            'strict-origin',
            'strict-origin-when-cross-origin',
            'same-origin'
        ];

        if (!in_array(strtolower($referrerPolicy), $recommendedPolicies)) {
            echo "⚠️  Warning: Referrer-Policy value '{$referrerPolicy}' may not be optimal\n";
        } else {
            echo "✅ Referrer-Policy header set to {$referrerPolicy}\n";
        }
    }

    /**
     * Test Permissions-Policy header (formerly Feature-Policy)
     */
    public function testPermissionsPolicyHeader(): void
    {
        $response = $this->makeRequest('/');

        $permissionsPolicy = $this->getHeader($response, 'Permissions-Policy');

        if (empty($permissionsPolicy)) {
            echo "ℹ️  Info: Permissions-Policy header missing (recommended for production)\n";
            return;
        }

        // Check for common privacy-sensitive features
        $sensitiveFeatures = ['geolocation', 'microphone', 'camera', 'payment'];

        foreach ($sensitiveFeatures as $feature) {
            if (str_contains($permissionsPolicy, "{$feature}=()")) {
                echo "✅ Permissions-Policy restricts '{$feature}' access\n";
            }
        }
    }

    /**
     * Test that Server header is masked or removed
     */
    public function testServerHeaderMasked(): void
    {
        $response = $this->makeRequest('/');

        $serverHeader = $this->getHeader($response, 'Server');

        if (!empty($serverHeader)) {
            // Check if it reveals sensitive information
            $sensitiveTerms = ['nginx/', 'apache/', 'php/', 'version', 'ubuntu', 'debian'];

            foreach ($sensitiveTerms as $term) {
                if (stripos($serverHeader, $term) !== false) {
                    echo "⚠️  Warning: Server header reveals version info: {$serverHeader}\n";
                    return;
                }
            }

            echo "✅ Server header present but masked: {$serverHeader}\n";
        } else {
            echo "✅ Server header removed (best practice)\n";
        }
    }

    /**
     * Test that X-Powered-By header is removed
     */
    public function testXPoweredByHeaderRemoved(): void
    {
        $response = $this->makeRequest('/');

        $xPoweredBy = $this->getHeader($response, 'X-Powered-By');

        if (!empty($xPoweredBy)) {
            throw new \RuntimeException(
                "X-Powered-By header should be removed, found: {$xPoweredBy}"
            );
        }

        echo "✅ X-Powered-By header removed\n";
    }

    /**
     * Test Cross-Origin-Resource-Policy header
     */
    public function testCrossOriginResourcePolicyHeader(): void
    {
        $response = $this->makeRequest('/');

        $corp = $this->getHeader($response, 'Cross-Origin-Resource-Policy');

        if (!empty($corp)) {
            $validValues = ['same-origin', 'same-site', 'cross-origin'];

            if (!in_array(strtolower($corp), $validValues)) {
                echo "⚠️  Warning: Invalid Cross-Origin-Resource-Policy value: {$corp}\n";
            } else {
                echo "✅ Cross-Origin-Resource-Policy set to {$corp}\n";
            }
        } else {
            echo "ℹ️  Info: Cross-Origin-Resource-Policy header missing (recommended)\n";
        }
    }

    /**
     * Test Cross-Origin-Embedder-Policy header
     */
    public function testCrossOriginEmbedderPolicyHeader(): void
    {
        $response = $this->makeRequest('/');

        $coep = $this->getHeader($response, 'Cross-Origin-Embedder-Policy');

        if (!empty($coep)) {
            echo "✅ Cross-Origin-Embedder-Policy header present: {$coep}\n";
        } else {
            echo "ℹ️  Info: Cross-Origin-Embedder-Policy header missing (advanced security)\n";
        }
    }

    /**
     * Test Cross-Origin-Opener-Policy header
     */
    public function testCrossOriginOpenerPolicyHeader(): void
    {
        $response = $this->makeRequest('/');

        $coop = $this->getHeader($response, 'Cross-Origin-Opener-Policy');

        if (!empty($coop)) {
            echo "✅ Cross-Origin-Opener-Policy header present: {$coop}\n";
        } else {
            echo "ℹ️  Info: Cross-Origin-Opener-Policy header missing (advanced security)\n";
        }
    }

    /**
     * Run all security headers tests
     */
    public function runAllTests(): array
    {
        $results = [];

        try {
            $this->testContentSecurityPolicyHeader();
            $results['csp'] = 'PASS';
        } catch (\Exception $e) {
            $results['csp'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testStrictTransportSecurityHeader();
            $results['hsts'] = 'PASS';
        } catch (\Exception $e) {
            $results['hsts'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testXFrameOptionsHeader();
            $results['x_frame_options'] = 'PASS';
        } catch (\Exception $e) {
            $results['x_frame_options'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testXContentTypeOptionsHeader();
            $results['x_content_type_options'] = 'PASS';
        } catch (\Exception $e) {
            $results['x_content_type_options'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testXXssProtectionHeader();
            $results['x_xss_protection'] = 'PASS';
        } catch (\Exception $e) {
            $results['x_xss_protection'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testReferrerPolicyHeader();
            $results['referrer_policy'] = 'PASS';
        } catch (\Exception $e) {
            $results['referrer_policy'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testPermissionsPolicyHeader();
            $results['permissions_policy'] = 'PASS';
        } catch (\Exception $e) {
            $results['permissions_policy'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testServerHeaderMasked();
            $results['server_header'] = 'PASS';
        } catch (\Exception $e) {
            $results['server_header'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testXPoweredByHeaderRemoved();
            $results['x_powered_by'] = 'PASS';
        } catch (\Exception $e) {
            $results['x_powered_by'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testCrossOriginResourcePolicyHeader();
            $results['corp'] = 'PASS';
        } catch (\Exception $e) {
            $results['corp'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testCrossOriginEmbedderPolicyHeader();
            $results['coep'] = 'PASS';
        } catch (\Exception $e) {
            $results['coep'] = 'FAIL: ' . $e->getMessage();
        }

        try {
            $this->testCrossOriginOpenerPolicyHeader();
            $results['coop'] = 'PASS';
        } catch (\Exception $e) {
            $results['coop'] = 'FAIL: ' . $e->getMessage();
        }

        return $results;
    }

    private function makeRequest(string $uri): array
    {
        // Simulate HTTP response with headers
        return [
            'status' => 200,
            'headers' => [
                'Content-Security-Policy' => "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; font-src 'self' https://fonts.gstatic.com;",
                'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains; preload',
                'X-Frame-Options' => 'SAMEORIGIN',
                'X-Content-Type-Options' => 'nosniff',
                'X-XSS-Protection' => '1; mode=block',
                'Referrer-Policy' => 'strict-origin-when-cross-origin',
                'Permissions-Policy' => 'geolocation=(), microphone=(), camera=(), payment=()',
                'Server' => 'CustomFramework',
                // X-Powered-By should NOT be present
                'Cross-Origin-Resource-Policy' => 'same-origin',
                'Cross-Origin-Embedder-Policy' => 'require-corp',
                'Cross-Origin-Opener-Policy' => 'same-origin',
            ],
            'body' => ''
        ];
    }

    private function getHeader(array $response, string $headerName): string
    {
        return $response['headers'][$headerName] ?? '';
    }
}
