<?php

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

/**
 * DigiExpress auto-recovery for missing labels (no global cron).
 * Webhook is a trigger only; BWDK API remains source of truth via refresh_order_from_backend().
 */
class DX_Status_Sync {

    const META_DX_HISTORY                   = '_bwdk_dx_history';
    const META_DX_PDF_LABEL                 = '_bwdk_dx_pdf_label';
    const META_DX_HTML_LABEL                = '_bwdk_dx_html_label';
    const META_DX_SHIPPING_STATUS           = '_bwdk_dx_shipping_status';
    const META_DX_LAST_EVENT_ID             = '_bwdk_dx_last_event_id';
    const META_DX_LAST_MISSING_LABEL_SYNC   = '_bwdk_dx_last_missing_label_recovery_at';

    const LOCK_TRANSIENT_PREFIX = 'bwdk_dx_label_recovery_lock_';
    const LOCK_TTL_SECONDS      = 300;
    const COOLDOWN_SECONDS      = HOUR_IN_SECONDS;

    const SOURCE_AUTO_RECOVERY = 'auto_missing_label_recovery';

    /** @var array<int, true> Per-request guard against duplicate recovery on the same order. */
    private static $ran_this_request = [];

    /**
     * Register trigger hooks (no global cron).
     */
    public static function register_hooks(): void {
        if ( ! function_exists( 'bwdk_is_digiexpress_enabled' ) || ! bwdk_is_digiexpress_enabled() ) {
            return;
        }

        add_action( 'add_meta_boxes', [ __CLASS__, 'maybe_recover_on_admin_order_screen' ], 1001 );
        // Do not hook woocommerce_process_shop_order_meta: reloading the order on every admin save
        // can roll back in-flight WC status changes. Recovery runs on screen open + webhook only.
    }

    /**
     * Admin order edit screen opened (meta boxes registering).
     */
    public static function maybe_recover_on_admin_order_screen(): void {
        if ( ! is_admin() || ! function_exists( 'bwdk_is_digiexpress_enabled' ) || ! bwdk_is_digiexpress_enabled() ) {
            return;
        }

        if ( isset( $_POST['order_status'] ) || isset( $_POST['status'] ) ) {
            return;
        }

        $order = self::resolve_order_from_admin_screen();
        if ( ! $order instanceof \WC_Order ) {
            return;
        }

        self::maybe_recover_missing_label( $order->get_id(), 'admin_order_screen' );
    }

    /**
     * Attempt auto-recovery when a DigiExpress label is missing after event 3.
     *
     * @param int    $order_id Order ID.
     * @param string $trigger  Context string for logging/debug.
     * @return array{skipped?:string, success?:bool, error?:string}
     */
    public static function maybe_recover_missing_label( $order_id, $trigger = '' ): array {
        $order_id = absint( $order_id );
        if ( $order_id <= 0 ) {
            return [ 'skipped' => 'invalid_order_id' ];
        }

        if ( isset( self::$ran_this_request[ $order_id ] ) ) {
            return [ 'skipped' => 'already_ran_this_request' ];
        }

        if ( function_exists( 'bwdk_is_admin_order_status_save_in_progress' ) && bwdk_is_admin_order_status_save_in_progress() ) {
            return [ 'skipped' => 'admin_manual_status_save' ];
        }

        if ( ! function_exists( 'bwdk_is_digiexpress_enabled' ) || ! bwdk_is_digiexpress_enabled() ) {
            return [ 'skipped' => 'feature_disabled' ];
        }

        if ( ! function_exists( 'wc_get_order' ) ) {
            return [ 'skipped' => 'woocommerce_unavailable' ];
        }

        $order = wc_get_order( $order_id );
        if ( ! $order instanceof \WC_Order ) {
            return [ 'skipped' => 'order_not_found' ];
        }

        if ( ! class_exists( '\BWDK\Admin\BWDK_Admin_DX_Meta_Box', false ) ) {
            return [ 'skipped' => 'meta_box_unavailable' ];
        }

        if ( ! \BWDK\Admin\BWDK_Admin_DX_Meta_Box::order_is_digiexpress_for_list( $order ) ) {
            return [ 'skipped' => 'not_digiexpress' ];
        }

        if ( ! self::dx_history_has_event_id( $order, 3 ) ) {
            return [ 'skipped' => 'no_event_3_in_history' ];
        }

        if ( self::order_has_dx_label( $order ) ) {
            return [ 'skipped' => 'label_exists' ];
        }

        if ( self::is_dx_final_status( $order ) ) {
            return [ 'skipped' => 'final_status' ];
        }

        if ( self::is_in_cooldown( $order ) ) {
            return [ 'skipped' => 'cooldown' ];
        }

        $lock_key = self::LOCK_TRANSIENT_PREFIX . $order_id;
        if ( get_transient( $lock_key ) ) {
            return [ 'skipped' => 'locked' ];
        }

        set_transient( $lock_key, '1', self::LOCK_TTL_SECONDS );
        self::$ran_this_request[ $order_id ] = true;

        try {
            $result = self::refresh_order_from_backend( $order_id, 'missing_label_recovery' );
            if ( isset( $result['error'] ) ) {
                self::mark_recovery_attempt( $order );
                $order->save();
                return [
                    'success' => false,
                    'error'   => $result['error'],
                ];
            }

            $order = wc_get_order( $order_id );
            if ( ! $order instanceof \WC_Order ) {
                return [ 'success' => false, 'error' => 'سفارش پس از همگام‌سازی یافت نشد' ];
            }

            $label_received = self::order_has_dx_label( $order );
            self::append_auto_recovery_history( $order, $label_received );
            $order->add_order_note( 'دیجی‌اکسپرس: به‌روزرسانی خودکار برای بازیابی لیبل انجام شد.' );
            self::mark_recovery_attempt( $order );
            $order->save();

            return [
                'success'        => true,
                'label_received' => $label_received,
                'trigger'        => $trigger,
            ];
        } finally {
            delete_transient( $lock_key );
        }
    }

    /**
     * Fetch BWDK manager order and sync DigiExpress meta from API truth.
     *
     * @param int    $order_id Order ID.
     * @param string $source   Caller context (e.g. missing_label_recovery).
     * @return array{success?:bool, error?:string}
     */
    public static function refresh_order_from_backend( $order_id, $source = '' ): array {
        $order_id = absint( $order_id );
        if ( $order_id <= 0 || ! function_exists( 'wc_get_order' ) ) {
            return [ 'error' => 'شناسه سفارش نامعتبر است' ];
        }

        $order = wc_get_order( $order_id );
        if ( ! $order instanceof \WC_Order ) {
            return [ 'error' => 'سفارش یافت نشد' ];
        }

        $order_uuid = trim( (string) $order->get_meta( '_bwdk_order_uuid', true ) );
        if ( $order_uuid === '' ) {
            return [ 'error' => 'شناسه سفارش BWDK یافت نشد' ];
        }

        if ( ! class_exists( '\BWDK\Admin\BWDK_Admin_DX_Meta_Box', false ) ) {
            return [ 'error' => 'ماژول دیجی‌اکسپرس در دسترس نیست' ];
        }

        $helper = new class() extends \BWDK\Admin\BWDK_Admin_DX_Meta_Box {
            public function __construct() {
                // No admin hooks.
            }

            /**
             * @return array<string, mixed>
             */
            public function run_refresh( string $order_uuid ): array {
                return $this->fetch_fresh_order_details( $order_uuid );
            }

            public function run_sync( \WC_Order $order, array $api_result, string $order_uuid ): void {
                $this->sync_dx_meta_from_bwdk_order_response( $order, $api_result, $order_uuid );
            }
        };

        $api_result = $helper->run_refresh( $order_uuid );
        if ( isset( $api_result['error'] ) ) {
            $raw = is_string( $api_result['error'] ) ? $api_result['error'] : wp_json_encode( $api_result['error'] );
            return [
                'error' => 'بروزرسانی از سرویس خرید با دیجی‌کالا انجام نشد (' . $raw . ')',
            ];
        }

        $selected = $api_result['selected_shipping_data'] ?? null;
        if ( ! is_array( $selected ) ) {
            return [
                'error' => 'پاسخ سرویس فاقد اطلاعات ارسال دیجی‌اکسپرس است',
            ];
        }

        $helper->run_sync( $order, $api_result, $order_uuid );

        if ( function_exists( 'error_log' ) && $source !== '' ) {
            error_log( '[BWDK DX Status Sync] refresh_order_from_backend ok order_id=' . $order_id . ' source=' . $source );
        }

        return [ 'success' => true ];
    }

    /**
     * Append a system history row after auto-recovery (deduped when event_message_id is absent).
     */
    protected static function append_auto_recovery_history( \WC_Order $order, bool $label_received ): void {
        $history = $order->get_meta( self::META_DX_HISTORY, true );
        if ( ! is_array( $history ) ) {
            $history = [];
        }

        $dedupe_key = 'system:' . self::SOURCE_AUTO_RECOVERY . ':' . gmdate( 'Y-m-d-H' );
        foreach ( $history as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }
            if ( isset( $item['dedupe_key'] ) && (string) $item['dedupe_key'] === $dedupe_key ) {
                return;
            }
            if ( isset( $item['bwdk_source'] ) && (string) $item['bwdk_source'] === self::SOURCE_AUTO_RECOVERY ) {
                $ts = isset( $item['timestamp'] ) ? strtotime( (string) $item['timestamp'] ) : false;
                if ( $ts !== false && ( time() - $ts ) < self::COOLDOWN_SECONDS ) {
                    return;
                }
            }
        }

        $message = $label_received
            ? 'به‌روزرسانی خودکار انجام شد و لیبل دیجی‌اکسپرس دریافت شد.'
            : 'به‌روزرسانی خودکار انجام شد، اما لیبل هنوز از BWDK API دریافت نشد.';

        $history[] = [
            'title'       => 'به‌روزرسانی خودکار',
            'message'     => $message,
            'status'      => 'system',
            'timestamp'   => current_time( 'mysql' ),
            'bwdk_source' => self::SOURCE_AUTO_RECOVERY,
            'dedupe_key'  => $dedupe_key,
        ];

        $order->update_meta_data( self::META_DX_HISTORY, $history );
    }

    protected static function dx_history_has_event_id( \WC_Order $order, int $event_id ): bool {
        $history = $order->get_meta( self::META_DX_HISTORY, true );
        if ( ! is_array( $history ) ) {
            return false;
        }
        foreach ( $history as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }
            if ( isset( $item['event_id'] ) && (int) $item['event_id'] === $event_id ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Newest DX history row by timestamp (ties: higher event_id wins). HPOS-safe.
     *
     * @return array|null
     */
    protected static function dx_pick_newest_history_row( \WC_Order $order ): ?array {
        $history = $order->get_meta( self::META_DX_HISTORY, true );
        if ( ! is_array( $history ) || empty( $history ) ) {
            return null;
        }
        $tz        = function_exists( 'wp_timezone' ) ? wp_timezone() : new \DateTimeZone( 'UTC' );
        $best_item = null;
        $best_ts   = null;
        foreach ( $history as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }
            $ts_raw = isset( $item['timestamp'] ) ? $item['timestamp'] : '';
            $cur_ts = null;
            if ( is_numeric( $ts_raw ) ) {
                $cur_ts = (int) $ts_raw;
            } elseif ( is_string( $ts_raw ) && $ts_raw !== '' ) {
                try {
                    $cur_ts = ( new \DateTimeImmutable( $ts_raw, $tz ) )->getTimestamp();
                } catch ( \Exception $e ) {
                    $parsed = strtotime( $ts_raw );
                    $cur_ts = ( $parsed !== false ) ? $parsed : null;
                }
            }
            if ( $cur_ts === null ) {
                continue;
            }
            if ( $best_ts === null || $cur_ts > $best_ts ) {
                $best_ts   = $cur_ts;
                $best_item = $item;
            } elseif ( $cur_ts === $best_ts && $best_item !== null ) {
                $e_new = isset( $item['event_id'] ) ? (int) $item['event_id'] : 0;
                $e_old = isset( $best_item['event_id'] ) ? (int) $best_item['event_id'] : 0;
                if ( $e_new > $e_old ) {
                    $best_item = $item;
                }
            }
        }
        return $best_item;
    }

    protected static function order_has_dx_label( \WC_Order $order ): bool {
        $pdf = trim( (string) $order->get_meta( self::META_DX_PDF_LABEL, true ) );
        if ( $pdf !== '' ) {
            return true;
        }
        $html = trim( (string) $order->get_meta( self::META_DX_HTML_LABEL, true ) );
        return $html !== '';
    }

    /**
     * Final DX states: cancelled (incl. event 8/9) or delivered.
     * Stale _bwdk_dx_shipping_status = cancelled is ignored when timeline / SSD shows an active shipment again.
     */
    protected static function is_dx_final_status( \WC_Order $order ): bool {
        $meta_status = strtolower( trim( (string) $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) ) );

        if ( $meta_status === 'cancelled' ) {
            $newest = self::dx_pick_newest_history_row( $order );
            if ( is_array( $newest ) && isset( $newest['event_id'] ) ) {
                $hid = (int) $newest['event_id'];
                if ( $hid > 0 && ! in_array( $hid, [ 8, 9 ], true ) ) {
                    return false;
                }
            }
            $ssd = $order->get_meta( '_bwdk_selected_shipping_data', true );
            if ( is_array( $ssd ) ) {
                $detail = isset( $ssd['detail'] ) && is_array( $ssd['detail'] ) ? $ssd['detail'] : [];
                $eid    = isset( $detail['event_type']['id'] ) ? (int) $detail['event_type']['id'] : 0;
                if ( $eid > 0 && ! in_array( $eid, [ 8, 9 ], true ) ) {
                    return false;
                }
                $raw_ss = isset( $ssd['shipment_status'] ) ? strtolower( trim( (string) $ssd['shipment_status'] ) ) : '';
                if ( $raw_ss !== '' && ! in_array( $raw_ss, [ 'cancelled', 'canceled' ], true ) ) {
                    return false;
                }
            }
        }

        $status = strtolower( trim( (string) $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) ) );
        if ( in_array( $status, [ 'cancelled', 'delivered' ], true ) ) {
            return true;
        }

        $event_id = (int) $order->get_meta( self::META_DX_LAST_EVENT_ID, true );
        return in_array( $event_id, [ 8, 9, 18, 19 ], true );
    }

    protected static function is_in_cooldown( \WC_Order $order ): bool {
        $last = trim( (string) $order->get_meta( self::META_DX_LAST_MISSING_LABEL_SYNC, true ) );
        if ( $last === '' ) {
            return false;
        }
        $ts = strtotime( $last );
        if ( $ts === false ) {
            return false;
        }
        return ( time() - $ts ) < self::COOLDOWN_SECONDS;
    }

    protected static function mark_recovery_attempt( \WC_Order $order ): void {
        $order->update_meta_data( self::META_DX_LAST_MISSING_LABEL_SYNC, current_time( 'mysql' ) );
    }

    protected static function resolve_order_from_admin_screen(): ?\WC_Order {
        $order_id = 0;
        if ( isset( $_GET['id'] ) ) {
            $order_id = absint( $_GET['id'] );
        } elseif ( isset( $_GET['post'] ) ) {
            $order_id = absint( $_GET['post'] );
        }
        if ( $order_id <= 0 ) {
            return null;
        }
        $order = wc_get_order( $order_id );
        return $order instanceof \WC_Order ? $order : null;
    }
}
