<?php

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

/**
 * DigiExpress decision helpers (revive vs create new shipment).
 *
 * Notes:
 * - "Revive" means retry the same Express request (same shipment_id / request_id reference) when still in merchant possession.
 * - "Create new shipment" is required for delivery_failed (new shipment_id / request_id).
 */
class BWDK_DigiExpress_Service {

    public const SHIPPING_TYPE_DIGIEXPRESS = 2;

    public const ACTION_CREATE_NEW_SHIPMENT = 'create_new_shipment';
    public const ACTION_REVIVE              = 'revive';
    public const ACTION_NO_ACTION           = 'no_action';

    /** DigiExpress event ids */
    public const EVENT_IDS_PICKUP_FAILED   = [ 6, 7 ];
    public const EVENT_IDS_DELIVERY_FAILED = [ 20, 21, 22, 23, 24, 25 ];
    public const EVENT_IDS_IN_TRANSIT      = [ 10, 11, 12, 13, 28, 29, 30 ];
    public const EVENT_IDS_OUT_FOR_DELIVERY = [ 14, 15, 16, 17 ];
    public const EVENT_IDS_DELIVERED       = [ 18, 19 ];
    public const EVENT_IDS_RETURNED        = [ 26, 27 ];

    /**
     * Decide what to do for DigiExpress shipment in current state.
     *
     * IMPORTANT:
     * - delivery_failed must NOT be revived; it must be handled by creating a NEW shipment (new shipment_id/request_id).
     * - The endpoint/flow for create_new_shipment is still TBD; for now we only return an action decision.
     *
     * @param array       $shipment Shipment payload (usually selected_shipping_data) with optional enrichments:
     *                              - selected_shipping_method.shipping_type
     *                              - bwdk_dx_shipping_status (normalized meta)
     *                              - bwdk_dx_event_id (normalized/meta fallback)
     * @param string|null $reason   Optional reason, e.g. preparation_time_changed|ready_date_changed
     */
    public function decide_digiexpress_action( array $shipment, ?string $reason = null ): string {
        if ( $this->is_delivery_failed( $shipment ) ) {
            return self::ACTION_CREATE_NEW_SHIPMENT;
        }
        if ( $this->can_revive( $shipment, $reason ) ) {
            return self::ACTION_REVIVE;
        }
        return self::ACTION_NO_ACTION;
    }

    /**
     * Whether this shipment is eligible for revive.
     *
     * Rules:
     * - shipping_type != digiexpress => false
     * - missing shipment_id => false
     * - picked_up (picked_up_at present OR event/state indicates Express possession) => false
     * - delivery_failed => false
     * - delivered/returned/out_for_delivery/in_transit => false
     * - pickup_failed => true
     * - reason in preparation_time_changed|ready_date_changed AND not picked up => true
     * - has_revive_with_same_ref=true is only a hint; it cannot override the above blocks.
     */
    public function can_revive( array $shipment, ?string $reason = null ): bool {
        if ( ! $this->is_digiexpress_shipping_type( $shipment ) ) {
            return false;
        }

        $shipment_id = isset( $shipment['shipment_id'] ) ? trim( (string) $shipment['shipment_id'] ) : '';
        if ( $shipment_id === '' ) {
            return false;
        }

        // Priority order (product rules): delivery_failed blocks revive unconditionally.
        if ( $this->is_delivery_failed( $shipment ) ) {
            return false;
        }

        if ( $this->is_picked_up_or_in_express_possession( $shipment ) ) {
            return false;
        }

        if ( $this->is_in_transit_or_out_for_delivery_or_delivered_or_returned( $shipment ) ) {
            return false;
        }

        if ( $this->is_pickup_failed( $shipment ) ) {
            return true;
        }

        $reason = is_string( $reason ) ? strtolower( trim( $reason ) ) : null;
        if ( $reason === 'preparation_time_changed' || $reason === 'ready_date_changed' ) {
            return true;
        }

        return false;
    }

    protected function is_digiexpress_shipping_type( array $shipment ): bool {
        // Preferred: selected_shipping_method.shipping_type
        $method = isset( $shipment['selected_shipping_method'] ) && is_array( $shipment['selected_shipping_method'] )
            ? $shipment['selected_shipping_method']
            : [];
        if ( array_key_exists( 'shipping_type', $method ) ) {
            return (int) $method['shipping_type'] === self::SHIPPING_TYPE_DIGIEXPRESS;
        }

        // Fallback: root shipping_type if present
        if ( array_key_exists( 'shipping_type', $shipment ) ) {
            $st = $shipment['shipping_type'];
            return is_numeric( $st ) ? ( (int) $st === self::SHIPPING_TYPE_DIGIEXPRESS ) : false;
        }

        // Last resort: if we have DX fields we treat it as DX; still requires shipment_id in can_revive().
        if ( array_key_exists( 'detail', $shipment ) && is_array( $shipment['detail'] ) ) {
            $detail = $shipment['detail'];
            if ( isset( $detail['event_type'] ) && is_array( $detail['event_type'] ) ) {
                return true;
            }
        }

        return true;
    }

    protected function is_pickup_failed( array $shipment ): bool {
        $status = $this->get_normalized_shipping_status( $shipment );
        if ( $status === 'pickup_failed' ) {
            return true;
        }
        $event_id = $this->get_event_id( $shipment );
        return in_array( $event_id, self::EVENT_IDS_PICKUP_FAILED, true );
    }

    protected function is_delivery_failed( array $shipment ): bool {
        $status = $this->get_normalized_shipping_status( $shipment );
        if ( $status === 'delivery_failed' ) {
            return true;
        }
        $event_id = $this->get_event_id( $shipment );
        return in_array( $event_id, self::EVENT_IDS_DELIVERY_FAILED, true );
    }

    protected function is_in_transit_or_out_for_delivery_or_delivered_or_returned( array $shipment ): bool {
        $status = $this->get_normalized_shipping_status( $shipment );
        if ( in_array( $status, [ 'in_transit', 'out_for_delivery', 'delivered', 'returned' ], true ) ) {
            return true;
        }
        $event_id = $this->get_event_id( $shipment );
        if ( $event_id === null ) {
            return false;
        }
        if ( in_array( $event_id, self::EVENT_IDS_DELIVERED, true ) || in_array( $event_id, self::EVENT_IDS_RETURNED, true ) ) {
            return true;
        }
        if ( in_array( $event_id, self::EVENT_IDS_OUT_FOR_DELIVERY, true ) ) {
            return true;
        }
        if ( in_array( $event_id, self::EVENT_IDS_IN_TRANSIT, true ) ) {
            return true;
        }
        return false;
    }

    protected function is_picked_up_or_in_express_possession( array $shipment ): bool {
        // Direct timestamp flag if backend provides it.
        $detail = isset( $shipment['detail'] ) && is_array( $shipment['detail'] ) ? $shipment['detail'] : [];
        $payload = isset( $detail['payload'] ) && is_array( $detail['payload'] ) ? $detail['payload'] : [];
        foreach ( [ 'picked_up_at', 'picked_up_date_time', 'pickup_at' ] as $key ) {
            if ( isset( $payload[ $key ] ) && trim( (string) $payload[ $key ] ) !== '' ) {
                return true;
            }
        }

        // If already in transit/out for delivery/etc, it's definitely in Express possession.
        if ( $this->is_in_transit_or_out_for_delivery_or_delivered_or_returned( $shipment ) ) {
            return true;
        }

        // Progress type/state heuristics (some payloads expose operational progress in event or root).
        $progress = '';
        $detail = isset( $shipment['detail'] ) && is_array( $shipment['detail'] ) ? $shipment['detail'] : [];
        $event = isset( $detail['event_type'] ) && is_array( $detail['event_type'] ) ? $detail['event_type'] : [];
        if ( isset( $event['progress_type'] ) ) {
            $progress = trim( (string) $event['progress_type'] );
        }
        if ( $progress === '' && isset( $shipment['progress_type'] ) ) {
            $progress = trim( (string) $shipment['progress_type'] );
        }
        $progress_l = strtolower( $progress );
        if ( $progress_l !== '' ) {
            // Explicit states reported by product/backend examples.
            $block = [ 'pickedup', 'picked_up', 'intheroadbeingdelivering', 'in_the_road_being_delivering', 'outfordelivery', 'out_for_delivery', 'inexpresspossession', 'in_express_possession' ];
            foreach ( $block as $b ) {
                if ( $progress_l === $b ) {
                    return true;
                }
            }
        }

        // Raw shipment_status heuristics (defensive; different backends use different strings).
        $raw = isset( $shipment['shipment_status'] ) ? strtolower( trim( (string) $shipment['shipment_status'] ) ) : '';
        if ( $raw !== '' ) {
            if ( strpos( $raw, 'picked' ) !== false || strpos( $raw, 'in_transit' ) !== false || strpos( $raw, 'out_for_delivery' ) !== false ) {
                return true;
            }
        }

        return false;
    }

    protected function get_normalized_shipping_status( array $shipment ): string {
        $candidates = [
            $shipment['bwdk_dx_shipping_status'] ?? null,
            $shipment['shipping_status_meta'] ?? null,
            $shipment['dx_shipping_status'] ?? null,
        ];
        foreach ( $candidates as $c ) {
            if ( is_string( $c ) && trim( $c ) !== '' ) {
                return strtolower( trim( $c ) );
            }
        }
        return '';
    }

    protected function get_event_id( array $shipment ): ?int {
        if ( array_key_exists( 'bwdk_dx_event_id', $shipment ) && is_numeric( $shipment['bwdk_dx_event_id'] ) ) {
            return (int) $shipment['bwdk_dx_event_id'];
        }
        $detail = isset( $shipment['detail'] ) && is_array( $shipment['detail'] ) ? $shipment['detail'] : [];
        $event_type = isset( $detail['event_type'] ) && is_array( $detail['event_type'] ) ? $detail['event_type'] : [];
        if ( isset( $event_type['id'] ) ) {
            return (int) $event_type['id'];
        }
        return null;
    }
}

