<?php
/**
 * REST Controller — wpnest-rfw/v1 endpoints (submit, verify-guest, status, admin).
 *
 * @package WPNest_Returns_For_WooCommerce
 */

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

class WPNest_RFW_REST_Controller {

    const NAMESPACE_V1     = 'wpnest-rfw/v1';
    const RATE_LIMIT_MAX   = 5;
    const RATE_LIMIT_TTL   = HOUR_IN_SECONDS;
    const RATE_LIMIT_NS    = 'wpnest_rfw_rl_';

    private static $instance = null;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        add_action( 'rest_api_init', array( $this, 'register_routes' ) );
    }

    public function register_routes() {
        register_rest_route(
            self::NAMESPACE_V1,
            '/submit',
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => array( $this, 'rest_submit' ),
                'permission_callback' => array( $this, 'permission_submit' ),
                'args'                => array(
                    'order_id'    => array( 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint' ),
                    'items'       => array(
                        'type'     => 'array',
                        'required' => true,
                        'items'    => array(
                            'type'       => 'object',
                            'properties' => array(
                                'order_item_id' => array( 'type' => 'integer' ),
                                'qty'           => array( 'type' => 'integer' ),
                                'reason_code'   => array( 'type' => 'string' ),
                                'reason_text'   => array( 'type' => 'string' ),
                            ),
                        ),
                    ),
                    'guest_token' => array( 'type' => 'string', 'required' => false ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/verify-guest',
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => array( $this, 'rest_verify_guest' ),
                'permission_callback' => '__return_true',
                'args'                => array(
                    'email'        => array( 'type' => 'string', 'required' => true ),
                    // Preferred: user-facing order number (np. "1234" lub "#1234").
                    'order_number' => array( 'type' => 'string', 'required' => false ),
                    // Back-compat: stary parametr — pełen order_key. Akceptowany
                    // dalej, ale formularz wysyła `order_number`.
                    'order_key'    => array( 'type' => 'string', 'required' => false ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/status/(?P<order_key>[a-zA-Z0-9_\-]+)',
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'rest_status' ),
                'permission_callback' => '__return_true',
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/admin/update-status',
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => array( $this, 'rest_admin_update_status' ),
                'permission_callback' => array( $this, 'permission_admin' ),
                'args'                => array(
                    'request_id'      => array( 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint' ),
                    'new_status'      => array( 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_key' ),
                    'note'            => array( 'type' => 'string', 'required' => false ),
                    'notify_customer' => array( 'type' => 'boolean', 'required' => false ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/admin/refund',
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => array( $this, 'rest_admin_refund' ),
                'permission_callback' => array( $this, 'permission_admin' ),
                'args'                => array(
                    'request_id' => array( 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint' ),
                    'amount'     => array( 'type' => 'number', 'required' => false ),
                    'reason'     => array( 'type' => 'string', 'required' => false ),
                    'restock'    => array( 'type' => 'boolean', 'required' => false ),
                    'line_items' => array( 'type' => 'array', 'required' => false ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/admin/add-note',
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => array( $this, 'rest_admin_add_note' ),
                'permission_callback' => array( $this, 'permission_admin' ),
                'args'                => array(
                    'request_id'       => array( 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint' ),
                    'note'             => array( 'type' => 'string', 'required' => true ),
                    'customer_visible' => array( 'type' => 'boolean', 'required' => false ),
                ),
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/admin/request/(?P<id>\d+)',
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'rest_admin_get_request' ),
                'permission_callback' => array( $this, 'permission_admin' ),
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/pdf/(?P<request_id>\d+)',
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'rest_stream_pdf' ),
                'permission_callback' => array( $this, 'permission_stream_pdf' ),
            )
        );

        register_rest_route(
            self::NAMESPACE_V1,
            '/admin/requests',
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'rest_admin_list' ),
                'permission_callback' => array( $this, 'permission_admin' ),
                'args'                => array(
                    'status'    => array( 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_key' ),
                    'date_from' => array( 'type' => 'string', 'required' => false ),
                    'date_to'   => array( 'type' => 'string', 'required' => false ),
                    'order_id'  => array( 'type' => 'integer', 'required' => false, 'sanitize_callback' => 'absint' ),
                    'page'      => array( 'type' => 'integer', 'required' => false, 'sanitize_callback' => 'absint' ),
                    'per_page'  => array( 'type' => 'integer', 'required' => false, 'sanitize_callback' => 'absint' ),
                ),
            )
        );
    }

    public function permission_submit( WP_REST_Request $request ) {
        $token = (string) $request->get_param( 'guest_token' );
        if ( '' !== $token ) {
            $order_id = WPNest_RFW_Guest_Handler::validate_guest_token( $token );
            if ( ! $order_id ) {
                return new WP_Error( 'rest_forbidden', __( 'Guest session expired. Please verify again.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 403 ) );
            }
            if ( $order_id !== (int) $request->get_param( 'order_id' ) ) {
                return new WP_Error( 'rest_forbidden', __( 'Token does not match the requested order.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 403 ) );
            }
            return true;
        }

        if ( ! is_user_logged_in() ) {
            return new WP_Error( 'rest_forbidden', __( 'You must be logged in to submit a return.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 401 ) );
        }

        $order = wc_get_order( (int) $request->get_param( 'order_id' ) );
        if ( ! $order instanceof WC_Order || (int) $order->get_customer_id() !== get_current_user_id() ) {
            return new WP_Error( 'rest_forbidden', __( 'You do not own this order.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 403 ) );
        }

        return true;
    }

    public function permission_admin() {
        return current_user_can( 'manage_woocommerce' );
    }

    public function rest_submit( WP_REST_Request $request ) {
        $items = $request->get_param( 'items' );
        $result = WPNest_RFW_Form_Handler::submit(
            array(
                'order_id'    => (int) $request->get_param( 'order_id' ),
                'items'       => is_array( $items ) ? $items : array(),
                'guest_token' => (string) $request->get_param( 'guest_token' ),
                'ip'          => $this->client_ip(),
            )
        );

        if ( is_wp_error( $result ) ) {
            return new WP_REST_Response(
                array(
                    'success' => false,
                    'code'    => $result->get_error_code(),
                    'message' => $result->get_error_message(),
                ),
                400
            );
        }

        return new WP_REST_Response(
            array(
                'success'    => true,
                'request_id' => (int) $result['request_id'],
                'message'    => $result['message'],
            ),
            201
        );
    }

    public function rest_verify_guest( WP_REST_Request $request ) {
        $ip      = $this->client_ip();
        $rl_key  = self::RATE_LIMIT_NS . substr( hash( 'sha256', $ip ), 0, 16 );
        $current = (int) get_transient( $rl_key );

        if ( $current >= self::RATE_LIMIT_MAX ) {
            return new WP_REST_Response(
                array(
                    'success' => false,
                    'code'    => 'rate_limit',
                    'message' => __( 'Too many attempts. Please try again later.', 'wpnest-returns-for-woocommerce' ),
                ),
                429
            );
        }
        set_transient( $rl_key, $current + 1, self::RATE_LIMIT_TTL );

        // Preferowany input: order_number. Stary klient (przed v1.0.1) wysyłał
        // order_key — wciąż akceptujemy dla backward-compat.
        $order_input = (string) $request->get_param( 'order_number' );
        if ( '' === $order_input ) {
            $order_input = (string) $request->get_param( 'order_key' );
        }
        $order = WPNest_RFW_Guest_Handler::verify(
            (string) $request->get_param( 'email' ),
            $order_input
        );

        if ( is_wp_error( $order ) ) {
            return new WP_REST_Response(
                array(
                    'success' => false,
                    'code'    => $order->get_error_code(),
                    'message' => $order->get_error_message(),
                ),
                400
            );
        }

        $token = WPNest_RFW_Guest_Handler::generate_guest_token( (int) $order->get_id() );

        return new WP_REST_Response(
            array(
                'success'      => true,
                'guest_token'  => $token,
                'order_id'     => (int) $order->get_id(),
                'order_number' => (string) $order->get_order_number(),
            ),
            200
        );
    }

    public function rest_status( WP_REST_Request $request ) {
        $order_key = preg_replace( '/[^a-zA-Z0-9_\-]/', '', (string) $request['order_key'] );
        if ( '' === $order_key ) {
            return new WP_Error( 'invalid_key', __( 'Invalid order key.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }
        $order_id = wc_get_order_id_by_order_key( $order_key );
        if ( ! $order_id ) {
            return new WP_Error( 'not_found', __( 'Not found.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }
        $request_row = WPNest_RFW_Request_Model::get_by_order( $order_id );
        if ( ! $request_row ) {
            return new WP_REST_Response( array( 'status' => 'none', 'status_label' => __( 'No return request', 'wpnest-returns-for-woocommerce' ) ), 200 );
        }
        $statuses = WPNest_RFW_Request_Model::get_statuses();
        return new WP_REST_Response(
            array(
                'status'       => (string) $request_row->status,
                'status_label' => isset( $statuses[ $request_row->status ] ) ? $statuses[ $request_row->status ] : ucfirst( $request_row->status ),
            ),
            200
        );
    }

    public function rest_admin_update_status( WP_REST_Request $request ) {
        $request_id   = (int) $request->get_param( 'request_id' );
        $new_status   = (string) $request->get_param( 'new_status' );
        $note         = (string) $request->get_param( 'note' );
        $notify_param = $request->get_param( 'notify_customer' );

        $existing = WPNest_RFW_Request_Model::get( $request_id );
        if ( ! $existing ) {
            return new WP_Error( 'not_found', __( 'Request not found.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }
        $old_status = (string) $existing->status;

        // Default behaviour: email the customer on every status change. The admin can
        // suppress this single change by explicitly sending `notify_customer: false`.
        if ( null !== $notify_param && ! (bool) $notify_param ) {
            WPNest_RFW_Email_Notifications::skip_next_status_change();
        }

        $ok = WPNest_RFW_Request_Model::update_status( $request_id, $new_status, get_current_user_id(), $note );
        if ( ! $ok ) {
            return new WP_Error( 'update_failed', __( 'Could not update status.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 400 ) );
        }

        // Sync the WC order status to mirror the return status.
        $order = wc_get_order( (int) $existing->order_id );
        if ( $order instanceof WC_Order ) {
            $wc_status = 'rfw-' . $new_status;
            if ( array_key_exists( 'wc-' . $wc_status, WPNest_RFW_Order_Status::get_statuses() ) ) {
                $order->update_status( $wc_status, sprintf( /* translators: %s: new status */ __( 'Return status updated to %s by admin.', 'wpnest-returns-for-woocommerce' ), $new_status ) );
            }
        }

        // The email already fired (or was suppressed) via the `wpnest_rfw_status_changed`
        // action inside update_status(); no need to dispatch the deprecated notify action.

        $row = WPNest_RFW_Request_Model::get( $request_id );
        return new WP_REST_Response(
            array(
                'success' => true,
                'item'    => $this->format_request_for_admin( $row ),
            ),
            200
        );
    }

    public function rest_admin_refund( WP_REST_Request $request ) {
        $line_items = $request->get_param( 'line_items' );
        $result     = WPNest_RFW_Refund_Service::issue(
            array(
                'request_id' => (int) $request->get_param( 'request_id' ),
                'amount'     => $request->get_param( 'amount' ),
                'reason'     => (string) $request->get_param( 'reason' ),
                'restock'    => (bool) $request->get_param( 'restock' ),
                'line_items' => is_array( $line_items ) ? $line_items : array(),
            )
        );

        if ( is_wp_error( $result ) ) {
            $status = $result->get_error_data();
            $status = ( is_array( $status ) && isset( $status['status'] ) ) ? (int) $status['status'] : 400;
            return new WP_REST_Response(
                array(
                    'success' => false,
                    'code'    => $result->get_error_code(),
                    'message' => $result->get_error_message(),
                ),
                $status
            );
        }

        $row = WPNest_RFW_Request_Model::get( (int) $request->get_param( 'request_id' ) );
        return new WP_REST_Response(
            array(
                'success' => true,
                'refund'  => $result,
                'item'    => $this->format_request_for_admin( $row ),
            ),
            201
        );
    }

    public function rest_admin_add_note( WP_REST_Request $request ) {
        $request_id       = (int) $request->get_param( 'request_id' );
        $body             = wp_kses_post( (string) $request->get_param( 'note' ) );
        $customer_visible = (bool) $request->get_param( 'customer_visible' );

        $body = trim( $body );
        if ( '' === $body ) {
            return new WP_Error( 'empty_note', __( 'Note body cannot be empty.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 400 ) );
        }

        $existing = WPNest_RFW_Request_Model::get( $request_id );
        if ( ! $existing ) {
            return new WP_Error( 'not_found', __( 'Request not found.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }
        $order = wc_get_order( (int) $existing->order_id );
        if ( ! $order instanceof WC_Order ) {
            return new WP_Error( 'order_not_found', __( 'Order not found.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }

        $tagged_body = sprintf( '[%s #%d] %s', __( 'Return', 'wpnest-returns-for-woocommerce' ), $request_id, $body );
        $note_id     = $order->add_order_note( $tagged_body, $customer_visible ? 1 : 0, true );

        return new WP_REST_Response(
            array(
                'success' => true,
                'note_id' => (int) $note_id,
                'body'    => $tagged_body,
                'customer_visible' => $customer_visible,
            ),
            201
        );
    }

    public function rest_admin_get_request( WP_REST_Request $request ) {
        $request_id = (int) $request['id'];
        $row        = WPNest_RFW_Request_Model::get( $request_id );
        if ( ! $row ) {
            return new WP_Error( 'not_found', __( 'Request not found.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }
        return new WP_REST_Response(
            array(
                'success' => true,
                'item'    => $this->format_request_for_admin( $row ),
            ),
            200
        );
    }

    /**
     * Permission gate for confirmation-PDF streaming. Any one of three rules passes:
     *   1. Logged-in user has `manage_woocommerce` (admin staff).
     *   2. Logged-in user is the customer who owns the order.
     *   3. `?token=<guest_token>` matches a valid 15-min guest transient AND that
     *      token belongs to the request's order.
     */
    public function permission_stream_pdf( WP_REST_Request $request ) {
        $request_id = (int) $request['request_id'];
        $row        = WPNest_RFW_Request_Model::get( $request_id );
        if ( ! $row ) {
            return new WP_Error( 'not_found', __( 'PDF not found.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }

        if ( current_user_can( 'manage_woocommerce' ) ) {
            return true;
        }

        $order = wc_get_order( (int) $row->order_id );
        if ( ! $order instanceof WC_Order ) {
            return new WP_Error( 'not_found', __( 'PDF not found.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 404 ) );
        }

        if ( is_user_logged_in() && (int) $order->get_customer_id() === get_current_user_id() ) {
            return true;
        }

        $token = (string) $request->get_param( 'token' );
        if ( '' !== $token ) {
            $token_order_id = WPNest_RFW_Guest_Handler::validate_guest_token( $token );
            if ( $token_order_id && (int) $token_order_id === (int) $row->order_id ) {
                return true;
            }
        }

        return new WP_Error( 'rest_forbidden', __( 'You do not have permission to download this PDF.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 403 ) );
    }

    /**
     * Stream a confirmation PDF after the permission_callback has authorized.
     * No part of the file path is exposed to the client.
     */
    public function rest_stream_pdf( WP_REST_Request $request ) {
        $request_id = (int) $request['request_id'];
        $row        = WPNest_RFW_Request_Model::get( $request_id );

        // Lazy-generate on first access if the PDF doesn't exist yet (idempotent).
        $path = (string) $row->pdf_path;
        if ( ! $path || ! file_exists( $path ) ) {
            $path = (string) WPNest_RFW_PDF_Generator::generate_confirmation( $request_id );
        }
        if ( ! $path || ! file_exists( $path ) ) {
            return new WP_Error( 'pdf_unavailable', __( 'Confirmation PDF could not be produced. Try again later.', 'wpnest-returns-for-woocommerce' ), array( 'status' => 503 ) );
        }

        $filename = sprintf( 'return-confirmation-%d.pdf', $request_id );
        nocache_headers();
        header( 'Content-Type: application/pdf' );
        header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
        header( 'Content-Length: ' . (string) filesize( $path ) );
        header( 'X-Content-Type-Options: nosniff' );
        header( 'Referrer-Policy: same-origin' );
        header( 'Cache-Control: private, no-store, max-age=0' );

        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped,WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Binary PDF stream; escaping would corrupt it. WP_Filesystem cannot stream.
        readfile( $path );
        exit;
    }

    public function rest_admin_list( WP_REST_Request $request ) {
        $args = array(
            'status'    => $request->get_param( 'status' ),
            'date_from' => $request->get_param( 'date_from' ),
            'date_to'   => $request->get_param( 'date_to' ),
            'order_id'  => (int) $request->get_param( 'order_id' ),
            'page'      => max( 1, (int) $request->get_param( 'page' ) ),
            'per_page'  => max( 1, min( 500, (int) $request->get_param( 'per_page' ) ?: 20 ) ),
        );

        $items = WPNest_RFW_Request_Model::get_list( $args );
        $total = WPNest_RFW_Request_Model::get_total( $args );

        return new WP_REST_Response(
            array(
                'items' => array_map( array( $this, 'format_request_for_admin' ), $items ),
                'total' => (int) $total,
                'pages' => (int) ceil( $total / $args['per_page'] ),
            ),
            200
        );
    }

    private function format_request_for_admin( $row ) {
        if ( ! $row ) {
            return null;
        }
        $order        = wc_get_order( (int) $row->order_id );
        $order_number = $order instanceof WC_Order ? $order->get_order_number() : (string) $row->order_id;
        $billing_name = '';
        $billing_mail = '';
        if ( $order instanceof WC_Order ) {
            $billing_name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
            $billing_mail = (string) $order->get_billing_email();
        }
        $statuses    = WPNest_RFW_Request_Model::get_statuses();
        $items_rows  = WPNest_RFW_Request_Item_Model::get_for_request( (int) $row->id );
        $items_view  = array();
        foreach ( $items_rows as $ir ) {
            $product_name = '';
            if ( $order instanceof WC_Order ) {
                $order_item = $order->get_item( (int) $ir->order_item_id );
                if ( $order_item ) {
                    $product_name = (string) $order_item->get_name();
                }
            }
            if ( '' === $product_name && $ir->product_id ) {
                $p = wc_get_product( (int) $ir->product_id );
                $product_name = $p ? $p->get_name() : ( '#' . (int) $ir->product_id );
            }
            $items_view[] = array(
                'order_item_id' => (int) $ir->order_item_id,
                'product_id'    => (int) $ir->product_id,
                'variation_id'  => (int) $ir->variation_id,
                'qty'           => (int) $ir->qty,
                'reason_code'   => (string) $ir->reason_code,
                'reason_label'  => WPNest_RFW_Reason_Codes::get_label( (string) $ir->reason_code ),
                'reason_text'   => (string) $ir->reason_text,
                'product_name'  => $product_name,
            );
        }

        return array(
            'id'             => (int) $row->id,
            'order_id'       => (int) $row->order_id,
            'order_number'   => (string) $order_number,
            'customer_name'  => $billing_name,
            'customer_email' => $billing_mail,
            'status'         => (string) $row->status,
            'status_label'   => isset( $statuses[ $row->status ] ) ? $statuses[ $row->status ] : (string) $row->status,
            'reason'         => (string) $row->reason,
            'has_returned'   => (bool) $row->has_returned,
            'is_digital'     => (bool) $row->is_digital,
            'item_count'     => count( $items_view ),
            'items'          => $items_view,
            'pdf_path'       => (string) $row->pdf_path,
            'pdf_url'        => $row->pdf_path ? WPNest_RFW_PDF_Generator::get_confirmation_url( (int) $row->id ) : '',
            'created_at'     => (string) $row->created_at,
            'updated_at'     => (string) $row->updated_at,
        );
    }

    private function client_ip() {
        $ip = '';
        if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
            $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
        }
        return $ip;
    }
}
