<?php
/**
 * Guest Handler — verify guest by email + order_key, issue 15-min token.
 *
 * @package WPNest_Returns_For_WooCommerce
 */

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

class WPNest_RFW_Guest_Handler {

    const TOKEN_TTL    = 900;
    const TRANSIENT_NS = 'wpnest_rfw_guest_';

    /**
     * Verify guest credentials and return the WC_Order or WP_Error.
     *
     * Accepts either the human-friendly **order number** (preferred — what the
     * customer sees in the order confirmation email) or the legacy long
     * `order_key` (`wc_order_…`). Order number input is forgiving:
     * leading `#`, surrounding whitespace, lowercase prefixes are stripped.
     *
     * @param string $email
     * @param string $order_input  Order number (e.g. "1234" or "#1234") or order_key.
     * @return WC_Order|WP_Error
     */
    public static function verify( $email, $order_input ) {
        $email       = sanitize_email( (string) $email );
        $order_input = self::normalize_order_input( (string) $order_input );

        if ( ! $email || '' === $order_input ) {
            return new WP_Error(
                'missing_fields',
                __( 'Adres e-mail i numer zamówienia są wymagane.', 'wpnest-returns-for-woocommerce' )
            );
        }

        $order = self::resolve_order( $order_input );
        if ( ! $order instanceof WC_Order ) {
            return new WP_Error(
                'not_found',
                __( 'Nie znaleźliśmy zamówienia o tym numerze.', 'wpnest-returns-for-woocommerce' )
            );
        }

        if ( strcasecmp( $order->get_billing_email(), $email ) !== 0 ) {
            return new WP_Error(
                'email_mismatch',
                __( 'E-mail nie pasuje do podanego numeru zamówienia.', 'wpnest-returns-for-woocommerce' )
            );
        }

        $eligibility = WPNest_RFW_Eligibility::evaluate( (int) $order->get_id() );
        if ( empty( $eligibility['eligible'] ) ) {
            return new WP_Error( 'not_eligible', $eligibility['reason'] ? $eligibility['reason'] : __( 'This order is not eligible for return.', 'wpnest-returns-for-woocommerce' ) );
        }

        return $order;
    }

    /**
     * Random 32-char hex token tied to an order_id, expires in 15 minutes.
     */
    public static function generate_guest_token( $order_id ) {
        $token = bin2hex( random_bytes( 16 ) );
        set_transient( self::TRANSIENT_NS . $token, (int) $order_id, self::TOKEN_TTL );
        return $token;
    }

    /**
     * @return int|false  order_id on success, false on miss/expired.
     */
    public static function validate_guest_token( $token ) {
        $token = preg_replace( '/[^a-f0-9]/', '', (string) $token );
        if ( '' === $token || strlen( $token ) > 64 ) {
            return false;
        }
        $order_id = get_transient( self::TRANSIENT_NS . $token );
        if ( false === $order_id ) {
            return false;
        }
        return (int) $order_id;
    }

    public static function consume_token( $token ) {
        $token = preg_replace( '/[^a-f0-9]/', '', (string) $token );
        if ( '' === $token ) {
            return;
        }
        delete_transient( self::TRANSIENT_NS . $token );
    }

    /**
     * Normalise customer input so users can paste "#1234", "1234", " 1234 ",
     * "ORDER-1234" etc. — we strip the leading `#` and trim whitespace, but
     * preserve everything else for resolve_order() to interpret.
     */
    public static function normalize_order_input( $value ) {
        $value = sanitize_text_field( (string) $value );
        $value = trim( $value );
        $value = ltrim( $value, '#' );          // `#1234` → `1234`
        $value = trim( $value );                 // re-trim w razie spacji po hashu
        return $value;
    }

    /**
     * Resolve customer-provided value to a WC_Order. Order of attempts:
     *
     *   1. Treat as order_key (legacy form, e.g. `wc_order_…`) — fast, indexed.
     *   2. Treat as numeric internal post ID — same speed as 1.
     *   3. Treat as the user-facing order *number* — uses WC's
     *      `Order_Key_Lookup` and the `woocommerce_order_number` filter so
     *      it works with Sequential Order Numbers plugins.
     *
     * @param string $value Pre-normalised input (see normalize_order_input()).
     * @return WC_Order|null
     */
    public static function resolve_order( $value ) {
        if ( '' === $value ) {
            return null;
        }

        // 1. Order key (wc_order_xxx…)
        if ( 0 === strpos( $value, 'wc_order_' ) ) {
            $id = wc_get_order_id_by_order_key( $value );
            if ( $id ) {
                $order = wc_get_order( $id );
                if ( $order instanceof WC_Order ) {
                    return $order;
                }
            }
        }

        // 2. Pure numeric — try as internal post ID first (fast path).
        if ( ctype_digit( $value ) ) {
            $order = wc_get_order( (int) $value );
            if ( $order instanceof WC_Order ) {
                return $order;
            }
        }

        // 3. User-facing order number lookup — works with Sequential Order
        // Numbers / Sequential Order Numbers Pro / WC native (where get_order_number
        // simply returns ID). HPOS-safe via wc_get_orders().
        $matches = wc_get_orders( array(
            'limit'  => 1,
            'status' => array_keys( wc_get_order_statuses() ),
            'return' => 'objects',
            // Stringi WP_Query/WC ORM nie mają natywnego filtru po order_number,
            // więc próbujemy filter `woocommerce_order_number_search`. Najpierw
            // standardowa ścieżka — wcześniejsza próba ID/key zwykle wystarcza.
            'search' => $value,
        ) );
        if ( ! empty( $matches ) ) {
            foreach ( $matches as $order ) {
                if ( (string) $order->get_order_number() === (string) $value ) {
                    return $order;
                }
            }
            // Brak literal match na order_number — NIE zgadujemy. Search mógł
            // złapać LIKE %value% na transaction_id / billing_phone, ale to
            // false-positive. Customer powie "nie znaleźliśmy zamówienia".
        }

        return null;
    }
}
