<?php

namespace BWDK\API;

use BWDK\Support\Constant;
use WC_Order_Query;
use WP_REST_Request;
use WP_REST_Response;

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

/**
 * DigiExpress webhook endpoint: receives POST when shipment events change.
 * Requires X-BWDK-Webhook-Secret; verified order data from BWDK API (source of truth).
 * Idempotent via event_message_id. HPOS compatible.
 */
class DigiExpress_Webhook {

    const OPTION_WEBHOOK_SECRET       = 'bwdk_webhook_secret';
    const META_DX_LAST_WEBHOOK_RAW    = '_bwdk_dx_last_webhook_raw';
    const META_DX_PROCESSED_EVENT_IDS = '_bwdk_dx_processed_event_ids';
    const META_SELECTED_SHIPPING_METHOD = '_bwdk_selected_shipping_method';
    const META_SELECTED_SHIPPING_DATA  = '_bwdk_selected_shipping_data';
    const META_DX_DETAIL              = '_bwdk_dx_detail';
    const META_DX_HTML_LABEL          = '_bwdk_dx_html_label';
    const META_DX_PDF_LABEL           = '_bwdk_dx_pdf_label';
    const META_DX_SHIPMENT_CODE       = '_bwdk_dx_shipment_code';
    const META_DX_EVENT_ID            = '_bwdk_dx_event_id';
    const META_DX_OCCURRED_AT         = '_bwdk_dx_occurred_at';
    const META_DX_PANEL_MESSAGE       = '_bwdk_dx_panel_message';
    const META_DX_LAST_EVENT_ID       = '_bwdk_dx_last_event_id';
    const META_DX_LAST_MESSAGE        = '_bwdk_dx_last_message';
    const META_DX_LAST_STATUS         = '_bwdk_dx_last_status';
    const META_DX_HISTORY             = '_bwdk_dx_history';
    const META_DX_SHIPPING_STATUS     = '_bwdk_dx_shipping_status';

    const RATE_LIMIT_TRANSIENT_PREFIX = 'bwdk_dx_webhook_ip_';
    const RATE_LIMIT_PER_MINUTE       = 60;
    const PROCESSED_IDS_MAX           = 50;

    /** Event IDs that map to WC status completed */
    const EVENT_IDS_COMPLETED = [ 18, 19 ];
    /** Event IDs that map to WC status on-hold */
    const EVENT_IDS_ON_HOLD = [ 6, 7, 20, 21, 22, 23, 24, 25 ];
    /** Event IDs that map to WC status failed */
    const EVENT_IDS_FAILED = [ 4, 5 ];

    public function register(): void {
        add_action( 'rest_api_init', [ $this, 'register_route' ] );
        $this->ensure_webhook_secret();
    }

    /**
     * Ensure every installation has a webhook secret (option bwdk_webhook_secret).
     */
    public function ensure_webhook_secret(): void {
        $existing = get_option( self::OPTION_WEBHOOK_SECRET, '' );
        if ( is_string( $existing ) && $existing !== '' ) {
            return;
        }
        $secret = wp_generate_password( 32, false, false );
        update_option( self::OPTION_WEBHOOK_SECRET, $secret, true );
    }

    public function register_route(): void {
        register_rest_route( 'bwdk/v1', '/digiexpress-webhook', [
            'methods'             => 'POST',
            'callback'            => [ $this, 'handle_digiexpress_webhook' ],
            'permission_callback' => '__return_true',
        ] );
    }

    /**
     * Handle DigiExpress webhook: require secret header, verify via BWDK API, sync meta, idempotency.
     */
    public function handle_digiexpress_webhook( WP_REST_Request $request ): WP_REST_Response {
        error_log('DX WEBHOOK ENTRY ENTERED');
        error_log('DX PATCH VERSION 2026-04-22 LABEL TRACE ACTIVE (webhook entry) file=' . __FILE__);
        $remote_ip = $this->get_remote_ip();
        $user_agent = $request->get_header( 'user-agent' );
        $user_agent = is_string( $user_agent ) ? $user_agent : '';

        try {
            $this->ensure_webhook_secret();
            $secret_header = $request->get_header( 'x-bwdk-webhook-secret' );
            $stored_secret = get_option( self::OPTION_WEBHOOK_SECRET, '' );
            if ( ! is_string( $secret_header ) || $secret_header === '' || ! is_string( $stored_secret ) || $stored_secret === '' || ! hash_equals( $stored_secret, $secret_header ) ) {
                return new WP_REST_Response(
                    [
                        'success' => false,
                        'error'   => 'Unauthorized',
                    ],
                    401
                );
            }

            if ( ! $this->check_rate_limit( $remote_ip ) ) {
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DigiExpress Webhook] Rate limit exceeded for IP: ' . $remote_ip );
                }
                return new WP_REST_Response( [ 'error' => 'Too many requests' ], 429 );
            }

            $data = $request->get_json_params();
            if ( ! is_array( $data ) || empty( $data ) ) {
                return new WP_REST_Response( [ 'error' => 'Invalid JSON' ], 400 );
            }

            error_log('Vahid: ' . wp_json_encode( [
                    'merchant_order_id' => $data['merchant_order_id'] ?? null,
                    'order_uuid'        => $data['order_uuid'] ?? null,
                    'shipment_id'       => $data['selected_shipping_data']['shipment_id'] ?? null,
                    'shipment_code'     => $data['selected_shipping_data']['shipment_code'] ?? null,
                    'event_message_id'  => $data['selected_shipping_data']['detail']['payload']['event_message_id'] ?? null,
                ],
                JSON_UNESCAPED_UNICODE ) );

            // Minimal targeted log: raw payload identifiers (for "Transaction not found for UUID" tracing).
            if ( function_exists( 'error_log' ) ) {
                error_log( 'DX webhook received: ' . wp_json_encode( [
                    'merchant_order_id' => $data['merchant_order_id'] ?? null,
                    'order_uuid'        => $data['order_uuid'] ?? null,
                    'shipment_id'       => $data['selected_shipping_data']['shipment_id'] ?? null,
                    'shipment_code'     => $data['selected_shipping_data']['shipment_code'] ?? null,
                    'event_message_id'  => $data['selected_shipping_data']['detail']['payload']['event_message_id'] ?? null,
                ], JSON_UNESCAPED_UNICODE ) );
            }

            $order_uuid = isset( $data['order_uuid'] ) ? trim( (string) $data['order_uuid'] ) : '';
            if ( $order_uuid === '' ) {
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DigiExpress Webhook] Missing order_uuid. IP=' . $remote_ip . ' UA=' . substr( $user_agent, 0, 100 ) );
                }
                return new WP_REST_Response( [ 'error' => 'Missing order_uuid' ], 400 );
            }

            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DigiExpress Webhook] order_uuid=' . $order_uuid . ' IP=' . $remote_ip . ' UA=' . substr( $user_agent, 0, 100 ) );
            }

            if ( ! function_exists( 'wc_get_order' ) || ! class_exists( 'WC_Order_Query' ) ) {
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DigiExpress Webhook] WooCommerce not available.' );
                }
                return new WP_REST_Response( [ 'error' => 'Service unavailable' ], 503 );
            }

            $order_query = new WC_Order_Query( [
                'limit'      => 1,
                'meta_key'   => Constant::META_BWDK_UUID,
                'meta_value' => $order_uuid,
            ] );
            $orders = $order_query->get_orders();

            if ( empty( $orders ) ) {
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DigiExpress Webhook] Order not found for order_uuid=' . $order_uuid );
                }
                return new WP_REST_Response( [ 'error' => 'Order not found' ], 404 );
            }

            $order = $orders[0];
            if ( ! $order instanceof \WC_Order ) {
                return new WP_REST_Response( [ 'error' => 'Order not found' ], 404 );
            }

            // Minimal targeted log: payload UUID vs matched WC order meta UUID.
            if ( function_exists( 'error_log' ) ) {
                error_log( 'DX matched WC order: ' . wp_json_encode( [
                    'wc_order_id'        => $order ? $order->get_id() : null,
                    'payload_order_uuid' => $data['order_uuid'] ?? null,
                    'meta_order_uuid'    => $order ? $order->get_meta( '_bwdk_order_uuid', true ) : null,
                ], JSON_UNESCAPED_UNICODE ) );
            }

            // Prefer payload order_uuid for API fetch; warn on mismatch with stored meta.
            $meta_uuid = $order->get_meta( Constant::META_BWDK_UUID, true );
            if ( function_exists( 'error_log' ) && is_string( $meta_uuid ) && $meta_uuid !== '' && $meta_uuid !== $order_uuid ) {
                error_log( '[DX webhook warning] payload/meta UUID mismatch: ' . wp_json_encode( [
                    'wc_order_id'        => $order->get_id(),
                    'payload_order_uuid' => $order_uuid,
                    'meta_order_uuid'    => $meta_uuid,
                ], JSON_UNESCAPED_UNICODE ) );
            }

            $order->update_meta_data( self::META_DX_LAST_WEBHOOK_RAW, $data );

            $api_result = $this->fetch_order_from_bwdk_api( $order_uuid, $order, $data );
            if ( is_array( $api_result ) && isset( $api_result['error'] ) ) {
                $order->save();
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DigiExpress Webhook] BWDK API failed for order_uuid=' . $order_uuid . ': ' . $api_result['error'] );
                }
                return new WP_REST_Response( [
                    'success'  => true,
                    'verified' => false,
                    'reason'   => 'bwdk_api_failed',
                ], 202 );
            }
            if ( ! is_array( $api_result ) ) {
                $order->save();
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DigiExpress Webhook] BWDK API failed for order_uuid=' . $order_uuid . ': invalid response' );
                }
                return new WP_REST_Response( [
                    'success'  => true,
                    'verified' => false,
                    'reason'   => 'bwdk_api_failed',
                ], 202 );
            }
            $api_details = $api_result;

            $selected_shipping_data = isset( $api_details['selected_shipping_data'] ) && is_array( $api_details['selected_shipping_data'] )
                ? $api_details['selected_shipping_data']
                : [];
            $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] )
                ? $selected_shipping_data['detail']
                : [];
            $payload = isset( $detail['payload'] ) && is_array( $detail['payload'] ) ? $detail['payload'] : [];
            $incoming_payload = isset( $data['detail']['payload'] ) && is_array( $data['detail']['payload'] ) ? $data['detail']['payload'] : [];
            $event_msg_id = isset( $payload['event_message_id'] ) && (string) $payload['event_message_id'] !== ''
                ? (string) $payload['event_message_id']
                : ( isset( $incoming_payload['event_message_id'] ) && (string) $incoming_payload['event_message_id'] !== '' ? (string) $incoming_payload['event_message_id'] : null );

            // Fallback idempotency key when event_message_id is missing (safe, best-effort).
            $fallback_msg_id = null;
            if ( $event_msg_id === null ) {
                $eid = isset( $detail['event_type']['id'] ) ? (int) $detail['event_type']['id'] : null;
                $occurred_at = isset( $payload['occurred_at'] ) ? trim( (string) $payload['occurred_at'] ) : '';
                $shipment_code = isset( $selected_shipping_data['shipment_code'] ) ? trim( (string) $selected_shipping_data['shipment_code'] ) : '';
                $shipment_id = isset( $selected_shipping_data['shipment_id'] ) ? trim( (string) $selected_shipping_data['shipment_id'] ) : '';
                if ( $eid !== null && ( $occurred_at !== '' || $shipment_code !== '' || $shipment_id !== '' ) ) {
                    $fallback_msg_id = 'fallback:' . (string) $eid . '|' . $occurred_at . '|' . $shipment_code . '|' . $shipment_id;
                }
            }

            $processed = $order->get_meta( self::META_DX_PROCESSED_EVENT_IDS, true );
            if ( ! is_array( $processed ) ) {
                $processed = [];
            }
            if ( $event_msg_id !== null && in_array( $event_msg_id, $processed, true ) ) {
                return new WP_REST_Response( [ 'success' => true, 'verified' => true ], 200 );
            }
            if ( $fallback_msg_id !== null && in_array( $fallback_msg_id, $processed, true ) ) {
                return new WP_REST_Response( [ 'success' => true, 'verified' => true ], 200 );
            }

            $this->sync_order_meta_from_api( $order, $api_details, $selected_shipping_data, $detail, $payload );
            if ( $event_msg_id !== null ) {
                $processed[] = $event_msg_id;
            } elseif ( $fallback_msg_id !== null ) {
                $processed[] = $fallback_msg_id;
            }
            if ( ! empty( $processed ) ) {
                $processed = array_slice( $processed, -self::PROCESSED_IDS_MAX, self::PROCESSED_IDS_MAX, true );
                $order->update_meta_data( self::META_DX_PROCESSED_EVENT_IDS, array_values( $processed ) );
            }

            $event_id = isset( $detail['event_type']['id'] ) ? (int) $detail['event_type']['id'] : null;
            if ( $event_id !== null ) {
                $this->maybe_update_order_status( $order, $event_id );
                $order->update_meta_data( self::META_DX_LAST_EVENT_ID, $event_id );

                // Map to normalized shipping status
                $shipping_status = $this->map_event_id_to_shipping_status( $event_id );
                if ( $shipping_status !== null ) {
                    // Prevent overwriting local cancelled state when API/webhook lags behind (keep local truth until API reflects cancel).
                    $current_local = $order->get_meta( self::META_DX_SHIPPING_STATUS, true );
                    $current_local = is_string( $current_local ) ? strtolower( trim( $current_local ) ) : '';
                    $api_reflects_cancelled = $this->api_selected_reflects_cancelled( $selected_shipping_data );
                    if ( $current_local === 'cancelled' && $shipping_status !== 'cancelled' && ! $api_reflects_cancelled ) {
                        // keep local cancelled
                    } else {
                        $order->update_meta_data( self::META_DX_SHIPPING_STATUS, $shipping_status );
                    }
                }

                // Logging
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX] Event ' . $event_id . ' for order ' . $order->get_id() );
                }
            }

            $resolved_msg = null;
            if ( is_array( $detail ) && class_exists( '\\DX_Status_Engine', true ) ) {
                $resolved_msg = \DX_Status_Engine::resolve_message( $detail );
            }

            if ( ! is_array( $resolved_msg ) && is_array( $detail ) && function_exists( 'BWDK\\Support\\bwdk_dx_build_message' ) ) {
                $legacy_msg = \BWDK\Support\bwdk_dx_build_message( $detail );
                if ( is_array( $legacy_msg ) ) {
                    $resolved_msg = [
                        'title'   => $legacy_msg['title'] ?? null,
                        'message' => $legacy_msg['message'] ?? '',
                        'status'  => $this->map_status_string_to_int( $legacy_msg['status'] ?? BWDK_DX_STATUS_INFO ),
                    ];
                }
            }

            $current_last_message = $order->get_meta( self::META_DX_LAST_MESSAGE, true );
            $current_last_status  = $order->get_meta( self::META_DX_LAST_STATUS, true );

            if ( $event_id === 3 && $resolved_msg === null ) {
                // Keep existing message/status for event 3 if no new info is provided.
                $order->update_meta_data( self::META_DX_LAST_EVENT_ID, $event_id );
            } elseif ( is_array( $resolved_msg ) ) {
                $resolved_title   = isset( $resolved_msg['title'] ) ? trim( (string) $resolved_msg['title'] ) : null;
                $resolved_message = isset( $resolved_msg['message'] ) ? trim( (string) $resolved_msg['message'] ) : '';
                $resolved_status  = isset( $resolved_msg['status'] ) ? (int) $resolved_msg['status'] : null;

                if ( $resolved_message !== '' ) {
                    $order->update_meta_data( self::META_DX_LAST_MESSAGE, $resolved_message );
                }
                if ( $resolved_status !== null ) {
                    $order->update_meta_data( self::META_DX_LAST_STATUS, $resolved_status );
                }

                $order->update_meta_data( self::META_DX_PANEL_MESSAGE, [
                    'title'   => $resolved_title,
                    'message' => $resolved_message,
                    'status'  => $this->map_status_int_to_string( $resolved_status ),
                ] );
            }

            $history = $order->get_meta( self::META_DX_HISTORY, true );
            if ( ! is_array( $history ) ) {
                $history = [];
            }
            $history[] = [
                'event_id'  => $event_id,
                'title'     => isset( $resolved_msg['title'] ) ? trim( (string) $resolved_msg['title'] ) : null,
                'message'   => isset( $resolved_msg['message'] ) ? trim( (string) $resolved_msg['message'] ) : ( is_string( $current_last_message ) ? $current_last_message : '' ),
                'status'    => isset( $resolved_msg['status'] ) ? (int) $resolved_msg['status'] : ( is_numeric( $current_last_status ) ? (int) $current_last_status : null ),
                'timestamp' => current_time( 'mysql' ),
            ];
            $order->update_meta_data( self::META_DX_HISTORY, $history );

            $order->save();

            return new WP_REST_Response( [ 'success' => true, 'verified' => true ], 200 );

        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DigiExpress Webhook] ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() );
            }
            return new WP_REST_Response( [ 'error' => 'Internal server error' ], 500 );
        }
    }

    /**
     * @return string
     */
    protected function get_remote_ip(): string {
        if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) && is_string( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
            return sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
        }
        if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
            $list = is_string( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ? explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ) : [];
            $first = trim( is_array( $list ) ? (string) ( $list[0] ?? '' ) : '' );
            if ( $first !== '' ) {
                return sanitize_text_field( $first );
            }
        }
        if ( ! empty( $_SERVER['REMOTE_ADDR'] ) && is_string( $_SERVER['REMOTE_ADDR'] ) ) {
            return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
        }
        return '0.0.0.0';
    }

    /**
     * Rate limit per IP: 60 requests per minute.
     */
    protected function check_rate_limit( string $ip ): bool {
        $key = self::RATE_LIMIT_TRANSIENT_PREFIX . md5( $ip );
        $count = (int) get_transient( $key );
        if ( $count >= self::RATE_LIMIT_PER_MINUTE ) {
            return false;
        }
        set_transient( $key, $count + 1, MINUTE_IN_SECONDS );
        return true;
    }

    /**
     * Fetch order details from BWDK API.
     * Returns: API data array on success; ['error' => string, 'code' => int] on failure.
     */
    protected function fetch_order_from_bwdk_api( string $request_order_uuid, ?\WC_Order $order = null, ?array $payload = null ) {
        $request_order_uuid = trim( $request_order_uuid );
        if ( $request_order_uuid === '' ) {
            return ['error' => 'کلید API تنظیم نشده است. از بخش تنظیمات افزونه، API Key را بررسی کنید.', 'code' => 0];
        }
        try {
            $opts = get_option( Constant::BWDK_OPTION_NAME, [] );
            if ( ! is_array( $opts ) || empty( $opts['api']['endpoint'] ) || empty( $opts['api']['api-key'] ) ) {
                return ['error' => 'کلید API تنظیم نشده است. از بخش تنظیمات افزونه، API Key را بررسی کنید.', 'code' => 0];
            }
            $base   = rtrim( (string) $opts['api']['endpoint'], '/' );
            $url    = $base . '/orders/api/v1/manager/' . $request_order_uuid . '/';
            $api_key = trim( (string) $opts['api']['api-key'] );
            if ( $api_key === '' ) {
                return ['error' => 'کلید API تنظیم نشده است. از بخش تنظیمات افزونه، API Key را بررسی کنید.', 'code' => 0];
            }

            // Minimal targeted log: confirm which UUID is being requested from API.
            if ( function_exists( 'error_log' ) ) {
                error_log( 'DX API fetch request: ' . wp_json_encode( [
                    'wc_order_id'        => $order ? $order->get_id() : null,
                    'request_order_uuid' => $request_order_uuid ?? null,
                    'payload_order_uuid' => is_array( $payload ) ? ( $payload['order_uuid'] ?? null ) : null,
                    'meta_order_uuid'    => $order ? $order->get_meta( '_bwdk_order_uuid', true ) : null,
                ], JSON_UNESCAPED_UNICODE ) );
            }

            $response = wp_remote_get( $url, [
                'timeout'   => 10,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Api-Key ' . $api_key,
                    'Content-Type'  => 'application/json; charset=utf-8',
                    'Accept'        => 'application/json',
                    'User-Agent'    => 'BWDK/WP',
                ],
            ] );

            // Minimal targeted log: raw response (helps trace "Transaction not found for UUID").
            if ( function_exists( 'error_log' ) ) {
                error_log( 'DX API fetch response: ' . wp_json_encode( [
                    'code' => wp_remote_retrieve_response_code( $response ),
                    'body' => wp_remote_retrieve_body( $response ),
                ], JSON_UNESCAPED_UNICODE ) );
            }

            if ( is_wp_error( $response ) ) {
                $error_message = $response->get_error_message();
                $mapped_error = $this->map_transport_error( $error_message );
                $this->log_api_error( $url, 'GET', 0, $response->get_error_code(), $error_message, '' );
                return ['error' => $mapped_error, 'code' => 0];
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            if ( $code === 401 || $code === 403 ) {
                $backend_msg = $this->extract_api_error_message( $body );
                $error = 'احراز هویت با سرویس خرید با دیجی‌کالا ناموفق بود. API Key نامعتبر است یا دسترسی این مرچنت/دامنه تأیید نشده است.';
                if ( $backend_msg ) {
                    $error .= ' ' . $backend_msg;
                }
                $this->log_api_error( $url, 'GET', $code, '', '', $body );
                return ['error' => $error, 'code' => $code];
            }
            if ( $code >= 400 ) {
                $backend_msg = $this->extract_api_error_message( $body );
                if ( $backend_msg ) {
                    $error = 'درخواست توسط سرویس خرید با دیجی‌کالا رد شد: ' . $backend_msg;
                } else {
                    $error = 'از سرویس خرید با دیجی‌کالا پاسخ معتبر دریافت نشد. لطفاً لاگ خطا را بررسی کنید.';
                }
                $this->log_api_error( $url, 'GET', $code, '', '', $body );
                return ['error' => $error, 'code' => $code];
            }
            $data = json_decode( $body, true );
            if ( $data === null && $body !== '' ) {
                $this->log_api_error( $url, 'GET', $code, '', 'Invalid JSON', $body );
                return ['error' => 'از سرویس خرید با دیجی‌کالا پاسخ معتبر دریافت نشد. لطفاً لاگ خطا را بررسی کنید.', 'code' => $code];
            }
            return $data;
        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DigiExpress Webhook] API exception: ' . $e->getMessage() );
            }
            return ['error' => 'خطای داخلی سرور. لطفاً لاگ خطا را بررسی کنید.', 'code' => 500];
        }
    }

    /**
     * Extract backend error message from API response body.
     */
    protected function extract_api_error_message( string $body_raw ): ?string {
        $body = json_decode( $body_raw, true );
        if ( ! is_array( $body ) ) {
            return null;
        }
        $keys = ['detail', 'message', 'error'];
        foreach ( $keys as $key ) {
            if ( isset( $body[$key] ) && is_string( $body[$key] ) && trim( $body[$key] ) !== '' ) {
                return trim( $body[$key] );
            }
        }
        return null;
    }

    /**
     * Map transport errors to user-friendly messages.
     */
    protected function map_transport_error( string $error_message ): string {
        $msg = strtolower( $error_message );
        if ( strpos( $msg, 'timed out' ) !== false || strpos( $msg, 'timeout' ) !== false ) {
            return 'اتصال به سرویس خرید با دیجی‌کالا برقرار نشد. سرور سایت در زمان مناسب پاسخی دریافت نکرد (Timeout). احتمال اختلال در هاست، فایروال یا اینترنت سرور وجود دارد.';
        }
        if ( strpos( $msg, 'ssl' ) !== false || strpos( $msg, 'certificate' ) !== false ) {
            return 'اتصال امن به سرویس خرید با دیجی‌کالا برقرار نشد. گواهی SSL یا تنظیمات cURL/OpenSSL سرور را بررسی کنید.';
        }
        if ( strpos( $msg, 'could not resolve host' ) !== false || strpos( $msg, 'name lookup timed out' ) !== false ) {
            return 'سرور سایت نتوانست آدرس سرویس خرید با دیجی‌کالا را resolve کند. DNS یا محدودیت شبکه هاست را بررسی کنید.';
        }
        if ( strpos( $msg, 'failed to connect' ) !== false || strpos( $msg, 'connection refused' ) !== false ) {
            return 'سرور سایت نتوانست به سرویس خرید با دیجی‌کالا وصل شود. احتمال مسدود بودن اتصال خروجی روی هاست وجود دارد.';
        }
        return 'اتصال به سرویس خرید با دیجی‌کالا برقرار نشد. لطفاً لاگ خطا را بررسی کنید.';
    }

    /**
     * Log API errors safely.
     */
    protected function log_api_error( string $endpoint, string $method, int $status_code, string $wp_error_code, string $wp_error_message, string $response_body ): void {
        $logger = function_exists( 'wc_get_logger' ) ? wc_get_logger() : null;
        if ( ! $logger ) {
            return;
        }
        $log_data = [
            'endpoint' => $endpoint,
            'method' => $method,
            'status_code' => $status_code,
            'wp_error_code' => $wp_error_code,
            'wp_error_message' => $wp_error_message,
            'response_body_length' => strlen( $response_body ),
        ];
        $logger->error( 'BWDK API Error: ' . wp_json_encode( $log_data, JSON_UNESCAPED_UNICODE ), ['source' => 'bwdk_api'] );
    }

    /**
     * Sync order meta from verified API response.
     */
    protected function sync_order_meta_from_api( \WC_Order $order, array $api_details, array $selected_shipping_data, array $detail, array $payload ): void {
        error_log('DX PATCH VERSION 2026-04-22 LABEL TRACE ACTIVE');
        // Guard against out-of-order webhook delivery / API lag overwriting newer local snapshot.
        // Compare occurred_at when both old/new are available and parsable.
        $incoming_occurred_at = isset( $payload['occurred_at'] ) ? trim( (string) $payload['occurred_at'] ) : '';
        $incoming_ts = $incoming_occurred_at !== '' ? strtotime( $incoming_occurred_at ) : false;
        $current_detail = $order->get_meta( self::META_DX_DETAIL, true );
        $current_detail = is_array( $current_detail ) ? $current_detail : [];
        $current_payload = isset( $current_detail['payload'] ) && is_array( $current_detail['payload'] ) ? $current_detail['payload'] : [];
        $current_occurred_at = isset( $current_payload['occurred_at'] ) ? trim( (string) $current_payload['occurred_at'] ) : '';
        $current_ts = $current_occurred_at !== '' ? strtotime( $current_occurred_at ) : false;
        if ( $incoming_ts !== false && $current_ts !== false && $current_ts > $incoming_ts ) {
            return;
        }

        if ( isset( $api_details['selected_shipping_method'] ) ) {
            $order->update_meta_data( self::META_SELECTED_SHIPPING_METHOD, $api_details['selected_shipping_method'] );
        }
        $order->update_meta_data( self::META_SELECTED_SHIPPING_DATA, $selected_shipping_data );
        $order->update_meta_data( self::META_DX_DETAIL, $detail );

        $pdf_label = isset( $selected_shipping_data['pdf_label'] ) ? trim( (string) $selected_shipping_data['pdf_label'] ) : '';
        error_log( 'DX PDF label candidate: ' . wp_json_encode( [
            'order_id'      => $order->get_id(),
            'has_pdf_label' => $pdf_label !== '',
            'pdf_label'     => $pdf_label,
        ] ) );
        if ( $pdf_label !== '' ) {
            $order->update_meta_data( '_bwdk_dx_pdf_label', esc_url_raw( $pdf_label ) );

            $old_html = $order->get_meta( self::META_DX_HTML_LABEL, true );
            if ( is_string( $old_html ) && $old_html !== '' ) {
                $order->delete_meta_data( self::META_DX_HTML_LABEL );
            }

            $order->save();

            error_log( 'DX PDF label saved meta: ' . wp_json_encode( [
                'order_id'          => $order->get_id(),
                'pdf_label_saved'   => (string) $order->get_meta( '_bwdk_dx_pdf_label', true ),
            ] ) );
        }
        if ( isset( $selected_shipping_data['shipment_code'] ) ) {
            $order->update_meta_data( self::META_DX_SHIPMENT_CODE, (string) $selected_shipping_data['shipment_code'] );
        }
        if ( isset( $detail['event_type']['id'] ) ) {
            $order->update_meta_data( self::META_DX_EVENT_ID, (int) $detail['event_type']['id'] );
        }
        if ( isset( $payload['occurred_at'] ) && (string) $payload['occurred_at'] !== '' ) {
            $order->update_meta_data( self::META_DX_OCCURRED_AT, (string) $payload['occurred_at'] );
        }
    }

    protected function maybe_update_order_status( \WC_Order $order, int $event_id ): void {
        try {
            // Only update to completed if delivered (event_ids 18, 19)
            if ( in_array( $event_id, [18, 19], true ) && $order->get_status() !== 'completed' ) {
                $order->update_status( 'completed', __( 'DigiExpress webhook: delivered (event_id ' . $event_id . ')', 'bwdk' ) );
            }
            // For all other statuses, do not change WooCommerce order status
        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DigiExpress Webhook] Status update: ' . $e->getMessage() );
            }
        }
    }

    protected function map_status_string_to_int( string $status ): int {
        $status = trim( strtolower( $status ) );
        switch ( $status ) {
            case BWDK_DX_STATUS_ERROR:
                return \DX_Status_Engine::STATUS_ERROR;
            case BWDK_DX_STATUS_SUCCESS:
                return \DX_Status_Engine::STATUS_SUCCESS;
            case BWDK_DX_STATUS_INFO:
                return \DX_Status_Engine::STATUS_INFO;
            case BWDK_DX_STATUS_WARNING:
            default:
                return \DX_Status_Engine::STATUS_WARNING;
        }
    }

    protected function map_status_int_to_string( ?int $status ): string {
        switch ( $status ) {
            case \DX_Status_Engine::STATUS_ERROR:
                return BWDK_DX_STATUS_ERROR;
            case \DX_Status_Engine::STATUS_SUCCESS:
                return BWDK_DX_STATUS_SUCCESS;
            case \DX_Status_Engine::STATUS_INFO:
                return BWDK_DX_STATUS_INFO;
            case \DX_Status_Engine::STATUS_WARNING:
            default:
                return BWDK_DX_STATUS_WARNING;
        }
    }

    protected function map_event_id_to_shipping_status( int $event_id ): ?string {
        if ( in_array( $event_id, [10, 11, 12, 13, 28, 29, 30], true ) ) {
            return 'in_transit';
        } elseif ( in_array( $event_id, [14, 15, 16, 17], true ) ) {
            return 'out_for_delivery';
        } elseif ( in_array( $event_id, [18, 19], true ) ) {
            return 'delivered';
        } elseif ( in_array( $event_id, [6, 7], true ) ) {
            return 'pickup_failed';
        } elseif ( in_array( $event_id, [8, 9], true ) ) {
            return 'cancelled';
        } elseif ( in_array( $event_id, [20, 21, 22, 23, 24, 25], true ) ) {
            return 'delivery_failed';
        } else {
            return 'pending';
        }
    }

    /**
     * True when BWDK selected_shipping_data reflects a cancelled shipment (authoritative API/webhook state).
     */
    protected function api_selected_reflects_cancelled( array $selected_shipping_data ): bool {
        $raw = isset( $selected_shipping_data['shipment_status'] ) ? strtolower( trim( (string) $selected_shipping_data['shipment_status'] ) ) : '';
        if ( in_array( $raw, [ 'cancelled', 'canceled' ], true ) ) {
            return true;
        }
        $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] )
            ? $selected_shipping_data['detail']
            : [];
        $event_type = isset( $detail['event_type'] ) && is_array( $detail['event_type'] ) ? $detail['event_type'] : [];
        if ( isset( $event_type['id'] ) ) {
            $eid = (int) $event_type['id'];
            if ( in_array( $eid, [ 8, 9 ], true ) ) {
                return true;
            }
        }
        return false;
    }
}

