<?php

declare(strict_types=1);

namespace Sethrey\SysvoteWebsite;

/**
 * Minimal pure-PHP client for the parts of the Stripe REST API this project
 * needs: creating a Checkout Session, retrieving a Checkout Session, and
 * verifying webhook signatures.
 *
 * No external dependency (no stripe-php SDK) — consistent with SmtpMailer.
 */
class StripeClient
{
    private string $secretKey;
    private string $apiBase = 'https://api.stripe.com/v1';

    public function __construct(string $secretKey)
    {
        $this->secretKey = $secretKey;
    }

    /**
     * Creates a Checkout Session (mode "payment" for a one-time purchase).
     *
     * @param array<string, mixed> $params Stripe Checkout Session params, e.g.
     *   ['mode' => 'payment', 'line_items' => [...], 'success_url' => ..., 'cancel_url' => ...]
     * @return array<string, mixed> Decoded JSON response from Stripe.
     */
    public function createCheckoutSession(array $params): array
    {
        return $this->request('POST', '/checkout/sessions', $params);
    }

    /**
     * @return array<string, mixed>
     */
    public function retrieveCheckoutSession(string $sessionId): array
    {
        return $this->request('GET', '/checkout/sessions/' . urlencode($sessionId));
    }

    /**
     * Verifies a Stripe webhook signature (the "Stripe-Signature" header)
     * against the raw request body, without requiring the stripe-php SDK.
     */
    public static function verifyWebhookSignature(
        string $payload,
        string $sigHeader,
        string $webhookSecret,
        int $toleranceSeconds = 300
    ): bool {
        $timestamp = null;
        $signatures = [];

        foreach (explode(',', $sigHeader) as $pair) {
            $segments = explode('=', trim($pair), 2);
            if (count($segments) !== 2) {
                continue;
            }
            [$key, $value] = $segments;
            if ($key === 't') {
                $timestamp = $value;
            } elseif ($key === 'v1') {
                $signatures[] = $value;
            }
        }

        if ($timestamp === null || empty($signatures)) {
            return false;
        }

        if (abs(time() - (int) $timestamp) > $toleranceSeconds) {
            return false;
        }

        $signedPayload = $timestamp . '.' . $payload;
        $expectedSignature = hash_hmac('sha256', $signedPayload, $webhookSecret);

        foreach ($signatures as $signature) {
            if (hash_equals($expectedSignature, $signature)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    private function request(string $method, string $path, array $params = []): array
    {
        $url = $this->apiBase . $path;

        $options = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->secretKey,
            ],
            CURLOPT_TIMEOUT => 15,
        ];

        if ($method === 'POST') {
            $options[CURLOPT_POST] = true;
            $options[CURLOPT_POSTFIELDS] = $this->toFormParams($params);
        } elseif ($method === 'GET' && !empty($params)) {
            $url .= '?' . $this->toFormParams($params);
        }

        $options[CURLOPT_URL] = $url;

        $ch = curl_init();
        curl_setopt_array($ch, $options);

        $responseBody = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($errno !== 0) {
            throw new \RuntimeException('Stripe API request failed: ' . $error);
        }

        $decoded = json_decode((string) $responseBody, true);

        if ($httpCode >= 400) {
            $message = is_array($decoded) && isset($decoded['error']['message'])
                ? (string) $decoded['error']['message']
                : ('Stripe API error (HTTP ' . $httpCode . ')');
            throw new \RuntimeException($message);
        }

        return is_array($decoded) ? $decoded : [];
    }

    /**
     * Stripe expects PHP-style bracketed form encoding for nested arrays
     * (e.g. line_items[0][price]=...). http_build_query produces this
     * natively for nested arrays.
     *
     * @param array<string, mixed> $params
     */
    private function toFormParams(array $params): string
    {
        return http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    }
}
