<?php

/**
 * Kriosa Security - Production Ready SDK
 * 
 * One file. Zero dependencies. Instant protection.
 * 
 * @version 3.0.0
 * @link https://kriosa.com
 * @license MIT
 * 
 * TWO WAYS TO USE:
 * 
 * METHOD 1 - Download and include:
 * ```php
 * require_once 'kriosa.php';
 * $kriosa = new Kriosa('sk_your_api_key_here');
 * if (!$kriosa->protect()) { die('Blocked'); }
 * ```
 * 
 * METHOD 2 - Direct from URL (auto-installs):
 * ```php
 * require_once 'https://kriosa.com/kriosa.php';
 * if (!kriosa_protect('sk_your_api_key_here')) { die('Blocked'); }
 * ```
 */

// ==================== AUTO-INSTALLER ====================
// If this file is being accessed via HTTP (not local file), 
// it will automatically install itself to the current directory
if (php_sapi_name() !== 'cli' && isset($_SERVER['HTTP_HOST'])) {
    // Check if we're being accessed remotely (not already installed locally)
    $expectedLocalPath = __DIR__ . '/kriosa.php';
    $isRemoteAccess = (strpos(__FILE__, 'http://') === 0 || strpos(__FILE__, 'https://') === 0);

    if ($isRemoteAccess || (isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'kriosa.com') !== false)) {
        // Try to install locally
        $localFile = __DIR__ . '/kriosa.php';
        if (!file_exists($localFile) && is_writable(__DIR__)) {
            $content = file_get_contents('https://kriosa.com/kriosa.php');
            if ($content && file_put_contents($localFile, $content)) {
                // Redirect to local version
                header('Location: kriosa.php');
                exit;
            }
        }
    }
}

// ==================== MAIN CLASS ====================

class Kriosa
{
    /**
     * API version
     */
    private const VERSION = '3.0.0';

    /**
     * Default timeout in seconds
     */
    private const DEFAULT_TIMEOUT = 5;

    /**
     * Cache TTL in seconds
     */
    private const CACHE_TTL = 300;

    /**
     * Your API key (the only thing users need)
     */
    private string $apiKey;

    /**
     * API endpoint (auto-detected or configurable)
     */
    private string $apiUrl;

    /**
     * Request timeout in seconds
     */
    private int $timeout;

    /**
     * Whether to run in debug mode
     */
    private bool $debug;

    /**
     * Cache directory for API responses
     */
    private ?string $cacheDir;

    /**
     * Configuration array
     */
    private array $config;

    /**
     * Constructor - Initialize with your API key
     * 
     * @param string $apiKey Your Kriosa API key (starts with sk_)
     * @param array $options Optional configuration
     * @throws InvalidArgumentException If API key is invalid
     */
    public function __construct(string $apiKey, array $options = [])
    {
        // Validate API key format
        if (!$this->isValidApiKey($apiKey)) {
            throw new InvalidArgumentException('Invalid API key format. API key should start with "sk_" and be at least 20 characters.');
        }

        $this->apiKey = $apiKey;
        $this->config = $options;
        $this->apiUrl = $options['api_url'] ?? $this->detectApiUrl();
        $this->timeout = $options['timeout'] ?? self::DEFAULT_TIMEOUT;
        $this->debug = $options['debug'] ?? false;
        $this->cacheDir = $options['cache_dir'] ?? $this->getDefaultCacheDir();

        // Create cache directory if needed
        if ($this->cacheDir && !is_dir($this->cacheDir)) {
            @mkdir($this->cacheDir, 0755, true);
        }

        $this->log("Kriosa initialized", [
            'version' => self::VERSION,
            'api_url' => $this->apiUrl,
            'cache_enabled' => $this->cacheDir !== null
        ]);
    }

    /**
     * Main protection method - Check if current request is safe
     * 
     * @return bool True if request is allowed, false if blocked
     */
    public function protect(): bool
    {
        $startTime = microtime(true);
        $requestId = $this->generateRequestId();

        $this->log("Security check started", ['request_id' => $requestId]);

        try {
            // Build request data automatically
            $requestData = $this->buildRequestData($requestId);

            // Check cache first
            $cachedResult = $this->getCachedResult($requestData);
            if ($cachedResult !== null) {
                $this->log("Cache hit", ['request_id' => $requestId]);
                return $cachedResult;
            }

            // Call API
            $result = $this->callApi($requestData, $requestId);

            // Cache result if allowed
            if ($result['allowed']) {
                $this->cacheResult($requestData, $result['allowed']);
            } else {
                // ========== ADD THIS: Show professional block page ==========
                $this->showBlockPage($result);
                // ============================================================
            }

            $processingTime = (microtime(true) - $startTime) * 1000;
            $this->log("Security check completed", [
                'request_id' => $requestId,
                'allowed' => $result['allowed'],
                'processing_time_ms' => round($processingTime, 2)
            ]);

            return $result['allowed'];
        } catch (Exception $e) {
            $this->log("Security check failed", [
                'request_id' => $requestId,
                'error' => $e->getMessage()
            ]);
            return $this->config['fail_closed'] ?? false ? false : true;
        }
    }
    /**
     * Show professional block page with shaking hand peace sign
     * 
     * @param array $result The API result containing threat details
     */
    private function showBlockPage(array $result): void
    {
        http_response_code(403);
        header('X-Kriosa-Blocked: true');

        $refId = substr(md5(uniqid()), 0, 12);

        echo <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="robots" content="noindex, nofollow">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
    <title>403 - Access Denied</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            padding: 20px;
        }
        
        @keyframes shakeHand {
            0% { transform: rotate(0deg); }
            20% { transform: rotate(15deg); }
            40% { transform: rotate(-12deg); }
            60% { transform: rotate(8deg); }
            80% { transform: rotate(-4deg); }
            100% { transform: rotate(0deg); }
        }
        
        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
        
        .container {
            text-align: center;
            max-width: 450px;
            animation: fadeIn 0.5s ease;
        }
        
        .hand-icon {
            margin-bottom: 30px;
        }
        
        .hand-icon i {
            font-size: 80px;
            color: #ff3366;
            display: inline-block;
            animation: shakeHand 0.6s ease-in-out 3;
            filter: drop-shadow(0 0 20px rgba(255,51,102,0.3));
        }
        
        .code {
            font-size: 72px;
            font-weight: 800;
            background: linear-gradient(135deg, #ff3366, #ff6b6b);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            margin-bottom: 16px;
            letter-spacing: 4px;
        }
        
        h1 {
            color: #ffffff;
            font-size: 24px;
            font-weight: 500;
            margin-bottom: 12px;
        }
        
        .divider {
            width: 50px;
            height: 2px;
            background: linear-gradient(90deg, transparent, #ff3366, transparent);
            margin: 20px auto;
        }
        
        p {
            color: rgba(255,255,255,0.6);
            font-size: 14px;
            line-height: 1.6;
            margin-bottom: 24px;
        }
        
        .ref {
            font-size: 11px;
            font-family: monospace;
            color: rgba(255,255,255,0.25);
            margin-bottom: 24px;
            word-break: break-all;
        }
        
        .btn {
            display: inline-block;
            padding: 12px 28px;
            background: rgba(255,255,255,0.05);
            border: 1px solid rgba(255,255,255,0.1);
            color: #ffffff;
            text-decoration: none;
            border-radius: 40px;
            font-size: 14px;
            font-weight: 500;
            transition: all 0.3s ease;
            cursor: pointer;
        }
        
        .btn:hover {
            background: rgba(255,51,102,0.2);
            border-color: rgba(255,51,102,0.5);
            transform: translateY(-2px);
        }
        
        .secure-badge {
            margin-top: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            font-size: 10px;
            color: rgba(255,255,255,0.2);
        }
        
        .secure-badge i {
            font-size: 10px;
        }
        
        @media (max-width: 480px) {
            .hand-icon i {
                font-size: 60px;
            }
            .code {
                font-size: 48px;
            }
            h1 {
                font-size: 20px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="hand-icon">
            <i class="fas fa-hand-peace"></i>
        </div>
        <div class="code">403</div>
        <h1>Access Denied</h1>
        <div class="divider"></div>
        <p>The server could not process your request.<br>Please verify your input and try again.</p>
        <div class="ref">REF: {$refId}</div>
        <button class="btn" onclick="history.back()">← Go Back</button>
        <div class="secure-badge">
            <i class="fas fa-shield-alt"></i>
            <span>Secure Connection</span>
            <i class="fas fa-lock"></i>
        </div>
    </div>
</body>
</html>
HTML;
        exit;
    }
    /**
     * Quick protect - One line protection
     * 
     * @param string $apiKey Your API key
     * @return bool True if allowed, false if blocked
     */
    public static function quick(string $apiKey): bool
    {
        static $instance = null;
        if ($instance === null) {
            $instance = new self($apiKey);
        }
        return $instance->protect();
    }

    /**
     * Check if request is an attack (returns details)
     * 
     * @return array Detailed security result
     */
    public function check(): array
    {
        $startTime = microtime(true);
        $requestId = $this->generateRequestId();

        try {
            $requestData = $this->buildRequestData($requestId);
            $result = $this->callApi($requestData, $requestId);

            $result['processing_time_ms'] = round((microtime(true) - $startTime) * 1000, 2);
            $result['request_id'] = $requestId;
            $result['kriosa_version'] = self::VERSION;

            return $result;
        } catch (Exception $e) {
            return [
                'allowed' => true,
                'error' => $e->getMessage(),
                'processing_time_ms' => round((microtime(true) - $startTime) * 1000, 2),
                'request_id' => $requestId,
                'kriosa_version' => self::VERSION,
                'fallback' => true
            ];
        }
    }

    /**
     * Build complete request data automatically
     */
    private function buildRequestData(string $requestId): array
    {
        // Get server data
        $server = $_SERVER;

        // Get client IP (handles proxies)
        $ip = $this->getClientIp();

        // Get user agent
        $userAgent = $server['HTTP_USER_AGENT'] ?? '';

        // Get request method
        $method = $server['REQUEST_METHOD'] ?? 'GET';

        // Get request URI
        $uri = $server['REQUEST_URI'] ?? '/';

        // Get session ID if available
        $sessionId = session_id() ?: null;

        // Get user ID from session if available
        $userId = $_SESSION['user_id'] ?? $_SESSION['userid'] ?? $_SESSION['uid'] ?? 0;
        $username = $_SESSION['username'] ?? $_SESSION['user'] ?? $_SESSION['email'] ?? null;

        // Get tenant ID from session if available
        $tenantId = $_SESSION['tenant_id'] ?? $_SESSION['tenant'] ?? null;

        // Get company ID from session if available
        $companyId = $_SESSION['company_id'] ?? $_SESSION['company'] ?? null;

        // Check if authenticated
        $isAuthenticated = ($userId > 0) || !empty($server['HTTP_AUTHORIZATION']) || !empty($server['HTTP_X_API_KEY']);

        return [
            // API key (the source of truth on your server)
            'api_key' => $this->apiKey,

            // Request metadata
            'request_id' => $requestId,
            'timestamp' => time(),
            'source' => 'kriosa_php_sdk_v3',

            // Client information
            'ip_address' => $ip,
            'user_agent' => $userAgent,
            'session_id' => $sessionId,

            // Session user info (if available)
            'session_user_id' => $userId,
            'session_username' => $username,
            'session_tenant_id' => $tenantId,
            'session_company_id' => $companyId,

            // Request details
            'method' => $method,
            'resource' => $uri,
            'path' => $uri,
            'query_string' => $server['QUERY_STRING'] ?? '',
            'payload' => $this->getPayload(),
            'query_params' => $_GET,
            'headers' => $this->getRelevantHeaders(),

            // Authentication status
            'is_authenticated' => $isAuthenticated,

            // SDK metadata
            'sdk_version' => self::VERSION,
            'sdk_language' => 'php',
            'php_version' => PHP_VERSION
        ];
    }

    /**
     * Get request payload (handles JSON, form data, etc.)
     */
    private function getPayload(): array
    {
        // Check for JSON input
        $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
        if (strpos($contentType, 'application/json') !== false) {
            $input = file_get_contents('php://input');
            if ($input) {
                $data = json_decode($input, true);
                if (is_array($data)) {
                    return $data;
                }
            }
        }

        // Return POST data
        return $_POST;
    }

    /**
     * Get relevant headers for security analysis
     */
    private function getRelevantHeaders(): array
    {
        $headers = [];
        $relevant = [
            'HTTP_REFERER' => 'Referer',
            'HTTP_ORIGIN' => 'Origin',
            'HTTP_ACCEPT_LANGUAGE' => 'Accept-Language',
            'HTTP_ACCEPT_ENCODING' => 'Accept-Encoding',
            'HTTP_X_FORWARDED_FOR' => 'X-Forwarded-For',
            'HTTP_X_REAL_IP' => 'X-Real-IP',
            'HTTP_X_REQUEST_ID' => 'X-Request-Id'
        ];

        foreach ($relevant as $serverKey => $headerName) {
            if (isset($_SERVER[$serverKey]) && $_SERVER[$serverKey]) {
                $headers[$headerName] = $_SERVER[$serverKey];
            }
        }

        return $headers;
    }

    /**
     * Get real client IP (handles Cloudflare, proxies, load balancers)
     */
    private function getClientIp(): string
    {
        $ip = null;

        // Cloudflare
        if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
            $ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
        }
        // Nginx proxy
        elseif (isset($_SERVER['HTTP_X_REAL_IP'])) {
            $ip = $_SERVER['HTTP_X_REAL_IP'];
        }
        // Load balancer / proxy
        elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $ip = trim($ips[0]);
        }
        // Direct connection
        elseif (isset($_SERVER['REMOTE_ADDR']) && !empty($_SERVER['REMOTE_ADDR'])) {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        // Localhost fallback
        else {
            $ip = '127.0.0.1';
        }

        // ========== FIX: Convert IPv6 localhost to IPv4 ==========
        if ($ip === '::1' || $ip === '0:0:0:0:0:0:0:1') {
            $ip = '127.0.0.1';
        }

        // Handle cases where IP is empty or invalid
        if (empty($ip) || $ip === '0.0.0.0') {
            $ip = '127.0.0.1';
        }
        // =========================================================

        // DEBUG: Log the IP being sent
        error_log("KRIOSA SDK: Final IP = " . $ip);

        return $ip;
    }
    /**
     * Validate IP address
     */
    private function isValidIp(string $ip): bool
    {
        return filter_var($ip, FILTER_VALIDATE_IP) !== false;
    }

    /**
     * Call the Kriosa API
     */
    private function callApi(array $requestData, string $requestId): array
    {
        if (!function_exists('curl_init')) {
            $this->log("cURL not available", ['request_id' => $requestId]);
            return $this->fallbackResponse('cURL not available');
        }

        $ch = curl_init($this->apiUrl);

        $jsonData = json_encode($requestData);
        if ($jsonData === false) {
            return $this->fallbackResponse('JSON encoding failed');
        }

        $headers = [
            'Content-Type: application/json',
            'Accept: application/json',
            'User-Agent: Kriosa-PHP-SDK/' . self::VERSION,
            'X-Request-ID: ' . $requestId
        ];

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $jsonData,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CONNECTTIMEOUT => 3,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 2
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);

        curl_close($ch);

        $this->log("API response", [
            'request_id' => $requestId,
            'http_code' => $httpCode,
            'response_length' => strlen($response)
        ]);

        if ($response === false) {
            $this->log("cURL error", ['request_id' => $requestId, 'error' => $curlError]);
            return $this->fallbackResponse($curlError);
        }

        if ($httpCode === 403) {
            return ['allowed' => false, 'reason' => 'attack_blocked'];
        }

        if ($httpCode !== 200) {
            $this->log("Unexpected status code", ['request_id' => $requestId, 'http_code' => $httpCode]);
            return $this->fallbackResponse("HTTP {$httpCode}");
        }

        $result = json_decode($response, true);
        if (!is_array($result)) {
            return $this->fallbackResponse('Invalid JSON response');
        }

        return $result;
    }

    /**
     * Fallback response when API is unreachable
     */
    private function fallbackResponse(string $reason = ''): array
    {
        $this->log("Using fallback mode", ['reason' => $reason]);

        // Basic local pattern check as last resort
        if ($this->hasLocalAttackPattern()) {
            return ['allowed' => false, 'reason' => 'local_pattern_match'];
        }

        return ['allowed' => true, 'fallback' => true, 'reason' => $reason];
    }

    /**
     * Basic local attack pattern check (fallback only)
     */
    private function hasLocalAttackPattern(): bool
    {
        $input = json_encode($_POST) . ' ' . json_encode($_GET) . ' ' . ($_SERVER['REQUEST_URI'] ?? '');

        $patterns = [
            '/\b(union\s+select|select.*from|insert\s+into|delete\s+from|drop\s+table)\b/i',
            '/<script[^>]*>.*<\/script[^>]*>/is',
            '/javascript\s*:/i',
            '/;.*(ls|dir|cat|whoami|id|pwd)/i',
            '/\.\.\//',
            '/\.\.\\\\/',
            '/\$\w+/'
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $input)) {
                $this->log("Local pattern match", ['pattern' => $pattern]);
                return true;
            }
        }

        return false;
    }

    /**
     * Get cached result
     */
    private function getCachedResult(array $requestData): ?bool
    {
        if (!$this->cacheDir) {
            return null;
        }

        $cacheKey = $this->getCacheKey($requestData);
        $cacheFile = $this->cacheDir . '/' . $cacheKey . '.cache';

        if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < self::CACHE_TTL) {
            $cached = @file_get_contents($cacheFile);
            if ($cached !== false) {
                $data = unserialize($cached);
                if (is_array($data) && isset($data['allowed'])) {
                    return $data['allowed'];
                }
            }
        }

        return null;
    }

    /**
     * Cache result
     */
    private function cacheResult(array $requestData, bool $allowed): void
    {
        if (!$this->cacheDir) {
            return;
        }

        $cacheKey = $this->getCacheKey($requestData);
        $cacheFile = $this->cacheDir . '/' . $cacheKey . '.cache';

        @file_put_contents($cacheFile, serialize(['allowed' => $allowed, 'cached_at' => time()]));
    }

    /**
     * Generate cache key from request data
     */
    private function getCacheKey(array $requestData): string
    {
        $keyData = [
            'api_key' => substr(md5($this->apiKey), 0, 16),
            'resource' => $requestData['resource'] ?? '/',
            'method' => $requestData['method'] ?? 'GET'
        ];

        return 'kriosa_' . md5(json_encode($keyData));
    }

    /**
     * Validate API key format
     */
    private function isValidApiKey(string $apiKey): bool
    {
        // API key should start with sk_ and be at least 20 characters
        return strpos($apiKey, 'sk_') === 0 && strlen($apiKey) >= 20;
    }

    /**
     * Detect API URL automatically
     */
    private function detectApiUrl(): string
    {
        // Check for local development
        $host = $_SERVER['HTTP_HOST'] ?? '';
        if (strpos($host, 'localhost') !== false || strpos($host, '127.0.0.1') !== false) {
            return 'http://localhost/api.php/v1/protect';
        }

        // Check if we're being accessed from kriosa.com domain
        if (strpos($host, 'kriosa.com') !== false) {
            return 'https://kriosa.com/api.php/v1/protect';
        }

        // Production URL
        return 'https://kriosa.com/api.php/v1/protect';
    }

    /**
     * Get default cache directory
     */
    private function getDefaultCacheDir(): ?string
    {
        // Try system temp directory
        $tempDir = sys_get_temp_dir() . '/kriosa_cache';
        if (is_writable(dirname($tempDir))) {
            return $tempDir;
        }

        // Try current directory
        if (is_writable(__DIR__)) {
            return __DIR__ . '/.kriosa_cache';
        }

        // Cache disabled
        return null;
    }

    /**
     * Generate unique request ID
     */
    private function generateRequestId(): string
    {
        return 'req_' . uniqid() . '_' . bin2hex(random_bytes(4));
    }

    /**
     * Log debug messages
     */
    private function log(string $message, array $context = []): void
    {
        if (!$this->debug) {
            return;
        }

        $log = date('Y-m-d H:i:s') . " [KRIOSA] " . $message;
        if (!empty($context)) {
            $log .= " " . json_encode($context);
        }

        error_log($log);
    }
}

// ==================== GLOBAL HELPER FUNCTIONS ====================

/**
 * Global helper function for ultra-simple protection
 * 
 * @param string $apiKey Your Kriosa API key
 * @return bool True if allowed, false if blocked
 */
function kriosa_protect(string $apiKey): bool
{
    return Kriosa::quick($apiKey);
}

/**
 * Global helper function with detailed result
 * 
 * @param string $apiKey Your Kriosa API key
 * @return array Detailed security result
 */
function kriosa_check(string $apiKey): array
{
    static $instance = null;
    if ($instance === null) {
        $instance = new Kriosa($apiKey);
    }
    return $instance->check();
}

// ==================== AUTO-EXECUTION (for direct URL usage) ====================
// If this file is included from a remote URL and the user hasn't instantiated it,
// automatically protect the request if KRIOSA_API_KEY is defined
if (defined('KRIOSA_API_KEY') && constant('KRIOSA_API_KEY') && !defined('KRIOSA_SKIP_AUTO')) {
    if (!kriosa_protect(constant('KRIOSA_API_KEY'))) {
        http_response_code(403);
        die('Request blocked by Kriosa Security');
    }
}
