<?php
namespace App\Core;

/**
 * Cloudflare R2 helper — AWS Signature V4 ringkas
 * Mendukung presigned URL (PUT/GET) dan signed request (HEAD/DELETE)
 */
class R2
{
    private string $accessKey;
    private string $secretKey;
    private string $region;
    private string $bucket;
    private string $endpoint; // e.g. https://ACCOUNT_ID.r2.cloudflarestorage.com

    public function __construct()
    {
        $this->accessKey = CONFIG['R2_ACCESS_KEY_ID'];
        $this->secretKey = CONFIG['R2_SECRET_ACCESS_KEY'];
        $this->region    = CONFIG['R2_REGION'] ?? 'auto';
        $this->bucket    = CONFIG['R2_BUCKET'];
        $this->endpoint  = rtrim(CONFIG['R2_ENDPOINT_BASE'], '/');
    }

    /**
     * Generate presigned URL untuk PUT (upload) atau GET (download/preview)
     */
    public function presignUrl(string $method, string $objectKey, int $expireSeconds = 300, array $extraQueryParams = [], string $contentType = ''): string
    {
        $host = $this->bucket . '.' . $this->parseHost();
        $path = '/' . ltrim($objectKey, '/');
        $now = new \DateTime('now', new \DateTimeZone('UTC'));
        $dateStamp = $now->format('Ymd');
        $amzDate = $now->format('Ymd\THis\Z');
        $service = 's3';
        $credentialScope = "{$dateStamp}/{$this->region}/{$service}/aws4_request";
        $credential = "{$this->accessKey}/{$credentialScope}";

        // Signed headers
        $signedHeaders = 'host';

        // Query parameters
        $queryParams = [
            'X-Amz-Algorithm'     => 'AWS4-HMAC-SHA256',
            'X-Amz-Credential'    => $credential,
            'X-Amz-Date'          => $amzDate,
            'X-Amz-Expires'       => (string)$expireSeconds,
            'X-Amz-SignedHeaders'  => $signedHeaders,
        ];

        if ($contentType && $method === 'PUT') {
            $signedHeaders = 'content-type;host';
            $queryParams['X-Amz-SignedHeaders'] = $signedHeaders;
        }

        $queryParams = array_merge($queryParams, $extraQueryParams);
        ksort($queryParams);

        $canonicalQueryString = http_build_query($queryParams, '', '&', PHP_QUERY_RFC3986);

        // Canonical headers
        if ($contentType && $method === 'PUT') {
            $canonicalHeaders = "content-type:{$contentType}\nhost:{$host}\n";
        } else {
            $canonicalHeaders = "host:{$host}\n";
        }

        $canonicalRequest = implode("\n", [
            $method,
            $this->uriEncodePath($path),
            $canonicalQueryString,
            $canonicalHeaders,
            $signedHeaders,
            'UNSIGNED-PAYLOAD',
        ]);

        $stringToSign = implode("\n", [
            'AWS4-HMAC-SHA256',
            $amzDate,
            $credentialScope,
            hash('sha256', $canonicalRequest),
        ]);

        $signingKey = $this->getSignatureKey($dateStamp, $service);
        $signature = hash_hmac('sha256', $stringToSign, $signingKey);

        $url = "https://{$host}{$path}?{$canonicalQueryString}&X-Amz-Signature={$signature}";
        return $url;
    }

    /**
     * Kirim signed request ke R2 (HEAD, DELETE) — server-side
     */
    public function signedRequest(string $method, string $objectKey): array
    {
        $host = $this->bucket . '.' . $this->parseHost();
        $path = '/' . ltrim($objectKey, '/');
        $url = "https://{$host}{$path}";

        $now = new \DateTime('now', new \DateTimeZone('UTC'));
        $dateStamp = $now->format('Ymd');
        $amzDate = $now->format('Ymd\THis\Z');
        $service = 's3';
        $credentialScope = "{$dateStamp}/{$this->region}/{$service}/aws4_request";

        $payloadHash = hash('sha256', '');
        $signedHeaders = 'host;x-amz-content-sha256;x-amz-date';

        $canonicalHeaders = "host:{$host}\nx-amz-content-sha256:{$payloadHash}\nx-amz-date:{$amzDate}\n";

        $canonicalRequest = implode("\n", [
            $method,
            $this->uriEncodePath($path),
            '', // no query string
            $canonicalHeaders,
            $signedHeaders,
            $payloadHash,
        ]);

        $stringToSign = implode("\n", [
            'AWS4-HMAC-SHA256',
            $amzDate,
            $credentialScope,
            hash('sha256', $canonicalRequest),
        ]);

        $signingKey = $this->getSignatureKey($dateStamp, $service);
        $signature = hash_hmac('sha256', $stringToSign, $signingKey);

        $authorization = "AWS4-HMAC-SHA256 Credential={$this->accessKey}/{$credentialScope}, SignedHeaders={$signedHeaders}, Signature={$signature}";

        $headers = [
            "Host: {$host}",
            "x-amz-date: {$amzDate}",
            "x-amz-content-sha256: {$payloadHash}",
            "Authorization: {$authorization}",
        ];

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => true,
            CURLOPT_NOBODY         => ($method === 'HEAD'),
            CURLOPT_TIMEOUT        => 30,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $error = curl_error($ch);
        curl_close($ch);

        $responseHeaders = [];
        if ($response !== false) {
            $headerStr = substr($response, 0, $headerSize);
            foreach (explode("\r\n", $headerStr) as $line) {
                if (str_contains($line, ':')) {
                    [$k, $v] = explode(':', $line, 2);
                    $responseHeaders[strtolower(trim($k))] = trim($v);
                }
            }
        }

        return [
            'httpCode' => $httpCode,
            'headers'  => $responseHeaders,
            'error'    => $error,
        ];
    }

    private function getSignatureKey(string $dateStamp, string $service): string
    {
        $kDate    = hash_hmac('sha256', $dateStamp, "AWS4{$this->secretKey}", true);
        $kRegion  = hash_hmac('sha256', $this->region, $kDate, true);
        $kService = hash_hmac('sha256', $service, $kRegion, true);
        $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
        return $kSigning;
    }

    private function parseHost(): string
    {
        // Remove https://
        return preg_replace('#^https?://#', '', $this->endpoint);
    }

    private function uriEncodePath(string $path): string
    {
        $segments = explode('/', $path);
        $encoded = array_map(function ($s) {
            return rawurlencode($s);
        }, $segments);
        return implode('/', $encoded);
    }
}
