<?php
declare(strict_types=1);

/**
 * PayPal REST API Wrapper
 *
 * Minimal: Auth (Client Credentials), Order, Capture, Webhook-Verifikation.
 * Nutzt cURL, keine externen Libraries.
 *
 * Endpoints:
 *   Sandbox: https://api-m.sandbox.paypal.com
 *   Live:    https://api-m.paypal.com
 */
class PayPalClient
{
    private string $clientId;
    private string $clientSecret;
    private string $apiBase;
    private ?string $accessToken = null;
    private ?int $tokenExpiresAt = null;

    public function __construct(string $clientId, string $clientSecret, bool $sandbox = true)
    {
        $this->clientId     = $clientId;
        $this->clientSecret = $clientSecret;
        $this->apiBase      = $sandbox
            ? 'https://api-m.sandbox.paypal.com'
            : 'https://api-m.paypal.com';
    }

    /**
     * Erstellt eine PayPal Order (intent: CAPTURE).
     * Rückgabe enthält insbesondere 'id' (Order-ID) und 'links'.
     */
    public function createOrder(float $amount, string $currency, string $description, string $customId): array
    {
        $payload = [
            'intent' => 'CAPTURE',
            'purchase_units' => [[
                'reference_id' => $customId,
                'description'  => mb_substr($description, 0, 127),
                'custom_id'    => $customId,
                'amount' => [
                    'currency_code' => $currency,
                    'value'         => number_format($amount, 2, '.', ''),
                ],
            ]],
            'application_context' => [
                'brand_name'          => 'come2canada.life',
                'shipping_preference' => 'NO_SHIPPING',
                'user_action'         => 'PAY_NOW',
            ],
        ];

        return $this->request('POST', '/v2/checkout/orders', $payload);
    }

    /**
     * Captured eine genehmigte Order. Status COMPLETED = erfolgreich.
     */
    public function captureOrder(string $orderId): array
    {
        return $this->request(
            'POST',
            '/v2/checkout/orders/' . urlencode($orderId) . '/capture',
            new stdClass()
        );
    }

    public function getOrder(string $orderId): array
    {
        return $this->request('GET', '/v2/checkout/orders/' . urlencode($orderId));
    }

    /**
     * Verifiziert die Signatur eines eingehenden Webhook-Events.
     * Headers müssen lowercase Keys haben.
     */
    public function verifyWebhookSignature(array $headers, string $rawBody, string $webhookId): bool
    {
        $payload = [
            'transmission_id'   => $headers['paypal-transmission-id']   ?? '',
            'transmission_time' => $headers['paypal-transmission-time'] ?? '',
            'cert_url'          => $headers['paypal-cert-url']          ?? '',
            'auth_algo'         => $headers['paypal-auth-algo']         ?? '',
            'transmission_sig'  => $headers['paypal-transmission-sig']  ?? '',
            'webhook_id'        => $webhookId,
            'webhook_event'     => json_decode($rawBody, true),
        ];

        try {
            $result = $this->request('POST', '/v1/notifications/verify-webhook-signature', $payload);
            return ($result['verification_status'] ?? '') === 'SUCCESS';
        } catch (Throwable $e) {
            error_log('[PayPalClient] Webhook-Verifikation fehlgeschlagen: ' . $e->getMessage());
            return false;
        }
    }

    // ---------------------------------------------------------- intern

    private function getAccessToken(): string
    {
        // Cache nutzen — Token gilt ~9h, refreshen 60s vor Ablauf
        if ($this->accessToken && $this->tokenExpiresAt && time() < $this->tokenExpiresAt - 60) {
            return $this->accessToken;
        }

        $ch = curl_init($this->apiBase . '/v1/oauth2/token');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_USERPWD        => $this->clientId . ':' . $this->clientSecret,
            CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
            CURLOPT_POSTFIELDS     => 'grant_type=client_credentials',
            CURLOPT_TIMEOUT        => 30,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErr  = curl_error($ch);
        curl_close($ch);

        if ($httpCode !== 200) {
            throw new RuntimeException("PayPal Auth fehlgeschlagen: HTTP $httpCode — $response $curlErr");
        }

        $data = json_decode($response, true);
        if (!isset($data['access_token'])) {
            throw new RuntimeException('PayPal Auth: kein access_token in Response');
        }

        $this->accessToken    = $data['access_token'];
        $this->tokenExpiresAt = time() + (int)($data['expires_in'] ?? 32400);

        return $this->accessToken;
    }

    private function request(string $method, string $endpoint, $payload = null): array
    {
        $token = $this->getAccessToken();

        $ch = curl_init($this->apiBase . $endpoint);
        $opts = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $token,
                'Prefer: return=representation',
            ],
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CUSTOMREQUEST => $method,
        ];

        if ($method === 'POST' && $payload !== null) {
            $opts[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }

        curl_setopt_array($ch, $opts);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErr  = curl_error($ch);
        curl_close($ch);

        if ($httpCode >= 400) {
            throw new RuntimeException("PayPal API $method $endpoint: HTTP $httpCode — $response $curlErr");
        }

        $data = json_decode($response, true);
        if (!is_array($data)) {
            // Manche Endpoints liefern bei 204 leere Response zurück
            return ['_raw' => $response, '_http_code' => $httpCode];
        }

        return $data;
    }
}
