<?php
/**
 * Security Test Cases for CuliUptime PHP Agent
 *
 * Based on US004-agent-management.md and US002-http-monitoring.md
 * Tests security requirements: IP whitelist, HTTPS enforcement, header validation
 */

class AgentSecurityTest
{
    private $testResults = [];

    public function runAllTests()
    {
        echo "=== CuliUptime Agent Security Tests ===\n";

        $this->testIPWhitelistEnforcement();
        $this->testHTTPSRequirement();
        $this->testHeaderValidation();
        $this->testInvalidIPBlocking();
        $this->testMissingHeaderBlocking();
        $this->testLocalhostAccess();

        $this->printResults();
    }

    /**
     * Test Case: IP Whitelist Enforcement
     * US004 Requirement: IP whitelist access control validation
     */
    public function testIPWhitelistEnforcement()
    {
        $testName = "IP Whitelist Enforcement";

        // Test with allowed IP (127.0.0.1)
        $result1 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'localhost',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0'
        ]);

        // Test with disallowed IP
        $result2 = $this->simulateRequest([
            'REMOTE_ADDR' => '192.168.1.100',
            'HTTP_HOST' => 'example.com',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0'
        ]);

        $passed = !$result1['blocked'] && $result2['blocked'];
        $this->recordResult($testName, $passed,
            "Expected: localhost access allowed, external IP blocked. " .
            "Actual: localhost=" . ($result1['blocked'] ? 'blocked' : 'allowed') .
            ", external=" . ($result2['blocked'] ? 'blocked' : 'allowed')
        );
    }

    /**
     * Test Case: HTTPS Requirement
     * US004 Requirement: HTTPS enforcement for non-localhost requests
     */
    public function testHTTPSRequirement()
    {
        $testName = "HTTPS Requirement";

        // Test localhost HTTP (should be allowed)
        $result1 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'localhost',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0',
            'HTTPS' => 'off'
        ]);

        // Test external HTTP (should be blocked)
        $result2 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1', // Whitelisted IP but external host
            'HTTP_HOST' => 'agent.example.com',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0',
            'HTTPS' => 'off'
        ]);

        // Test external HTTPS (should be allowed if IP whitelisted)
        $result3 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'agent.example.com',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0',
            'HTTPS' => 'on'
        ]);

        $passed = !$result1['blocked'] && $result2['blocked'] && !$result3['blocked'];
        $this->recordResult($testName, $passed,
            "Expected: localhost HTTP allowed, external HTTP blocked, external HTTPS allowed. " .
            "Results: localhost_http=" . ($result1['blocked'] ? 'blocked' : 'allowed') .
            ", external_http=" . ($result2['blocked'] ? 'blocked' : 'allowed') .
            ", external_https=" . ($result3['blocked'] ? 'blocked' : 'allowed')
        );
    }

    /**
     * Test Case: Header Validation
     * US004 Requirement: X-Agent-Secret header validation
     */
    public function testHeaderValidation()
    {
        $testName = "Header Validation";

        // Test with correct header
        $result1 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'localhost',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0'
        ]);

        // Test with wrong header value
        $result2 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'localhost',
            'X-Agent-Secret' => 'wrong-secret'
        ]);

        // Test with missing header
        $result3 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'localhost'
            // No X-Agent-Secret header
        ]);

        $passed = !$result1['blocked'] && $result2['blocked'] && $result3['blocked'];
        $this->recordResult($testName, $passed,
            "Expected: correct header allowed, wrong/missing headers blocked. " .
            "Results: correct=" . ($result1['blocked'] ? 'blocked' : 'allowed') .
            ", wrong=" . ($result2['blocked'] ? 'blocked' : 'allowed') .
            ", missing=" . ($result3['blocked'] ? 'blocked' : 'allowed')
        );
    }

    /**
     * Test Case: Invalid IP Blocking
     * US004 Requirement: Block requests from non-whitelisted IPs
     */
    public function testInvalidIPBlocking()
    {
        $testName = "Invalid IP Blocking";

        $invalidIPs = ['192.168.1.1', '10.0.0.1', '172.16.0.1', '203.0.113.1'];
        $allBlocked = true;

        foreach ($invalidIPs as $ip) {
            $result = $this->simulateRequest([
                'REMOTE_ADDR' => $ip,
                'HTTP_HOST' => 'agent.example.com',
                'X-Agent-Secret' => 'culiops-healthcheck-1.0',
                'HTTPS' => 'on'
            ]);

            if (!$result['blocked']) {
                $allBlocked = false;
                break;
            }
        }

        $this->recordResult($testName, $allBlocked,
            "Expected: All non-whitelisted IPs blocked. Result: " .
            ($allBlocked ? "All blocked" : "Some allowed")
        );
    }

    /**
     * Test Case: Missing Header Blocking
     * US004 Requirement: Block requests without required headers
     */
    public function testMissingHeaderBlocking()
    {
        $testName = "Missing Header Blocking";

        $result = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'localhost',
            'HTTPS' => 'on'
            // Missing X-Agent-Secret header
        ]);

        $this->recordResult($testName, $result['blocked'],
            "Expected: Request blocked when X-Agent-Secret header missing. " .
            "Result: " . ($result['blocked'] ? 'blocked' : 'allowed')
        );
    }

    /**
     * Test Case: Localhost Access
     * US004 Requirement: Allow localhost/127.0.0.1 access
     */
    public function testLocalhostAccess()
    {
        $testName = "Localhost Access";

        // Test 127.0.0.1
        $result1 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => '127.0.0.1',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0'
        ]);

        // Test localhost hostname
        $result2 = $this->simulateRequest([
            'REMOTE_ADDR' => '127.0.0.1',
            'HTTP_HOST' => 'localhost',
            'X-Agent-Secret' => 'culiops-healthcheck-1.0'
        ]);

        $passed = !$result1['blocked'] && !$result2['blocked'];
        $this->recordResult($testName, $passed,
            "Expected: Both 127.0.0.1 and localhost access allowed. " .
            "Results: 127.0.0.1=" . ($result1['blocked'] ? 'blocked' : 'allowed') .
            ", localhost=" . ($result2['blocked'] ? 'blocked' : 'allowed')
        );
    }

    /**
     * Simulate a request to the agent with given environment variables
     */
    private function simulateRequest($env)
    {
        // Save current environment
        $originalEnv = [];
        foreach ($env as $key => $value) {
            $originalEnv[$key] = $_SERVER[$key] ?? null;
            $_SERVER[$key] = $value;
        }

        // Prepare headers array simulation
        $headers = [];
        if (isset($env['X-Agent-Secret'])) {
            $headers['X-Agent-Secret'] = $env['X-Agent-Secret'];
        }

        // Mock getallheaders() function behavior
        global $mockHeaders;
        $mockHeaders = $headers;

        ob_start();
        $result = ['blocked' => false, 'error' => '', 'output' => ''];

        try {
            // Test the security logic from agent.php
            $allowed_ips = ['127.0.0.1', 'localhost'];
            $allowed_headers = ['X-Agent-Secret' => 'culiops-healthcheck-1.0'];

            $client_ip = $_SERVER['REMOTE_ADDR'];
            $server_ip = $_SERVER['HTTP_HOST'] ?? gethostbyname(gethostname());

            // HTTPS check
            if (!in_array($server_ip, $allowed_ips) && (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off')) {
                $result['blocked'] = true;
                $result['error'] = 'HTTPS required';
            }
            // IP check
            elseif (!in_array($server_ip, $allowed_ips) && !in_array($client_ip, $allowed_ips)) {
                $result['blocked'] = true;
                $result['error'] = 'Access denied: Invalid IP address';
            }
            // Header check
            else {
                foreach ($allowed_headers as $header => $value) {
                    if (!isset($headers[$header]) || $headers[$header] !== $value) {
                        $result['blocked'] = true;
                        $result['error'] = 'Access denied: Invalid headers';
                        break;
                    }
                }
            }

        } catch (Exception $e) {
            $result['error'] = $e->getMessage();
        }

        $result['output'] = ob_get_clean();

        // Restore environment
        foreach ($originalEnv as $key => $value) {
            if ($value === null) {
                unset($_SERVER[$key]);
            } else {
                $_SERVER[$key] = $value;
            }
        }

        return $result;
    }

    private function recordResult($testName, $passed, $details)
    {
        $this->testResults[] = [
            'name' => $testName,
            'passed' => $passed,
            'details' => $details
        ];

        echo ($passed ? "✓ PASS" : "✗ FAIL") . ": $testName\n";
        if (!$passed) {
            echo "   Details: $details\n";
        }
    }

    private function printResults()
    {
        $passed = 0;
        $total = count($this->testResults);

        foreach ($this->testResults as $result) {
            if ($result['passed']) $passed++;
        }

        echo "\n=== Test Results ===\n";
        echo "Passed: $passed/$total\n";

        if ($passed === $total) {
            echo "🎉 All security tests passed!\n";
        } else {
            echo "❌ Some security tests failed. Review the agent security implementation.\n";
            echo "\nFailed tests:\n";
            foreach ($this->testResults as $result) {
                if (!$result['passed']) {
                    echo "- {$result['name']}: {$result['details']}\n";
                }
            }
        }
    }
}

// Run tests if executed directly
if (basename(__FILE__) === basename($_SERVER['SCRIPT_NAME'])) {
    $securityTest = new AgentSecurityTest();
    $securityTest->runAllTests();
}
?>
