<?php

declare(strict_types=1);

function stripe_secret_key(): string
{
    return trim((string) env('STRIPE_SECRET_KEY', ''));
}

function stripe_webhook_secret(): string
{
    return trim((string) env('STRIPE_WEBHOOK_SECRET', ''));
}

function stripe_currency(): string
{
    $currency = strtolower(trim((string) env('STRIPE_CURRENCY', 'eur')));
    if ($currency === '') {
        return 'eur';
    }

    return preg_match('/^[a-z]{3}$/', $currency) === 1 ? $currency : 'eur';
}

function stripe_webhook_tolerance_seconds(): int
{
    $tolerance = (int) env('STRIPE_WEBHOOK_TOLERANCE', '300');
    return max(30, min(3600, $tolerance));
}

function stripe_vat_rate(): float
{
    return 0.20;
}

function stripe_success_url(): string
{
    $configured = trim((string) env('STRIPE_SUCCESS_URL', ''));
    if ($configured !== '') {
        return $configured;
    }

    $baseUrl = rtrim((string) config('app.base_url', ''), '/');
    if ($baseUrl === '') {
        $baseUrl = 'http://localhost:5173';
    }

    return $baseUrl . '/checkout/success?session_id={CHECKOUT_SESSION_ID}';
}

function stripe_cancel_url(): string
{
    $configured = trim((string) env('STRIPE_CANCEL_URL', ''));
    if ($configured !== '') {
        return $configured;
    }

    $baseUrl = rtrim((string) config('app.base_url', ''), '/');
    if ($baseUrl === '') {
        $baseUrl = 'http://localhost:5173';
    }

    return $baseUrl . '/cart?stripe=cancel';
}

function stripe_is_enabled(): bool
{
    return stripe_secret_key() !== '';
}

function stripe_clip(string $value, int $maxLength): string
{
    $trimmed = trim($value);
    if ($trimmed === '') {
        return '';
    }

    if ($maxLength <= 0) {
        return '';
    }

    if (function_exists('mb_substr')) {
        return mb_substr($trimmed, 0, $maxLength);
    }

    return substr($trimmed, 0, $maxLength);
}

function stripe_normalize_order_lines(array $linesInput): array
{
    $normalized = [];
    foreach ($linesInput as $line) {
        if (!is_array($line)) {
            continue;
        }

        $articleId = (int) ($line['article_id'] ?? $line['id'] ?? 0);
        $qty = (int) ($line['qty'] ?? $line['quantite'] ?? $line['quantity'] ?? 0);
        if ($articleId <= 0 || $qty <= 0) {
            continue;
        }

        if (!isset($normalized[$articleId])) {
            $normalized[$articleId] = 0;
        }
        $normalized[$articleId] += $qty;
    }

    return $normalized;
}

function stripe_encode_lines_metadata(array $lines): string
{
    if ($lines === []) {
        return '';
    }

    ksort($lines);
    $parts = [];
    foreach ($lines as $articleId => $qty) {
        $id = (int) $articleId;
        $quantity = (int) $qty;
        if ($id <= 0 || $quantity <= 0) {
            continue;
        }
        $parts[] = $id . ':' . $quantity;
    }

    return implode('|', $parts);
}

function stripe_decode_lines_metadata(array $metadata): array
{
    $chunks = [];
    if (isset($metadata['camparts_lines']) && is_string($metadata['camparts_lines'])) {
        $chunks[] = $metadata['camparts_lines'];
    }

    foreach ($metadata as $key => $value) {
        if (!is_string($key) || !is_string($value)) {
            continue;
        }
        if (preg_match('/^camparts_lines_(\d+)$/', $key, $matches) !== 1) {
            continue;
        }
        $index = (int) ($matches[1] ?? 0);
        if ($index <= 0) {
            continue;
        }
        $chunks[$index] = $value;
    }

    if ($chunks === []) {
        return [];
    }

    ksort($chunks);
    $encoded = implode('', array_values($chunks));

    if (trim($encoded) === '') {
        return [];
    }

    $lines = [];
    foreach (explode('|', $encoded) as $entry) {
        $parts = explode(':', trim($entry), 2);
        if (count($parts) !== 2) {
            continue;
        }

        $articleId = (int) ($parts[0] ?? 0);
        $qty = (int) ($parts[1] ?? 0);
        if ($articleId <= 0 || $qty <= 0) {
            continue;
        }

        if (!isset($lines[$articleId])) {
            $lines[$articleId] = 0;
        }
        $lines[$articleId] += $qty;
    }

    return $lines;
}

function stripe_lines_metadata_chunks(array $lines): array
{
    $encoded = stripe_encode_lines_metadata($lines);
    if ($encoded === '') {
        return ['camparts_lines' => ''];
    }

    if (strlen($encoded) <= 480) {
        return ['camparts_lines' => $encoded];
    }

    $chunks = str_split($encoded, 480);
    if (count($chunks) > 20) {
        throw new RuntimeException('Panier trop volumineux pour un paiement Stripe.');
    }

    $metadata = [];
    foreach ($chunks as $index => $chunk) {
        $metadata['camparts_lines_' . ($index + 1)] = $chunk;
    }

    return $metadata;
}

function stripe_validate_checkout_payload(array $payload, array $currentUser): array
{
    if (!stripe_is_enabled()) {
        throw new RuntimeException('Paiement Stripe non configure.');
    }

    $userId = (int) ($currentUser['id'] ?? 0);
    if ($userId <= 0) {
        throw new RuntimeException('Connexion requise pour payer par carte.');
    }

    $userMail = strtolower(trim((string) ($currentUser['mail'] ?? '')));
    if ($userMail === '') {
        throw new RuntimeException('Email utilisateur manquant pour Stripe.');
    }

    $linesInput = $payload['lignes'] ?? $payload['lines'] ?? [];
    if (!is_array($linesInput)) {
        throw new InvalidArgumentException('Le champ lignes est invalide.');
    }

    $lines = stripe_normalize_order_lines($linesInput);
    if ($lines === []) {
        throw new InvalidArgumentException('Aucune ligne de commande valide.');
    }

    cart_prune_expired_reservations();

    $articleIds = array_keys($lines);
    $articles = repo_find_articles_by_ids($articleIds);
    $reservedByUser = cart_reserved_qty_for_user($userId, $articleIds);
    $reservedByOthers = repo_reserved_qty_by_article($articleIds, $userId);

    foreach ($articleIds as $articleId) {
        $reservedQty = (int) ($reservedByUser[$articleId] ?? 0);
        if ($reservedQty < $lines[$articleId]) {
            throw new RuntimeException('Reservation panier expiree ou insuffisante pour l\'article #' . $articleId . '.');
        }
    }

    foreach ($articleIds as $articleId) {
        if (!isset($articles[$articleId])) {
            throw new RuntimeException('Article introuvable: ' . $articleId);
        }

        $rawStock = (int) ($articles[$articleId]['stock'] ?? 0);
        $reservedElsewhere = (int) ($reservedByOthers[$articleId] ?? 0);
        $available = max(0, $rawStock - $reservedElsewhere);
        if ($available < $lines[$articleId]) {
            throw new RuntimeException('Stock insuffisant pour l\'article #' . $articleId);
        }
    }

    $address = stripe_clip((string) ($payload['adresse'] ?? ''), 240);
    $firstName = stripe_clip((string) ($currentUser['prenom'] ?? ''), 120);
    $lastName = stripe_clip((string) ($currentUser['nom'] ?? ''), 120);

    $lineItems = [];
    foreach ($lines as $articleId => $qty) {
        $article = $articles[$articleId] ?? null;
        if (!is_array($article)) {
            continue;
        }

        $name = stripe_clip((string) ($article['nom'] ?? ('Article #' . $articleId)), 120);
        if ($name === '') {
            $name = 'Article #' . $articleId;
        }
        $sku = stripe_clip((string) ($article['reference'] ?? $articleId), 64);
        $unitPriceHt = normalize_decimal((float) ($article['prix'] ?? 0));
        $unitPriceTtc = normalize_decimal($unitPriceHt * (1 + stripe_vat_rate()));
        $unitAmountCents = max(1, (int) round($unitPriceTtc * 100));

        $lineItems[] = [
            'article_id' => (int) $articleId,
            'qty' => (int) $qty,
            'name' => $name,
            'reference' => $sku,
            'unit_amount_cents' => $unitAmountCents,
        ];
    }

    if ($lineItems === []) {
        throw new RuntimeException('Panier vide apres validation Stripe.');
    }

    return [
        'user_id' => $userId,
        'user_mail' => $userMail,
        'first_name' => $firstName,
        'last_name' => $lastName,
        'address' => $address,
        'lines' => $lines,
        'line_items' => $lineItems,
    ];
}

function stripe_api_request(string $method, string $path, array $params = []): array
{
    $secretKey = stripe_secret_key();
    if ($secretKey === '') {
        throw new RuntimeException('STRIPE_SECRET_KEY manquant.');
    }

    $method = strtoupper(trim($method));
    if (!in_array($method, ['GET', 'POST'], true)) {
        throw new InvalidArgumentException('Methode Stripe non supportee.');
    }

    $url = 'https://api.stripe.com' . $path;
    if ($method === 'GET' && $params !== []) {
        $query = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
        if ($query !== '') {
            $url .= '?' . $query;
        }
    }

    if (!function_exists('curl_init')) {
        throw new RuntimeException('Extension cURL requise pour Stripe.');
    }

    $ch = curl_init($url);
    if ($ch === false) {
        throw new RuntimeException('Impossible d\'initialiser cURL pour Stripe.');
    }

    $headers = [
        'Authorization: Bearer ' . $secretKey,
        'Stripe-Version: 2023-10-16',
        'Accept: application/json',
    ];

    if ($method === 'POST') {
        $body = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        $headers[] = 'Content-Type: application/x-www-form-urlencoded';
    }

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_TIMEOUT, 25);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);

    $rawResponse = curl_exec($ch);
    if ($rawResponse === false) {
        $error = curl_error($ch);
        curl_close($ch);
        throw new RuntimeException('Erreur Stripe cURL: ' . ($error !== '' ? $error : 'inconnue'));
    }

    $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $decoded = json_decode((string) $rawResponse, true);
    if (!is_array($decoded)) {
        throw new RuntimeException('Reponse Stripe invalide (JSON).');
    }

    if ($httpCode >= 400) {
        $message = trim((string) (($decoded['error']['message'] ?? '') ?: 'Erreur Stripe HTTP ' . $httpCode));
        throw new RuntimeException($message);
    }

    return $decoded;
}

function stripe_create_checkout_session(array $payload, array $currentUser): array
{
    $checkout = stripe_validate_checkout_payload($payload, $currentUser);
    $metadata = array_merge(
        [
            'camparts_user_id' => (string) $checkout['user_id'],
            'camparts_user_mail' => stripe_clip((string) $checkout['user_mail'], 240),
            'camparts_user_first_name' => stripe_clip((string) $checkout['first_name'], 120),
            'camparts_user_last_name' => stripe_clip((string) $checkout['last_name'], 120),
            'camparts_address' => stripe_clip((string) $checkout['address'], 240),
        ],
        stripe_lines_metadata_chunks($checkout['lines'])
    );

    $lineItems = [];
    foreach ($checkout['line_items'] as $entry) {
        if (!is_array($entry)) {
            continue;
        }

        $lineItems[] = [
            'price_data' => [
                'currency' => stripe_currency(),
                'unit_amount' => max(1, (int) ($entry['unit_amount_cents'] ?? 0)),
                'product_data' => [
                    'name' => stripe_clip((string) ($entry['name'] ?? 'Article Camparts'), 120),
                    'metadata' => [
                        'article_id' => (string) ((int) ($entry['article_id'] ?? 0)),
                        'reference' => stripe_clip((string) ($entry['reference'] ?? ''), 64),
                    ],
                ],
            ],
            'quantity' => max(1, (int) ($entry['qty'] ?? 1)),
        ];
    }

    $session = stripe_api_request('POST', '/v1/checkout/sessions', [
        'mode' => 'payment',
        'payment_method_types' => ['card'],
        'success_url' => stripe_success_url(),
        'cancel_url' => stripe_cancel_url(),
        'client_reference_id' => (string) $checkout['user_id'],
        'customer_email' => (string) $checkout['user_mail'],
        'metadata' => $metadata,
        'line_items' => $lineItems,
    ]);

    $checkoutUrl = trim((string) ($session['url'] ?? ''));
    $sessionId = trim((string) ($session['id'] ?? ''));
    if ($checkoutUrl === '' || $sessionId === '') {
        throw new RuntimeException('Session Stripe invalide.');
    }

    return [
        'id' => $sessionId,
        'url' => $checkoutUrl,
        'status' => (string) ($session['status'] ?? ''),
        'payment_status' => (string) ($session['payment_status'] ?? ''),
    ];
}

function stripe_reference_from_session_id(string $sessionId): string
{
    $sanitized = strtoupper((string) preg_replace('/[^A-Z0-9]/', '', $sessionId));
    if ($sanitized === '') {
        $sanitized = strtoupper(base_convert((string) time(), 10, 36));
    }

    $reference = 'CMD-STRIPE-' . $sanitized;
    return substr($reference, 0, 64);
}

function stripe_sanitize_checkout_session_id(string $sessionId): string
{
    $sessionId = trim($sessionId);
    if ($sessionId === '' || preg_match('/^cs_[A-Za-z0-9_]+$/', $sessionId) !== 1) {
        throw new InvalidArgumentException('Session Stripe invalide.');
    }

    return $sessionId;
}

function stripe_retrieve_checkout_session(string $sessionId): array
{
    $safeSessionId = stripe_sanitize_checkout_session_id($sessionId);

    return stripe_api_request('GET', '/v1/checkout/sessions/' . rawurlencode($safeSessionId));
}

function stripe_order_id_from_payload(array $order): int|string|null
{
    $orderId = $order['order_id'] ?? $order['id'] ?? null;
    if (is_int($orderId)) {
        return $orderId;
    }
    if (is_string($orderId)) {
        $trimmed = trim($orderId);
        if ($trimmed !== '') {
            return $trimmed;
        }
    }

    return null;
}

function stripe_parse_webhook_event(string $rawBody, string $signatureHeader): array
{
    $webhookSecret = stripe_webhook_secret();
    if ($webhookSecret === '') {
        throw new DomainException('STRIPE_WEBHOOK_SECRET manquant.', 500);
    }

    if (trim($signatureHeader) === '') {
        throw new DomainException('Signature Stripe absente.', 400);
    }

    $timestamp = 0;
    $signatures = [];
    foreach (explode(',', $signatureHeader) as $part) {
        $entry = explode('=', trim($part), 2);
        if (count($entry) !== 2) {
            continue;
        }
        $name = trim($entry[0]);
        $value = trim($entry[1]);
        if ($name === 't') {
            $timestamp = (int) $value;
        } elseif ($name === 'v1' && $value !== '') {
            $signatures[] = $value;
        }
    }

    if ($timestamp <= 0 || $signatures === []) {
        throw new DomainException('Signature Stripe invalide.', 400);
    }

    $tolerance = stripe_webhook_tolerance_seconds();
    if (abs(time() - $timestamp) > $tolerance) {
        throw new DomainException('Webhook Stripe expire (horodatage).', 400);
    }

    $signedPayload = $timestamp . '.' . $rawBody;
    $expected = hash_hmac('sha256', $signedPayload, $webhookSecret);
    $valid = false;
    foreach ($signatures as $signature) {
        if (hash_equals($expected, $signature)) {
            $valid = true;
            break;
        }
    }

    if (!$valid) {
        throw new DomainException('Signature Stripe non valide.', 400);
    }

    $event = json_decode($rawBody, true);
    if (!is_array($event)) {
        throw new DomainException('Webhook Stripe JSON invalide.', 400);
    }

    return $event;
}

function stripe_order_payload_from_session(array $session): array
{
    $metadata = $session['metadata'] ?? [];
    if (!is_array($metadata)) {
        $metadata = [];
    }

    $userId = (int) ($metadata['camparts_user_id'] ?? 0);
    if ($userId <= 0) {
        throw new RuntimeException('Metadata Stripe utilisateur invalide.');
    }

    $lines = stripe_decode_lines_metadata($metadata);
    if ($lines === []) {
        throw new RuntimeException('Metadata Stripe lignes invalide.');
    }

    $payloadLines = [];
    foreach ($lines as $articleId => $qty) {
        $payloadLines[] = [
            'article_id' => (int) $articleId,
            'qty' => (int) $qty,
        ];
    }

    return [
        'utilisateur' => [
            'id' => $userId,
            'mail' => trim((string) ($metadata['camparts_user_mail'] ?? '')),
            'prenom' => trim((string) ($metadata['camparts_user_first_name'] ?? '')),
            'nom' => trim((string) ($metadata['camparts_user_last_name'] ?? '')),
        ],
        'adresse' => trim((string) ($metadata['camparts_address'] ?? '')),
        'lignes' => $payloadLines,
    ];
}

function stripe_handle_successful_checkout(array $session): array
{
    $sessionId = trim((string) ($session['id'] ?? ''));
    if ($sessionId === '') {
        throw new RuntimeException('Session Stripe invalide (id manquant).');
    }

    $paymentStatus = strtolower(trim((string) ($session['payment_status'] ?? '')));
    if ($paymentStatus !== '' && $paymentStatus !== 'paid') {
        return [
            'handled' => false,
            'reason' => 'payment_not_paid',
            'session_id' => $sessionId,
        ];
    }

    $reference = stripe_reference_from_session_id($sessionId);
    $existing = order_get_by_reference($reference);
    if (is_array($existing)) {
        $existingOrderId = stripe_order_id_from_payload($existing);
        if ($existingOrderId !== null) {
            try {
                $existing = order_update_status($existingOrderId, 'validee');
            } catch (Throwable) {
                // Keep existing order payload if status update is not supported.
            }
        }

        return [
            'handled' => true,
            'duplicate' => true,
            'session_id' => $sessionId,
            'order' => $existing,
        ];
    }

    $payload = stripe_order_payload_from_session($session);
    $payload['reference'] = $reference;

    try {
        $order = order_create($payload);
    } catch (Throwable $exception) {
        if (stripos($exception->getMessage(), 'duplicate entry') !== false) {
            $existing = order_get_by_reference($reference);
            if (is_array($existing)) {
                return [
                    'handled' => true,
                    'duplicate' => true,
                    'session_id' => $sessionId,
                    'order' => $existing,
                ];
            }
        }
        throw $exception;
    }

    $createdOrderId = stripe_order_id_from_payload($order);
    if ($createdOrderId !== null) {
        try {
            $order = order_update_status($createdOrderId, 'validee');
        } catch (Throwable) {
            // Keep order creation successful even if status column is not available.
        }
    }

    $storedOrder = order_get_by_reference($reference);
    if (is_array($storedOrder)) {
        $order = $storedOrder;
    }

    $notifications = send_order_notification_emails($order, $payload);
    $lowStockAlert = send_low_stock_alert_emails($order);
    $notifications['low_stock_sent'] = (bool) ($lowStockAlert['sent'] ?? false);
    $notifications['low_stock_items'] = is_array($lowStockAlert['items'] ?? null)
        ? $lowStockAlert['items']
        : [];

    return [
        'handled' => true,
        'duplicate' => false,
        'session_id' => $sessionId,
        'order' => $order,
        'notifications' => $notifications,
    ];
}

function stripe_confirm_checkout_for_user(string $sessionId, array $currentUser): array
{
    $userId = (int) ($currentUser['id'] ?? 0);
    if ($userId <= 0) {
        throw new RuntimeException('Connexion requise pour confirmer le paiement Stripe.');
    }

    $session = stripe_retrieve_checkout_session($sessionId);
    $metadata = $session['metadata'] ?? [];
    if (!is_array($metadata)) {
        $metadata = [];
    }

    $metadataUserId = (int) ($metadata['camparts_user_id'] ?? 0);
    if ($metadataUserId > 0 && $metadataUserId !== $userId) {
        throw new DomainException('Session Stripe non autorisee pour cet utilisateur.', 403);
    }

    $sessionMail = strtolower(trim((string) (
        $session['customer_email']
        ?? $metadata['camparts_user_mail']
        ?? ''
    )));
    $currentUserMail = strtolower(trim((string) ($currentUser['mail'] ?? '')));
    if (
        $metadataUserId <= 0
        && $sessionMail !== ''
        && $currentUserMail !== ''
        && $sessionMail !== $currentUserMail
    ) {
        throw new DomainException('Session Stripe non autorisee pour cet utilisateur.', 403);
    }

    return stripe_handle_successful_checkout($session);
}

function stripe_handle_webhook_event(array $event): array
{
    $type = trim((string) ($event['type'] ?? ''));
    $data = $event['data'] ?? [];
    $object = is_array($data) ? ($data['object'] ?? null) : null;
    $session = is_array($object) ? $object : [];

    if ($type === '') {
        return ['handled' => false, 'reason' => 'missing_type'];
    }

    if (in_array($type, ['checkout.session.completed', 'checkout.session.async_payment_succeeded'], true)) {
        return stripe_handle_successful_checkout($session);
    }

    if (in_array($type, ['checkout.session.async_payment_failed', 'payment_intent.payment_failed'], true)) {
        return [
            'handled' => true,
            'ignored' => true,
            'event_type' => $type,
        ];
    }

    return [
        'handled' => false,
        'ignored' => true,
        'event_type' => $type,
    ];
}
