<?php

/**
 * Stripe API helper (no composer dependency).
 *
 * Uses Stripe REST endpoints directly via cURL and expects STRIPE_SECRET_KEY to be set.
 */

/**
 * @param string|null $idempotencyKey Stripe Idempotency-Key header (recommended for POST create).
 * @return array{ok: bool, status: int, body: array|null, raw: string}
 */
function stripe_form_request(string $method, string $path, array $params, ?string $idempotencyKey = null): array
{
    $key = defined('STRIPE_SECRET_KEY') ? (string) STRIPE_SECRET_KEY : '';
    $url = 'https://api.stripe.com' . $path;

    $headers = [
        'Authorization: Bearer ' . $key,
        'Content-Type: application/x-www-form-urlencoded',
    ];
    $ik = $idempotencyKey !== null ? trim($idempotencyKey) : '';
    if ($ik !== '') {
        $headers[] = 'Idempotency-Key: ' . $ik;
    }

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 45,
        CURLOPT_CUSTOMREQUEST => strtoupper($method),
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POSTFIELDS => http_build_query($params, '', '&'),
    ]);

    $raw = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $decoded = null;
    if (is_string($raw) && $raw !== '') {
        $decoded = json_decode($raw, true);
    }

    return [
        'ok' => $status >= 200 && $status < 300,
        'status' => $status,
        'body' => is_array($decoded) ? $decoded : null,
        'raw' => is_string($raw) ? $raw : '',
    ];
}

/**
 * @return array{ok: bool, status: int, body: array|null, raw: string}
 */
function stripe_request(string $method, string $path): array
{
    $key = defined('STRIPE_SECRET_KEY') ? (string) STRIPE_SECRET_KEY : '';
    $url = 'https://api.stripe.com' . $path;

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 45,
        CURLOPT_CUSTOMREQUEST => strtoupper($method),
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $key,
        ],
    ]);

    $raw = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $decoded = null;
    if (is_string($raw) && $raw !== '') {
        $decoded = json_decode($raw, true);
    }

    return [
        'ok' => $status >= 200 && $status < 300,
        'status' => $status,
        'body' => is_array($decoded) ? $decoded : null,
        'raw' => is_string($raw) ? $raw : '',
    ];
}

/**
 * Convert a decimal money string to minor units (cents).
 * Example: "123.45" -> 12345, "123" -> 12300
 */
function stripe_amount_to_minor_units($amount): int
{
    $s = trim((string) $amount);
    if ($s === '' || !is_numeric($s)) {
        return 0;
    }

    // Avoid float rounding; normalize with 2 decimals.
    $neg = false;
    if ($s !== '' && $s[0] === '-') {
        $neg = true;
        $s = substr($s, 1);
    }
    $parts = explode('.', $s, 2);
    $whole = preg_replace('/\D+/', '', $parts[0] ?? '') ?: '0';
    $frac = preg_replace('/\D+/', '', $parts[1] ?? '') ?: '';
    $frac = substr(str_pad($frac, 2, '0'), 0, 2);
    $minor = ((int) $whole) * 100 + (int) $frac;
    return $neg ? -$minor : $minor;
}

/**
 * Create a Stripe Checkout Session for a Shopify order.
 *
 * @return array{ok: bool, status: int, id?: string, url?: string, error?: string}
 */
function stripe_create_checkout_session_for_shopify_order(array $order): array
{
    $key = defined('STRIPE_SECRET_KEY') ? (string) STRIPE_SECRET_KEY : '';
    if ($key === '') {
        return ['ok' => false, 'status' => 500, 'error' => 'STRIPE_SECRET_KEY not configured'];
    }

    $orderId = isset($order['id']) ? (string) $order['id'] : '';
    $orderName = isset($order['name']) ? (string) $order['name'] : $orderId;
    $amountRaw = $order['total_price'] ?? $order['current_total_price'] ?? null;
    $currency = strtolower((string) ($order['currency'] ?? $order['presentment_currency'] ?? 'usd'));
    if ($currency === '') {
        $currency = 'usd';
    }

    $amountMinor = stripe_amount_to_minor_units($amountRaw);
    if ($amountMinor <= 0) {
        return ['ok' => false, 'status' => 400, 'error' => 'Invalid order amount'];
    }

    $successUrl = defined('STRIPE_CHECKOUT_SUCCESS_URL') ? (string) STRIPE_CHECKOUT_SUCCESS_URL : '';
    $cancelUrl = defined('STRIPE_CHECKOUT_CANCEL_URL') ? (string) STRIPE_CHECKOUT_CANCEL_URL : '';
    $baseUrl = defined('STRIPE_PUBLIC_BASE_URL') ? rtrim((string) STRIPE_PUBLIC_BASE_URL, '/') : '';

    // Stripe requires absolute success_url/cancel_url. If not explicitly configured,
    // build URLs to our simple message pages using STRIPE_PUBLIC_BASE_URL.
    if ($successUrl === '' && $baseUrl !== '') {
        $successUrl = $baseUrl . '/api/stripe-checkout-success.php?session_id={CHECKOUT_SESSION_ID}';
    }
    if ($cancelUrl === '' && $baseUrl !== '') {
        $cancelUrl = $baseUrl . '/api/stripe-checkout-cancel.php';
    }
    if ($successUrl === '' || $cancelUrl === '') {
        return ['ok' => false, 'status' => 500, 'error' => 'Stripe redirect URLs not configured (set STRIPE_PUBLIC_BASE_URL or STRIPE_CHECKOUT_SUCCESS_URL/STRIPE_CHECKOUT_CANCEL_URL)'];
    }

    $email = trim((string) ($order['email'] ?? $order['contact_email'] ?? ''));

    // Build payment_method_types[] from STRIPE_CHECKOUT_PAYMENT_METHOD_TYPES (default card + us_bank_account for ACH).
    $typesRaw = defined('STRIPE_CHECKOUT_PAYMENT_METHOD_TYPES')
        ? trim((string) STRIPE_CHECKOUT_PAYMENT_METHOD_TYPES)
        : 'card,us_bank_account';
    $pmTypes = [];
    foreach (explode(',', $typesRaw) as $chunk) {
        $t = strtolower(trim($chunk));
        if ($t !== '') {
            $pmTypes[] = $t;
        }
    }
    $pmTypes = array_values(array_unique($pmTypes));
    if ($pmTypes === []) {
        $pmTypes = ['card'];
    }
    // US bank debits are USD-only on Stripe Checkout; drop us_bank_account for other currencies.
    if ($currency !== 'usd') {
        $pmTypes = array_values(array_filter($pmTypes, static function (string $t): bool {
            return $t !== 'us_bank_account';
        }));
    }
    if ($pmTypes === []) {
        $pmTypes = ['card'];
    }

    // Stripe Checkout line items:
    // To ensure Stripe charges the *order total* (including shipping/tax/discounts) and avoids mismatches,
    // we create a SINGLE line item for the order total and include a human-readable item summary.
    $shopifyItems = $order['line_items'] ?? [];
    $itemSummary = '';
    $firstImage = '';
    if (is_array($shopifyItems) && $shopifyItems !== []) {
        $parts = [];
        foreach ($shopifyItems as $li) {
            if (!is_array($li)) {
                continue;
            }
            $qty = (int) ($li['quantity'] ?? 0);
            if ($qty <= 0) {
                continue;
            }
            $title = trim((string) ($li['title'] ?? $li['name'] ?? 'Item'));
            if ($title === '') {
                $title = 'Item';
            }
            $parts[] = $title . ' x' . $qty;

            if ($firstImage === '') {
                $img = trim((string) ($li['image'] ?? $li['image_url'] ?? ''));
                if ($img !== '' && preg_match('#^https?://#i', $img) === 1) {
                    $firstImage = $img;
                }
            }
        }
        if ($parts !== []) {
            // Keep description short to fit Stripe UI nicely.
            $itemSummary = implode(', ', array_slice($parts, 0, 12));
        }
    }

    $params = [
        'mode' => 'payment',
        'success_url' => $successUrl,
        'cancel_url' => $cancelUrl,
        'metadata[shopify_order_id]' => $orderId,
        'metadata[source]' => 'shopify',
        // Also copy metadata onto the PaymentIntent so payment_intent.* webhooks can find the order id (ACH async).
        'payment_intent_data[metadata][shopify_order_id]' => $orderId,
        'payment_intent_data[metadata][source]' => 'shopify',
        'automatic_tax[enabled]' => 'false',
        'allow_promotion_codes' => 'false',
    ];
    // Single line item for order total.
    $params['line_items[0][quantity]'] = 1;
    $params['line_items[0][price_data][currency]'] = $currency;
    $params['line_items[0][price_data][unit_amount]'] = $amountMinor;
    $params['line_items[0][price_data][product_data][name]'] = 'Shopify Order ' . ($orderName !== '' ? $orderName : $orderId);
    if ($itemSummary !== '') {
        $params['line_items[0][price_data][product_data][description]'] = $itemSummary;
    }
    if ($firstImage !== '') {
        $params['line_items[0][price_data][product_data][images][0]'] = $firstImage;
    }
    $idx = 0;
    foreach ($pmTypes as $t) {
        $params['payment_method_types[' . $idx . ']'] = $t;
        $idx++;
    }
    if (in_array('us_bank_account', $pmTypes, true)) {
        $vm = defined('STRIPE_US_BANK_VERIFICATION_METHOD')
            ? strtolower(trim((string) STRIPE_US_BANK_VERIFICATION_METHOD))
            : 'automatic';
        if (!in_array($vm, ['automatic', 'instant', 'microdeposits'], true)) {
            $vm = 'automatic';
        }
        $params['payment_method_options[us_bank_account][verification_method]'] = $vm;
    }
    if ($email !== '') {
        $params['customer_email'] = $email;
    }

    // One Checkout Session per Shopify order id: retries / duplicate webhooks reuse the same session.
    $idempotencyKey = 'shopify_order_' . $orderId . '_checkout_session';

    $res = stripe_form_request('POST', '/v1/checkout/sessions', $params, $idempotencyKey);
    if ($res['ok'] && is_array($res['body'])) {
        $id = $res['body']['id'] ?? null;
        $url = $res['body']['url'] ?? null;
        if (is_string($id) && $id !== '' && is_string($url) && $url !== '') {
            return ['ok' => true, 'status' => $res['status'], 'id' => $id, 'url' => $url];
        }
    }

    $msg = null;
    if (is_array($res['body'])) {
        $msg = $res['body']['error']['message'] ?? $res['body']['message'] ?? null;
    }
    if (!is_string($msg) || $msg === '') {
        $msg = $res['raw'];
    }

    return ['ok' => false, 'status' => $res['status'], 'error' => is_string($msg) && $msg !== '' ? $msg : 'Stripe error'];
}

/**
 * Fetch a Stripe Checkout Session.
 *
 * @return array{ok: bool, status: int, session?: array, error?: string}
 */
function stripe_get_checkout_session(string $sessionId): array
{
    $key = defined('STRIPE_SECRET_KEY') ? (string) STRIPE_SECRET_KEY : '';
    if ($key === '') {
        return ['ok' => false, 'status' => 500, 'error' => 'STRIPE_SECRET_KEY not configured'];
    }
    $sessionId = trim($sessionId);
    if ($sessionId === '') {
        return ['ok' => false, 'status' => 400, 'error' => 'Missing session_id'];
    }

    $res = stripe_request('GET', '/v1/checkout/sessions/' . rawurlencode($sessionId));
    if ($res['ok'] && is_array($res['body'])) {
        return ['ok' => true, 'status' => $res['status'], 'session' => $res['body']];
    }

    $msg = $res['body']['error']['message'] ?? $res['body']['message'] ?? $res['raw'];
    return ['ok' => false, 'status' => $res['status'], 'error' => is_string($msg) && $msg !== '' ? $msg : 'Stripe error'];
}

/**
 * Verify a Stripe webhook signature header.
 *
 * Stripe signs payloads as: v1 = HMAC_SHA256(secret, "{timestamp}.{payload}")
 */
function stripe_verify_webhook_signature(string $payload, ?string $sigHeader, string $secret, int $toleranceSeconds = 300): bool
{
    if ($secret === '' || $sigHeader === null || $sigHeader === '') {
        return false;
    }

    $parts = [];
    foreach (explode(',', $sigHeader) as $kv) {
        $kv = trim($kv);
        if ($kv === '' || strpos($kv, '=') === false) {
            continue;
        }
        [$k, $v] = explode('=', $kv, 2);
        $k = trim($k);
        $v = trim($v);
        if ($k !== '') {
            $parts[$k][] = $v;
        }
    }

    $tsList = $parts['t'] ?? [];
    $sigList = $parts['v1'] ?? [];
    if ($tsList === [] || $sigList === []) {
        return false;
    }

    $timestamp = (int) $tsList[0];
    if ($timestamp <= 0) {
        return false;
    }

    $now = time();
    if ($toleranceSeconds > 0 && abs($now - $timestamp) > $toleranceSeconds) {
        return false;
    }

    $signedPayload = $timestamp . '.' . $payload;
    $expected = hash_hmac('sha256', $signedPayload, $secret);

    foreach ($sigList as $sig) {
        if (is_string($sig) && hash_equals($expected, $sig)) {
            return true;
        }
    }
    return false;
}

