<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Cors
{
    /** Exact origins allowed for browser access */
    private $allowedOrigins = [
        'http://localhost:3000',
        // 'http://127.0.0.1:5173',
        // 'https://app.webtrix24.com',
        // 'https://admin.webtrix24.com',
    ];

    /** Optional: allow any subdomain of webtrix24.com */
    private $allowedOriginSuffixes = ['.webtrix24.com','.coachgenie.in'];

    /** IPs that can call the API regardless of Origin */
    private $allowedIps = [
        // '111.125.249.242',
        // '203.0.113.10',
        // '198.51.100.0', // use exact IPs here; CIDR not parsed in this snippet
    ];

    /** Only trust these proxies when deriving client IP */
    private $trustedProxies = [
        // '127.0.0.1', '::1'
        // 'YOUR_EDGE_PROXY_IP',
        // 'YOUR_LOAD_BALANCER_IP',
    ];
    /** Public headers clients may send in CORS requests */
    private $allowHeaders = 'Content-Type, Authorization, X-Requested-With, SadminID, sadminid, Smemberid, smemberid, Token, token, X-Api-Key, X-Signature, X-Timestamp, X-Signature-Version,X-FILENAME,X-FILESIZE,X-UPLOAD-ID,X-CHUNK-INDEX,X-CHUNK-TOTAL,X-CHUNK-START,X-CHUNK-END';

    /** Methods you support */
    private $allowMethods = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';

    /** Headers you want to expose to browser JS */
    private $exposeHeaders = 'X-Total-Count';

    public function handle()
    {
        // Skip for CLI
        if (php_sapi_name() === 'cli') return;

        $method   = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $origin   = $_SERVER['HTTP_ORIGIN'] ?? null;
        $clientIp = $this->getClientIp();
        // 1) Origin check
        $originOk = $this->isOriginAllowed($origin);

        // 2) IP check
        $ipOk = $this->isIpAllowed($clientIp);

        // 3) Signature check (only if first two failed)
        $signatureOk = false;
        if (!$originOk && !$ipOk) {
            $signatureOk = $this->isSignatureValid();
        }

        // If NONE passed → block
        if (!$originOk && !$ipOk && !$signatureOk) {
            if ($method === 'OPTIONS') {
                header('HTTP/1.1 403 Forbidden');
                exit;
            }
            header('Content-Type: application/json; charset=utf-8');
            header('HTTP/1.1 403 Forbidden');
            echo json_encode(['error' => 'Origin/IP/signature not allowed']);
            exit;
        }

        // If the Origin itself is allowed, emit CORS headers for browser use
        if ($originOk && $origin) {
            header("Access-Control-Allow-Origin: {$origin}");
            header('Vary: Origin'); // caches vary by Origin
            header('Access-Control-Allow-Credentials: true');

            header("Access-Control-Allow-Methods: {$this->allowMethods}");
            header("Access-Control-Allow-Headers: {$this->allowHeaders}");
            header("Access-Control-Expose-Headers: {$this->exposeHeaders}");
            header('Access-Control-Max-Age: 86400');
            header('Vary: Access-Control-Request-Method, Access-Control-Request-Headers');
        }

        // Permit preflight only if at least one gate passed
        if ($method === 'OPTIONS') {
            // 204 No Content keeps responses clean for preflight
            header('HTTP/1.1 204 No Content');
            exit;
        }
    }

    private function isOriginAllowed(?string $origin): bool
    {
        if (!$origin) return false;

        // Exact match
        if (in_array($origin, $this->allowedOrigins, true)) return true;

        // Suffix match
        $host = $_SERVER['HTTP_HOST'];
        if (!$host) return false;
        
        foreach ($this->allowedOriginSuffixes as $suffix) {
            if ($suffix && substr($host, -strlen($suffix)) === $suffix) {
                return true;
            }
        }
        return false;
    }

    private function isIpAllowed(string $ip): bool
    {
        if (!$ip) return false;
        // exact IPs only; if you need CIDR, add a matcher here
        return in_array($ip, $this->allowedIps, true);
    }

    /**
     * Signature verification hook that calls your SignatureAuth library.
     * It tries a few common method names so you don’t have to refactor your library.
     */
    private function isSignatureValid(): bool
    {
        // by pass the security validation
        return true;
        // Direct include if not already loaded
        $path = APPPATH . 'libraries/core/SignatureAuth.php';
        if (file_exists($path)) {
            require_once $path;
        }

        if (class_exists('SignatureAuth')) {
            $auth = new \SignatureAuth();
        } elseif (class_exists('signatureauth')) {
            $auth = new \signatureauth();
        } else {
            return false;
        }

        $method   = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $path     = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
        $query    = $_SERVER['QUERY_STRING'] ?? '';
        $rawBody  = file_get_contents('php://input') ?: '';
        $headers  = $this->getAllHeadersNormalized();

        return method_exists($auth, 'validateRequest')
            ? (bool) $auth->validateRequest($method, $path, $query, $rawBody, $headers)
            : false;
    }

    /** Normalize incoming headers to a simple [Header-Name => value] map */
    private function getAllHeadersNormalized(): array
    {
        $out = [];

        // Prefer getallheaders() if available
        if (function_exists('getallheaders')) {
            foreach (getallheaders() as $k => $v) {
                $out[$this->normalizeHeaderKey($k)] = $v;
            }
        }

        // Fallback to $_SERVER
        foreach ($_SERVER as $key => $value) {
            if (strpos($key, 'HTTP_') === 0) {
                $name = str_replace('_', '-', strtolower(substr($key, 5)));
                $name = implode('-', array_map('ucfirst', explode('-', $name)));
                $out[$name] = $value;
            } elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'], true)) {
                $name = implode('-', array_map('ucfirst', explode('_', strtolower($key))));
                $out[$name] = $value;
            }
        }

        return $out;
    }

    private function normalizeHeaderKey(string $k): string
    {
        $k = strtolower($k);
        $parts = array_map('ucfirst', explode('-', str_replace('_', '-', $k)));
        return implode('-', $parts);
    }

    private function getClientIp(): string
    {
        // Start with direct remote addr
        $ip = $_SERVER['REMOTE_ADDR'] ?? '';

        // If behind trusted proxies, honor X-Forwarded-For chain
        $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
        if ($xff && in_array($ip, $this->trustedProxies, true)) {
            // XFF format: client, proxy1, proxy2...
            $ips = array_map('trim', explode(',', $xff));
            // Walk from right to left as long as the hop is trusted
            $lastHop = $ip;
            foreach (array_reverse($ips) as $candidate) {
                if (in_array($lastHop, $this->trustedProxies, true)) {
                    $ip = $candidate;
                    $lastHop = $candidate;
                } else {
                    break;
                }
            }
        }

        // Cloudflare specific (only if your proxy is trusted)
        if (!empty($_SERVER['HTTP_CF_CONNECTING_IP']) && in_array($ip, $this->trustedProxies, true)) {
            $ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
        }

        return $ip;
    }
}
