<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Clielo_CinetPay {

    const API_INIT  = 'https://api-checkout.cinetpay.com/v2/payment';
    const API_CHECK = 'https://api-checkout.cinetpay.com/v2/payment/check';

    public static function init(): void {
        // Webhook toujours enregistré (serveur → serveur, sans auth)
        add_action( 'wp_ajax_nopriv_clielo_cinetpay_webhook', [ __CLASS__, 'handle_webhook_ajax' ] );
        add_action( 'wp_ajax_clielo_cinetpay_webhook',        [ __CLASS__, 'handle_webhook_ajax' ] );

        if ( ! clielo_is_premium() ) {
            return;
        }

        add_action( 'admin_init', [ __CLASS__, 'register_settings' ] );

        if ( self::is_enabled() ) {
            add_action( 'wp_ajax_clielo_cinetpay_checkout', [ __CLASS__, 'ajax_create_checkout' ] );
            add_action( 'template_redirect', [ __CLASS__, 'handle_return_redirect' ] );
        }
    }

    /* ─── Settings ─────────────────────────────────────────── */

    public static function get_settings(): array {
        $defaults = [
            'enabled'      => '0',
            'mode'         => 'test',
            'api_key'      => '',
            'site_id'      => '',
            'test_api_key' => '',
            'test_site_id' => '',
            'currency'     => '',
            'channels'     => 'ALL',
        ];
        return wp_parse_args( get_option( 'clielo_cinetpay_settings', [] ), $defaults );
    }

    public static function is_enabled(): bool {
        if ( ! clielo_is_premium() ) {
            return false;
        }
        $s     = self::get_settings();
        $creds = self::get_credentials( $s );
        return $s['enabled'] === '1' && ! empty( $creds['api_key'] ) && ! empty( $creds['site_id'] );
    }

    public static function get_credentials( array $s = [] ): array {
        if ( empty( $s ) ) {
            $s = self::get_settings();
        }
        if ( ( $s['mode'] ?? 'live' ) === 'test' ) {
            return [
                'api_key' => $s['test_api_key'] ?? '',
                'site_id' => $s['test_site_id'] ?? '',
            ];
        }
        return [
            'api_key' => $s['api_key'] ?? '',
            'site_id' => $s['site_id'] ?? '',
        ];
    }

    public static function get_webhook_url(): string {
        return admin_url( 'admin-ajax.php?action=clielo_cinetpay_webhook' );
    }

    public static function register_settings(): void {
        register_setting( 'clielo_cinetpay_settings', 'clielo_cinetpay_settings', [
            'type'              => 'array',
            'sanitize_callback' => [ __CLASS__, 'sanitize_settings' ],
        ] );
    }

    public static function sanitize_settings( $input ): array {
        $clean                 = [];
        $clean['enabled']      = ! empty( $input['enabled'] ) ? '1' : '0';
        $clean['mode']         = in_array( $input['mode'] ?? '', [ 'test', 'live' ], true ) ? $input['mode'] : 'test';
        $clean['api_key']      = sanitize_text_field( $input['api_key'] ?? '' );
        $clean['site_id']      = sanitize_text_field( $input['site_id'] ?? '' );
        $clean['test_api_key'] = sanitize_text_field( $input['test_api_key'] ?? '' );
        $clean['test_site_id'] = sanitize_text_field( $input['test_site_id'] ?? '' );
        $clean['currency']     = '';  // Devise lue depuis Réglages → Devise globale
        $clean['channels']     = in_array( $input['channels'] ?? '', [ 'ALL', 'MOBILE_MONEY', 'CREDIT_CARD' ], true )
            ? $input['channels']
            : 'ALL';
        return $clean;
    }

    /* ─── Admin settings page ──────────────────────────────── */

    public static function render_tab_content(): void {
        $s     = self::get_settings();
        $color = esc_attr( Clielo_Admin::get_color() );
        ?>
        <form method="post" action="options.php">
            <?php settings_fields( 'clielo_cinetpay_settings' ); ?>

            <?php // ── Activation ── ?>
            <div style="background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:20px 24px;margin-bottom:20px">
                <label style="display:flex;align-items:center;gap:12px;cursor:pointer">
                    <input type="checkbox" name="clielo_cinetpay_settings[enabled]" value="1" <?php checked( $s['enabled'], '1' ); ?> style="width:18px;height:18px" />
                    <div>
                        <strong style="font-size:15px"><?php esc_html_e( 'Activer les paiements CinetPay', 'clielo' ); ?></strong>
                        <div style="color:#666;font-size:13px;margin-top:2px"><?php esc_html_e( 'Mobile Money (Orange Money, MTN, Moov, Wave) + cartes bancaires pour l\'Afrique.', 'clielo' ); ?></div>
                    </div>
                </label>
            </div>

            <?php // ── Mode ── ?>
            <div style="background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:20px 24px;margin-bottom:20px">
                <h2 style="font-size:15px;margin:0 0 16px 0"><?php esc_html_e( 'Mode', 'clielo' ); ?></h2>
                <select name="clielo_cinetpay_settings[mode]" style="width:100%;max-width:300px;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px">
                    <option value="test" <?php selected( $s['mode'], 'test' ); ?>><?php esc_html_e( 'Test (sandbox)', 'clielo' ); ?></option>
                    <option value="live" <?php selected( $s['mode'], 'live' ); ?>><?php esc_html_e( 'Production (live)', 'clielo' ); ?></option>
                </select>
                <?php if ( ( $s['mode'] ?? 'test' ) === 'test' ) : ?>
                    <p style="margin:8px 0 0;color:#f59e0b;font-size:13px">&#9888; <?php esc_html_e( 'Mode test actif — aucun paiement réel ne sera effectué.', 'clielo' ); ?></p>
                <?php endif; ?>
            </div>

            <?php // ── Clés API ── ?>
            <div style="background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:20px 24px;margin-bottom:20px">
                <h2 style="font-size:15px;margin:0 0 4px 0"><?php esc_html_e( 'Clés API Test', 'clielo' ); ?></h2>
                <p style="font-size:13px;color:#666;margin:0 0 16px 0"><?php esc_html_e( 'Clés sandbox depuis votre espace CinetPay (mode test).', 'clielo' ); ?></p>
                <?php self::render_field( 'test_api_key', __( 'API Key (test)', 'clielo' ), $s['test_api_key'] ?? '', true ); ?>
                <?php self::render_field( 'test_site_id', __( 'Site ID (test)', 'clielo' ), $s['test_site_id'] ?? '' ); ?>

                <h2 style="font-size:15px;margin:24px 0 4px 0"><?php esc_html_e( 'Clés API Production', 'clielo' ); ?></h2>
                <p style="font-size:13px;color:#666;margin:0 0 16px 0">
                    <?php
                    printf(
                        /* translators: %s: HTML link to CinetPay dashboard */
                        esc_html__( 'Récupérez vos clés dans votre %s.', 'clielo' ),
                        '<a href="https://dashboard.cinetpay.com" target="_blank" rel="noopener" style="color:' . esc_attr( $color ) . ';font-weight:600">espace CinetPay</a>'
                    );
                    ?>
                </p>
                <?php self::render_field( 'api_key', __( 'API Key (production)', 'clielo' ), $s['api_key'], true ); ?>
                <?php self::render_field( 'site_id', __( 'Site ID (production)', 'clielo' ), $s['site_id'] ); ?>
            </div>

            <?php // ── Webhook ── ?>
            <div style="background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:20px 24px;margin-bottom:20px">
                <h2 style="font-size:15px;margin:0 0 16px 0"><?php esc_html_e( 'Notify URL (Webhook)', 'clielo' ); ?></h2>
                <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px 18px;margin-bottom:16px;font-size:13px;color:#475569;line-height:1.7">
                    <?php esc_html_e( 'Dans votre espace CinetPay → Services → IPN (Notify URL), collez l\'URL ci-dessous. CinetPay l\'appellera à chaque paiement pour confirmer la transaction côté serveur.', 'clielo' ); ?>
                </div>
                <div style="display:flex;gap:8px">
                    <input type="text" readonly value="<?php echo esc_attr( self::get_webhook_url() ); ?>" id="clielo-cp-webhook-url"
                           style="flex:1;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px;background:#f9fafb;font-family:monospace;font-size:12px" />
                    <button type="button"
                            onclick="navigator.clipboard.writeText(document.getElementById('clielo-cp-webhook-url').value);this.textContent='<?php esc_attr_e( 'Copié !', 'clielo' ); ?>';setTimeout(()=>{this.textContent='<?php esc_attr_e( 'Copier', 'clielo' ); ?>'},2000)"
                            style="padding:8px 16px;border:1px solid #d0d5dd;border-radius:6px;background:#fff;cursor:pointer;font-size:13px">
                        <?php esc_html_e( 'Copier', 'clielo' ); ?>
                    </button>
                </div>
            </div>

            <?php // ── Devise ── ?>
            <?php
            $global_currency    = Clielo_Admin::get_currency();
            $cp_supported       = [ 'XOF', 'XAF', 'CDF', 'GNF', 'USD' ];
            $cp_currency_ok     = in_array( $global_currency, $cp_supported, true );
            ?>
            <div style="background:<?php echo $cp_currency_ok ? '#f0f4ff' : '#fff8e1'; ?>;border:1px solid <?php echo $cp_currency_ok ? '#c7d7fa' : '#f59e0b'; ?>;border-radius:10px;padding:16px 24px;margin-bottom:20px">
                <p style="margin:0;font-size:13px;color:#374151">
                    <strong><?php esc_html_e( 'Devise :', 'clielo' ); ?></strong>
                    <?php echo esc_html( $global_currency ); ?>
                    <?php if ( $cp_currency_ok ) : ?>
                        <span style="color:#16a34a">✓ <?php esc_html_e( 'Compatible CinetPay', 'clielo' ); ?></span>
                    <?php else : ?>
                        <span style="color:#b45309">⚠️ <?php esc_html_e( 'Non compatible CinetPay. CinetPay accepte :', 'clielo' ); ?> <?php echo esc_html( implode( ', ', $cp_supported ) ); ?></span>
                    <?php endif; ?>
                    — <?php esc_html_e( 'Configurez la devise dans', 'clielo' ); ?>
                    <a href="<?php echo esc_url( admin_url( 'admin.php?page=clielo-settings' ) ); ?>"><?php esc_html_e( 'Réglages → Devise', 'clielo' ); ?></a>.
                </p>
            </div>

            <?php // ── Canaux de paiement ── ?>
            <div style="background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:20px 24px;margin-bottom:20px">
                <h2 style="font-size:15px;margin:0 0 16px 0"><?php esc_html_e( 'Canaux de paiement', 'clielo' ); ?></h2>
                <select name="clielo_cinetpay_settings[channels]" style="width:100%;max-width:300px;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px">
                    <option value="ALL" <?php selected( $s['channels'], 'ALL' ); ?>><?php esc_html_e( 'Tous (Mobile Money + Cartes)', 'clielo' ); ?></option>
                    <option value="MOBILE_MONEY" <?php selected( $s['channels'], 'MOBILE_MONEY' ); ?>><?php esc_html_e( 'Mobile Money uniquement', 'clielo' ); ?></option>
                    <option value="CREDIT_CARD" <?php selected( $s['channels'], 'CREDIT_CARD' ); ?>><?php esc_html_e( 'Cartes bancaires uniquement', 'clielo' ); ?></option>
                </select>
            </div>

            <?php submit_button( __( 'Enregistrer', 'clielo' ) ); ?>
        </form>
        <?php
    }

    public static function render_settings_page(): void {
        $color = esc_attr( Clielo_Admin::get_color() );
        ?>
        <div class="wrap" style="max-width:750px">
            <h1 style="display:flex;align-items:center;gap:10px;margin-bottom:24px">
                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="<?php echo $color; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>
                <?php esc_html_e( 'Paiement CinetPay', 'clielo' ); ?>
            </h1>
            <?php self::render_tab_content(); ?>
        </div>
        <?php
    }

    private static function render_field( string $key, string $label, string $value, bool $secret = false ): void {
        $type = $secret ? 'password' : 'text';
        ?>
        <div style="margin-bottom:16px">
            <label style="display:block;font-size:13px;font-weight:600;color:#555;margin-bottom:6px"><?php echo esc_html( $label ); ?></label>
            <input type="<?php echo esc_attr( $type ); ?>" name="clielo_cinetpay_settings[<?php echo esc_attr( $key ); ?>]"
                   value="<?php echo esc_attr( $value ); ?>"
                   style="width:100%;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px;font-family:monospace;font-size:13px" />
        </div>
        <?php
    }

    /* ─── AJAX : Initialiser le paiement CinetPay ──────────── */

    public static function ajax_create_checkout(): void {
        check_ajax_referer( 'clielo_nonce', 'nonce' );

        if ( ! is_user_logged_in() ) {
            wp_send_json_error( [ 'message' => __( 'Non connecté.', 'clielo' ) ], 403 );
        }

        if ( current_user_can( 'manage_options' ) ) {
            wp_send_json_error( [ 'message' => __( 'Les administrateurs ne peuvent pas commander.', 'clielo' ) ], 403 );
        }

        $post_id           = absint( $_POST['post_id'] ?? 0 );
        $selected_pack_idx = absint( $_POST['selected_pack'] ?? 0 );
        $selected_indices  = array_map( 'absint', (array) json_decode( sanitize_text_field( wp_unslash( $_POST['selected_indices'] ?? '[]' ) ), true ) );

        if ( ! $post_id || ! is_array( $selected_indices ) ) {
            wp_send_json_error( [ 'message' => __( 'Données manquantes.', 'clielo' ) ], 400 );
        }

        $post = get_post( $post_id );
        if ( ! $post || $post->post_type !== Clielo_Admin::get_post_type() ) {
            wp_send_json_error( [ 'message' => __( 'Post invalide.', 'clielo' ) ], 400 );
        }

        $client_id = get_current_user_id();

        $existing = Clielo_Orders::get_order_for_client( $post_id, $client_id );
        if ( $existing && ! in_array( $existing->status, [ Clielo_Orders::STATUS_PENDING, Clielo_Orders::STATUS_ACCEPTED ], true ) ) {
            wp_send_json_error( [ 'message' => __( 'Une commande est déjà en cours.', 'clielo' ) ], 403 );
        }

        $packs    = Clielo_Options::get_packs( $post_id );
        $all_opts = Clielo_Options::get_options( $post_id );

        if ( empty( $packs ) || ! isset( $packs[ $selected_pack_idx ] ) ) {
            wp_send_json_error( [ 'message' => __( 'Pack invalide.', 'clielo' ) ], 400 );
        }

        $base_offer = $packs[ $selected_pack_idx ];
        $selected   = [];
        foreach ( $selected_indices as $idx ) {
            if ( isset( $all_opts[ $idx ] ) ) {
                $selected[] = $all_opts[ $idx ];
            }
        }

        $total_ht    = floatval( $base_offer['price'] ?? 0 );
        $total_delay = absint( $base_offer['delay'] ?? 0 );
        foreach ( $selected as $opt ) {
            $total_ht    += floatval( $opt['price'] ?? 0 );
            $total_delay += absint( $opt['delay'] ?? 0 );
        }

        $tax_rate       = floatval( Clielo_Invoices::get_settings()['tax_rate'] ?? 0 );
        $full_total_ttc = $tax_rate > 0 ? round( $total_ht * ( 1 + $tax_rate / 100 ), 2 ) : $total_ht;

        // Mode de paiement configuré sur le service
        $payment_mode       = sanitize_text_field( get_post_meta( $post_id, '_clielo_payment_mode', true ) ?: 'single' );
        $installments_count = absint( get_post_meta( $post_id, '_clielo_installments_count', true ) ?: 1 );

        // Montant à encaisser maintenant (upfront uniquement)
        if ( $payment_mode === 'monthly' ) {
            $upfront_ttc = Clielo_Payments::get_monthly_fee( $full_total_ttc, $installments_count );
        } elseif ( in_array( $payment_mode, [ 'deposit', 'installments' ], true ) ) {
            $upfront_ttc = Clielo_Payments::get_upfront_amount( $full_total_ttc, $payment_mode );
        } else {
            $payment_mode = 'single';
            $upfront_ttc  = $full_total_ttc;
        }

        $settings = self::get_settings();
        $currency = Clielo_Admin::get_currency();

        // XOF/XAF/CDF/GNF : entiers (pas de centimes). USD : centimes.
        $no_cents   = in_array( $currency, [ 'XOF', 'XAF', 'CDF', 'GNF' ], true );
        $amount_int = $no_cents ? (int) round( $upfront_ttc ) : (int) round( $upfront_ttc * 100 );

        // Transaction ID unique (max 50 chars, préfixe 'cl' = indicateur CinetPay)
        $transaction_id = 'cl' . $post_id . 'c' . $client_id . 't' . time();

        // Stocker les métadonnées 24h (idempotence webhook + fallback retour client)
        set_transient( 'clielo_cp_' . $transaction_id, [
            'type'               => 'order',
            'post_id'            => $post_id,
            'client_id'          => $client_id,
            'selected_pack_idx'  => $selected_pack_idx,
            'selected_indices'   => $selected_indices,
            'base_offer'         => $base_offer,
            'selected'           => $selected,
            'full_total_ttc'     => $full_total_ttc,
            'upfront_ttc'        => $upfront_ttc,
            'total_delay'        => $total_delay,
            'payment_mode'       => $payment_mode,
            'installments_count' => $installments_count,
        ], DAY_IN_SECONDS );

        $user         = wp_get_current_user();
        $service_page = get_permalink( $post_id );

        if ( $payment_mode !== 'single' ) {
            /* translators: 1: service title 2: upfront amount 3: currency */
            $description = sprintf( __( 'Acompte — %1$s (%2$s %3$s)', 'clielo' ), get_the_title( $post_id ), number_format( $upfront_ttc, 0, ',', ' ' ), $currency );
        } else {
            /* translators: %s: service post title */
            $description = sprintf( __( 'Commande — %s', 'clielo' ), get_the_title( $post_id ) );
        }

        $creds          = self::get_credentials( $settings );
        $first_name     = sanitize_text_field( $user->first_name ?: $user->display_name );
        $raw_last       = sanitize_text_field( $user->last_name );
        $last_name      = strlen( $raw_last ) >= 2 ? $raw_last : ( strlen( $user->display_name ) >= 2 ? $user->display_name : '--' );
        $body = wp_json_encode( [
            'apikey'                => $creds['api_key'],
            'site_id'               => $creds['site_id'],
            'transaction_id'        => $transaction_id,
            'amount'                => $amount_int,
            'currency'              => $currency,
            'description'           => $description,
            'return_url'            => add_query_arg( [ 'cinetpay_success' => '1', 'cp_txn' => $transaction_id ], $service_page ),
            'notify_url'            => self::get_webhook_url(),
            'channels'              => $settings['channels'],
            'customer_id'           => (string) $client_id,
            'customer_name'         => $first_name,
            'customer_surname'      => $last_name,
            'customer_email'        => sanitize_email( $user->user_email ),
            'customer_phone_number' => '',
            'customer_address'      => 'N/A',
            'customer_city'         => 'N/A',
            'customer_country'      => 'CI',
            'customer_state'        => 'CI',
            'customer_zip_code'     => '00000',
        ] );

        $response = wp_remote_post( self::API_INIT, [
            'headers'     => [ 'Content-Type' => 'application/json' ],
            'body'        => $body,
            'timeout'     => 30,
            'data_format' => 'body',
        ] );

        if ( is_wp_error( $response ) ) {
            wp_send_json_error( [ 'message' => $response->get_error_message() ], 500 );
        }

        $data = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( ( $data['code'] ?? '' ) !== '201' ) {
            wp_send_json_error( [ 'message' => $data['description'] ?? $data['message'] ?? __( 'Erreur CinetPay.', 'clielo' ) ], 500 );
        }

        $payment_url = $data['data']['payment_url'] ?? '';
        if ( ! $payment_url ) {
            wp_send_json_error( [ 'message' => __( 'URL de paiement introuvable.', 'clielo' ) ], 500 );
        }

        wp_send_json_success( [ 'checkout_url' => $payment_url ] );
    }

    /* ─── Webhook CinetPay (Notify URL) ────────────────────── */

    public static function handle_webhook_ajax(): void {
        // phpcs:disable WordPress.Security.NonceVerification.Missing -- CinetPay webhook, nonce not applicable
        $transaction_id = sanitize_text_field( wp_unslash( $_POST['cpm_trans_id'] ?? '' ) );
        // phpcs:enable WordPress.Security.NonceVerification.Missing

        if ( ! $transaction_id ) {
            status_header( 400 );
            exit;
        }

        // Toujours vérifier côté serveur (ne jamais faire confiance aux données POST)
        if ( self::verify_payment( $transaction_id ) ) {
            self::process_completed_payment( $transaction_id );
        }

        status_header( 200 );
        header( 'Content-Type: application/json' );
        echo wp_json_encode( [ 'status' => 'ok' ] );
        exit;
    }

    /* ─── Retour client depuis CinetPay ─────────────────────── */

    public static function handle_return_redirect(): void {
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- CinetPay return URL
        if ( empty( $_GET['cinetpay_success'] ) ) {
            return;
        }

        $transaction_id = sanitize_text_field( wp_unslash( $_GET['cp_txn'] ?? '' ) );
        if ( ! $transaction_id ) {
            return;
        }

        // Si le transient n'existe plus, le webhook a déjà traité le paiement
        if ( ! get_transient( 'clielo_cp_' . $transaction_id ) ) {
            return;
        }

        if ( self::verify_payment( $transaction_id ) ) {
            self::process_completed_payment( $transaction_id );
        }
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    }

    /* ─── Vérification paiement via API CinetPay ────────────── */

    private static function verify_payment( string $transaction_id ): bool {
        $settings = self::get_settings();
        $creds    = self::get_credentials( $settings );

        $response = wp_remote_post( self::API_CHECK, [
            'headers'     => [ 'Content-Type' => 'application/json' ],
            'body'        => wp_json_encode( [
                'apikey'         => $creds['api_key'],
                'site_id'        => $creds['site_id'],
                'transaction_id' => $transaction_id,
            ] ),
            'timeout'     => 30,
            'data_format' => 'body',
        ] );

        if ( is_wp_error( $response ) ) {
            return false;
        }

        $data = json_decode( wp_remote_retrieve_body( $response ), true );

        // Code '00' + status 'ACCEPTED' = paiement confirmé
        return ( $data['code'] ?? '' ) === '00' && ( $data['data']['status'] ?? '' ) === 'ACCEPTED';
    }

    /* ─── Dispatch post-paiement ────────────────────────────── */

    private static function process_completed_payment( string $transaction_id ): void {
        $transient_key = 'clielo_cp_' . $transaction_id;
        $meta          = get_transient( $transient_key );

        if ( ! $meta ) {
            return; // Déjà traité ou expiré
        }

        // Supprimer immédiatement pour idempotence (webhook + fallback peuvent appeler en parallèle)
        delete_transient( $transient_key );

        $type = $meta['type'] ?? 'order';

        if ( $type === 'schedule' ) {
            self::process_schedule_payment( (int) ( $meta['schedule_id'] ?? 0 ) );
            return;
        }

        self::process_order_payment( $meta, $transaction_id );
    }

    /* ─── Traitement paiement commande (type='order') ───────── */

    private static function process_order_payment( array $meta, string $transaction_id ): void {
        $post_id            = (int) $meta['post_id'];
        $client_id          = (int) $meta['client_id'];
        $base_offer         = $meta['base_offer'];
        $selected           = $meta['selected'];
        $full_total_ttc     = floatval( $meta['full_total_ttc'] ?? $meta['total_price'] ?? 0 );
        $upfront_ttc        = floatval( $meta['upfront_ttc'] ?? $full_total_ttc );
        $total_delay        = (int) $meta['total_delay'];
        $payment_mode       = sanitize_text_field( $meta['payment_mode'] ?? 'single' );
        $installments_count = (int) ( $meta['installments_count'] ?? 0 );

        // Idempotence DB : vérifier qu'aucune commande active n'existe déjà
        global $wpdb;
        $table = Clielo_Orders::table_name();
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $exists = $wpdb->get_var( $wpdb->prepare(
            "SELECT id FROM {$table} WHERE post_id = %d AND client_id = %d AND status NOT IN ('accepted','quote') ORDER BY id DESC LIMIT 1",
            $post_id,
            $client_id
        ) );
        if ( $exists ) {
            return;
        }

        $deposit_percent = match ( $payment_mode ) {
            'deposit'      => 50,
            'installments' => 40,
            default        => 100,
        };

        $order_id = Clielo_Orders::create_order_paid(
            $post_id,
            $client_id,
            $base_offer,
            $selected,
            $full_total_ttc,
            $total_delay,
            $transaction_id,
            '',
            [
                'payment_mode'       => $payment_mode,
                'deposit_percent'    => $deposit_percent,
                'installments_count' => $installments_count,
            ],
            0, 0.0, 0.0, 0, 0.0, ''
        );

        if ( ! $order_id ) {
            return;
        }

        // Message récap dans le chat
        $s        = self::get_settings();
        $currency = Clielo_Admin::get_currency();
        $lines    = [];
        $lines[]  = "\xF0\x9F\x93\x8B " . __( 'Ma sélection :', 'clielo' );
        $lines[]  = "\xF0\x9F\x93\xA6 " . ( $base_offer['name'] ?? 'Pack' ) . ' — ' . number_format( floatval( $base_offer['price'] ?? 0 ), 0, ',', ' ' ) . ' ' . $currency;
        foreach ( $selected as $opt ) {
            $lines[] = "\xE2\x9C\x85 " . ( $opt['name'] ?? 'Option' ) . ' — ' . number_format( floatval( $opt['price'] ?? 0 ), 0, ',', ' ' ) . ' ' . $currency;
        }
        $lines[] = "\xF0\x9F\x92\xB0 " . __( 'Total :', 'clielo' ) . ' ' . number_format( $full_total_ttc, 0, ',', ' ' ) . ' ' . $currency;
        if ( $payment_mode !== 'single' ) {
            $lines[] = "\xF0\x9F\x92\xB3 " . __( 'Acompte payé via CinetPay :', 'clielo' ) . ' ' . number_format( $upfront_ttc, 0, ',', ' ' ) . ' ' . $currency;
        } else {
            $lines[] = "\xE2\x9C\x85 " . __( 'Payé via CinetPay (Mobile Money / Carte)', 'clielo' );
        }
        if ( $total_delay > 0 ) {
            $lines[] = "\xE2\x8F\xB0 " . __( 'Délai total', 'clielo' ) . ' : ' . $total_delay . ' ' . __( 'jour(s)', 'clielo' );
        }
        Clielo_DB::insert_message( $post_id, $client_id, implode( "\n", $lines ), $client_id );

        // Message système statut
        $order   = Clielo_Orders::get_order( $order_id );
        $message = Clielo_Orders::format_status_message( 'started', $order, '', '' );
        if ( $message ) {
            Clielo_DB::insert_message( $post_id, 0, $message, $client_id );
        }

        // Factures + échéancier
        if ( clielo_is_premium() && class_exists( 'Clielo_Invoices' ) ) {
            if ( $payment_mode === 'single' ) {
                Clielo_Invoices::on_order_accepted( $order_id, Clielo_Orders::STATUS_ACCEPTED, Clielo_Orders::STATUS_STARTED, $client_id );
            } else {
                // Créer l'échéancier
                Clielo_Payments::create_schedule_for_order( $order_id );
                // Facture de l'acompte (première ligne payée du schedule)
                $schedule = Clielo_Payments::get_schedule_for_order( $order_id );
                foreach ( $schedule as $sched_row ) {
                    if ( $sched_row->status === 'paid' ) {
                        Clielo_Invoices::create_partial_invoice(
                            $order_id,
                            floatval( $sched_row->amount_ttc ),
                            'acompte',
                            (int) $sched_row->id,
                            (int) $sched_row->installment_no
                        );
                        break;
                    }
                }
            }
        }

        do_action( 'clielo_order_created', $order_id, $post_id, $client_id );
        do_action( 'clielo_order_status_changed', $order_id, 'started', '', 0 );
    }

    /* ─── Traitement paiement échéancier (type='schedule') ─── */

    private static function process_schedule_payment( int $schedule_id ): void {
        if ( ! $schedule_id ) {
            return;
        }

        $row = Clielo_Payments::get_row( $schedule_id );
        if ( ! $row || $row->status === 'paid' ) {
            return; // Déjà traité
        }

        global $wpdb;
        $wpdb->update( Clielo_Payments::table_name(), [ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            'status'  => 'paid',
            'paid_at' => current_time( 'mysql' ),
        ], [ 'id' => $schedule_id ] );

        if ( class_exists( 'Clielo_Invoices' ) ) {
            $invoice_type = match ( $row->type ) {
                'deposit_balance' => 'solde',
                'installment'     => 'mensualite',
                default           => 'solde',
            };
            Clielo_Invoices::create_partial_invoice(
                (int) $row->order_id,
                floatval( $row->amount_ttc ),
                $invoice_type,
                $schedule_id,
                (int) $row->installment_no
            );
        }
    }

    /* ─── Envoi d'un lien de paiement CinetPay (échéancier) ── */

    public static function send_payment_link( int $schedule_id, int $acting_user_id ): bool {
        $row = Clielo_Payments::get_row( $schedule_id );
        if ( ! $row || $row->status === 'paid' ) {
            return false;
        }

        $order = Clielo_Orders::get_order( (int) $row->order_id );
        if ( ! $order ) {
            return false;
        }

        $settings = self::get_settings();
        if ( ! self::is_enabled() ) {
            return false;
        }

        $transaction_id = 'cls' . $schedule_id . 't' . time();
        $label          = Clielo_Payments::get_type_label( $row->type, (int) $row->installment_no );
        $currency       = Clielo_Admin::get_currency();
        $no_cents       = in_array( $currency, [ 'XOF', 'XAF', 'CDF', 'GNF' ], true );
        $amount_int     = $no_cents
            ? (int) round( floatval( $row->amount_ttc ) )
            : (int) round( floatval( $row->amount_ttc ) * 100 );

        set_transient( 'clielo_cp_' . $transaction_id, [
            'type'        => 'schedule',
            'schedule_id' => $schedule_id,
        ], DAY_IN_SECONDS );

        $user         = get_userdata( (int) $order->client_id );
        $service_page = get_permalink( (int) $order->post_id );

        $creds      = self::get_credentials( $settings );
        $first_name  = $user ? sanitize_text_field( $user->first_name ?: $user->display_name ) : '--';
        $raw_last2   = $user ? sanitize_text_field( $user->last_name ) : '';
        $last_name   = strlen( $raw_last2 ) >= 2 ? $raw_last2 : ( $user && strlen( $user->display_name ) >= 2 ? $user->display_name : '--' );
        $body = wp_json_encode( [
            'apikey'                => $creds['api_key'],
            'site_id'               => $creds['site_id'],
            'transaction_id'        => $transaction_id,
            'amount'                => $amount_int,
            'currency'              => $currency,
            /* translators: 1: payment type label (e.g. "Mensualité 2") 2: service title */
            'description'           => sprintf( __( '%1$s — %2$s', 'clielo' ), $label, get_the_title( (int) $order->post_id ) ),
            'return_url'            => add_query_arg( [ 'cinetpay_success' => '1', 'cp_txn' => $transaction_id ], $service_page ),
            'notify_url'            => self::get_webhook_url(),
            'channels'              => $settings['channels'],
            'customer_id'           => (string) $order->client_id,
            'customer_name'         => $first_name,
            'customer_surname'      => $last_name,
            'customer_email'        => $user ? sanitize_email( $user->user_email ) : '',
            'customer_phone_number' => '',
            'customer_address'      => 'N/A',
            'customer_city'         => 'N/A',
            'customer_country'      => 'CI',
            'customer_state'        => 'CI',
            'customer_zip_code'     => '00000',
        ] );

        $response = wp_remote_post( self::API_INIT, [
            'headers'     => [ 'Content-Type' => 'application/json' ],
            'body'        => $body,
            'timeout'     => 30,
            'data_format' => 'body',
        ] );

        if ( is_wp_error( $response ) ) {
            delete_transient( 'clielo_cp_' . $transaction_id );
            return false;
        }

        $data = json_decode( wp_remote_retrieve_body( $response ), true );
        if ( ( $data['code'] ?? '' ) !== '201' ) {
            delete_transient( 'clielo_cp_' . $transaction_id );
            return false;
        }

        $checkout_url = $data['data']['payment_url'] ?? '';
        if ( ! $checkout_url ) {
            delete_transient( 'clielo_cp_' . $transaction_id );
            return false;
        }

        global $wpdb;
        $wpdb->update( Clielo_Payments::table_name(), [ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            'status'            => 'sent',
            'stripe_session_id' => $transaction_id,
            'checkout_url'      => $checkout_url,
            'sent_at'           => current_time( 'mysql' ),
        ], [ 'id' => $schedule_id ] );

        // Envoyer l'URL dans le chat (marker [SF_SCHED:{id}] pour le JS)
        $amount_fmt = number_format( floatval( $row->amount_ttc ), 0, ',', ' ' );
        $msg  = sprintf( "[SF_SCHED:%d]\n", $schedule_id );
        $msg .= sprintf( "--- %s ---\n", $label );
        /* translators: 1: formatted amount 2: currency code */
        $msg .= sprintf( __( '💳 Montant : %1$s %2$s', 'clielo' ), $amount_fmt, $currency ) . "\n";
        $msg .= $checkout_url;

        Clielo_DB::insert_message( (int) $order->post_id, 0, $msg, (int) $order->client_id );

        if ( class_exists( 'Clielo_Notifications' ) ) {
            Clielo_Notifications::on_payment_link_sent( $row, $order, $checkout_url );
        }

        return true;
    }
}
