<?php

namespace WeDevs\Dokan\REST;

use WC_Product;
use WC_Product_Simple;
use WC_REST_Products_Controller;
use WeDevs\Dokan\Intelligence\Manager;
use WeDevs\Dokan\Intelligence\Services\Model;
use WeDevs\Dokan\ProductEditor\Elements;
use WeDevs\Dokan\ProductEditor\FormSchema;
use WeDevs\Dokan\ProductEditor\PayloadResolver;
use WeDevs\Dokan\Traits\VendorAuthorizable;
use WP_REST_Server;
use WP_REST_Response;
use WP_REST_Request;
use WP_Error;

class ProductControllerV3 extends WC_REST_Products_Controller {

    use VendorAuthorizable;

    /**
     * Endpoint namespace
     *
     * @since 5.0.0
     *
     * @var string
     */
    protected $namespace = 'dokan/v3';

    /**
     * Whether the rest_pre_dispatch filter has already been registered.
     *
     * @since 5.0.0
     *
     * @var bool
     */
    private static $filter_registered = false;

    /**
     * Check if the current user can create a product.
     *
     * @since 5.0.0
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return true|WP_Error
     */
    public function create_item_permissions_check( $request ) {
        if ( ! current_user_can( 'dokan_add_product' ) ) {
            return new WP_Error( 'dokan_rest_cannot_create', __( 'You do not have permission to create products.', 'dokan-lite' ), [ 'status' => 403 ] );
        }

        return true;
    }

    /**
     * Check if the current user can view/update the given product.
     *
     * @since 5.0.0
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return true|WP_Error
     */
    public function check_permission( $request ) {
        if ( ! current_user_can( 'dokan_edit_product' ) ) {
            return new WP_Error( 'dokan_rest_cannot_edit', __( 'You do not have permission to edit products.', 'dokan-lite' ), [ 'status' => 403 ] );
        }

        if ( ! $this->check_ownership( $request ) ) {
            return new WP_Error( 'dokan_rest_cannot_view', __( 'You do not have permission to view/edit this product.', 'dokan-lite' ), [ 'status' => 403 ] );
        }

        return true;
    }

    /**
     * Whether the current user owns the product targeted by the request.
     *
     * Centralizes the per-product authorship gate shared by the update and delete
     * permission checks. A request without an `id` has no product to own, so it
     * passes here and the capability check stays the gate.
     *
     * @since 5.0.7
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return bool
     */
    protected function check_ownership( $request ): bool {
        $product_id = $request->get_param( 'id' );

        return ! $product_id || dokan_is_product_author( $product_id );
    }

    /**
     * Check if the current user has permission for batch operations.
     *
     * @since 5.0.0
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return true|WP_Error
     */
    public function batch_items_permissions_check( $request ) {
        if ( ! current_user_can( 'dokan_edit_product' ) ) {
            return new WP_Error( 'dokan_rest_cannot_batch', __( 'You do not have permission to batch update products.', 'dokan-lite' ), [ 'status' => 403 ] );
        }

        return true;
    }

    /**
     * Check ownership before a batched product update.
     *
     * WooCommerce's WC_REST_Controller::batch_items() calls this once per "update"
     * item, so this — not the route-level batch_items_permissions_check() — is where
     * a vendor is stopped from editing another vendor's product.
     *
     * @since 5.0.7
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return true|WP_Error
     */
    public function update_item_permissions_check( $request ) {
        return $this->check_permission( $request );
    }

    /**
     * Check ownership before a batched product delete.
     *
     * Mirrors update_item_permissions_check(); the batch route is the only delete
     * path the v3 controller exposes, so the ownership gate must live here.
     *
     * @since 5.0.7
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return true|WP_Error
     */
    public function delete_item_permissions_check( $request ) {
        if ( ! current_user_can( 'dokan_delete_product' ) ) {
            return new WP_Error( 'dokan_rest_cannot_delete', __( 'You do not have permission to delete products.', 'dokan-lite' ), [ 'status' => 403 ] );
        }

        if ( ! $this->check_ownership( $request ) ) {
            return new WP_Error( 'dokan_rest_cannot_delete', __( 'You do not have permission to delete this product.', 'dokan-lite' ), [ 'status' => 403 ] );
        }

        return true;
    }

    /**
     * Bulk create, update and delete items.
     *
     * Resolves each item's payload through PayloadResolver before delegating
     * to the parent WC_REST_Controller::batch_items().
     *
     * @since 5.0.0
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return array|WP_Error
     */
    public function batch_items( $request ) {
        $body = $request->get_json_params();

        if ( empty( $body ) ) {
            $body = $request->get_body_params();
        }

        foreach ( [ 'update', 'create' ] as $key ) {
            if ( ! empty( $body[ $key ] ) && is_array( $body[ $key ] ) ) {
                foreach ( $body[ $key ] as $index => $item ) {
                    $body[ $key ][ $index ] = PayloadResolver::resolve( $item );
                }
            }
        }

        $request->set_body( wp_json_encode( $body ) );

        return parent::batch_items( $request );
    }

    /**
     * Register the routes for products.
     *
     * @since 5.0.0
     *
     * @return void
     */
    public function register_routes() {
        if ( ! self::$filter_registered ) {
            add_filter( 'rest_pre_dispatch', [ $this, 'resolve_product_payload_before_validation' ], 1, 3 );
            self::$filter_registered = true;
        }

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base,
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [ $this, 'create_item' ],
                    'permission_callback' => [ $this, 'create_item_permissions_check' ],
                    'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
                ],
                'schema' => [ $this, 'get_item_schema' ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>[\d]+)',
            [
                [
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => [ $this, 'update_item' ],
                    'permission_callback' => [ $this, 'check_permission' ],
                    'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
                ],
                'schema' => [ $this, 'get_item_schema' ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/batch',
            [
                [
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => [ $this, 'batch_items' ],
                    'permission_callback' => [ $this, 'batch_items_permissions_check' ],
                ],
                'schema' => [ $this, 'get_public_batch_schema' ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/init/fields',
            [
				[
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => [ $this, 'init_form_fields' ],
					'permission_callback' => [ $this, 'check_permission' ],
				],
			]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>[\d]+)/fields',
            [
				'args' => [
					'id' => [
						'description' => __( 'Unique identifier for the object.', 'dokan-lite' ),
						'type'        => 'integer',
					],
				],
				[
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => [ $this, 'get_form_fields' ],
					'permission_callback' => [ $this, 'check_permission' ],
				],
			]
        );
    }

    /**
     * Create a product item.
     *
     * @since 5.0.0
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return WP_Error|WP_REST_Response
     */
    public function create_item( $request ) {
        $product = parent::create_item( $request );

        if ( is_wp_error( $product ) ) {
            return $this->clarify_download_error( $product );
        }

        $product_id = (int) $product->data['id'];
        $params     = $request->get_params();
        $this->populate_post_data( $params );

        do_action( 'dokan_new_product_added', $product_id, $params );

        return $product;
    }

    /**
     * Update a product item.
     *
     * @since 5.0.0
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return WP_Error|WP_REST_Response
     */
    public function update_item( $request ) {
        $product = parent::update_item( $request );

        if ( is_wp_error( $product ) ) {
            return $this->clarify_download_error( $product );
        }

        $product_id = (int) $product->data['id'];
        $params     = $request->get_params();
        $this->populate_post_data( $params );

        do_action( 'dokan_product_updated', $product_id, $params );

        return $product;
    }

    /**
     * Replace WooCommerce's admin-oriented "approved download directory" rejection with vendor-friendly guidance.
     *
     * WooCommerce raises product_invalid_download whenever a downloadable file can't be used — most often because
     * its folder isn't an approved download directory — and tells the user to "contact a site administrator,"
     * which a vendor can't act on. The specific cause isn't exposed (the error code and data are generic) and
     * WooCommerce has already run the directory check, so rather than re-deriving it we simply restate the
     * message; the field's tooltip explains the approved-directory requirement up front.
     *
     * @since 5.0.6
     *
     * @param WP_Error $error Error returned from the parent WooCommerce REST save.
     *
     * @return WP_Error
     */
    protected function clarify_download_error( WP_Error $error ): WP_Error {
        if ( 'product_invalid_download' !== $error->get_error_code() ) {
            return $error;
        }

        return new WP_Error(
            $error->get_error_code(),
            __( 'The downloadable file could not be saved. Please pick a file from upload directory approved by the store admin.', 'dokan-lite' ),
            $error->get_error_data()
        );
    }

    /**
     * Populate $_POST with resolved request params so legacy hooks
     * (e.g. dokan_new_product_added, dokan_product_updated consumers)
     * that read from $_POST continue to work.
     *
     * @since 5.0.0
     *
     * @param array $params Resolved request parameters.
     *
     * @return void
     */
    private function populate_post_data( array $params ): void {
        $_POST = array_merge( $_POST, $params ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
    }

    /**
     * Resolve product request body before schema validation runs (so WC schema sees WC-shaped payload).
     * Runs on rest_pre_dispatch so that schema_to_wc_api is applied before args validation.
     *
     * @param mixed            $result  Response to replace the short-circuit result with.
     * @param WP_REST_Server  $server  Server instance.
     * @param WP_REST_Request $request Request used to generate the response.
     *
     * @return mixed Unchanged result so dispatch continues; request body is modified in place.
     */
    public function resolve_product_payload_before_validation( $result, $server, $request ) {
        $route = $request->get_route();
        $route_normalized = trim( $route, '/' );
        $prefix = trim( $this->namespace . '/' . $this->rest_base, '/' );
        // Only resolve for create (dokan/v3/products) or update (dokan/v3/products/123), not for .../fields.
        if ( $route_normalized !== $prefix && ! preg_match( '#^' . preg_quote( $prefix, '#' ) . '/\d+$#', $route_normalized ) ) {
            return $result;
        }

        $params   = $request->get_params();
        $resolved = PayloadResolver::resolve( $params );
        $request->set_body( wp_json_encode( $resolved ) );
        return $result;
    }

    public function init_form_fields( $request ) {

        if ( ! current_user_can( 'dokan_edit_product' ) || ! dokan_is_seller_enabled( dokan_get_current_user_id() ) ) {
            return new WP_Error(
                'dokan_rest_product_init_error',
                __( 'Invalid product ID.', 'dokan-lite' ),
                [ 'status' => 401 ]
            );
        }

        $product_id = $request->get_param( 'id' );
        $product = wc_get_product( $product_id );
        $is_new_product = false;

        if ( empty( $product_id ) ) {
            $product = new WC_Product_Simple();
            $product->set_status( 'auto-draft' );
            $product->set_name( '' );
            $product->save();
            $is_new_product = true;

            // Assign the current vendor as the product author.
            wp_update_post(
                [
                    'ID'          => $product->get_id(),
                    'post_author' => dokan_get_current_user_id(),
                ]
            );
            $product_id = $product->get_id();
        }
        // check if product exists and belongs to the current vendor
        if ( ! dokan_is_product_author( $product_id ) ) {
            return new WP_Error(
                'dokan_rest_product_init_error',
                __( 'Invalid product ID.', 'dokan-lite' ),
                [ 'status' => 404 ]
            );
        }

        $vendor_earning = dokan()->commission->get_earning_by_product( $product_id );
        $fields = dokan()->product_editor->get_schema( $product_id );

        // Preselect the requested product type on a brand-new product, e.g. the
        // "Add New Auction Product" button opens ...#/products/create?type=auction.
        $requested_type = (string) $request->get_param( 'type' );
        if ( $is_new_product && array_key_exists( $requested_type, wc_get_product_types() ) ) {
            foreach ( $fields as &$field ) {
                if ( Elements::TYPE === ( $field['id'] ?? '' ) ) {
                    $field['value'] = $requested_type;
                    break;
                }
            }
            unset( $field );
        }
        $layouts = FormSchema::get_layouts();
        $is_enabled = dokan_get_option( 'dokan_ai_image_gen_availability', 'dokan_ai', 'off' ) === 'on';
        $manager = dokan()->get_container()->get( Manager::class );
        $is_image_configured = $is_enabled && $manager->is_configured( Model::SUPPORTS_IMAGE );

        $args = [
            'form_items'             => $fields,
            'form_layouts'           => $layouts,
            'product_id'             => $product_id,
            'is_new_product'         => $is_new_product,
            'view_product_url'       => get_permalink( $product_id ),
            'vendor_earning'         => $vendor_earning,
            'can_add_new_attribute'  => dokan_get_option( 'add_new_attribute', 'dokan_selling', 'off' ) === 'on',
            'ai_settings'            => [
                'ai_text_enable'    => $manager->is_configured(),
                'ai_image_enable'   => $is_image_configured,
            ],
            'products_url'           => dokan_get_navigation_url( 'products', true ),
        ];

        $data = apply_filters( 'dokan_product_editor_args', $args, $product_id, $product );

        return rest_ensure_response( $data );
    }

    /**
     * Get item fields for form manager
     *
     * @param WP_REST_Request $request Request data.
     *
     * @since 5.0.0
     *
     * @return WP_REST_Response|WP_Error
     */
    public function get_form_fields( $request ) {
        $product_id = $request->get_param( 'id' );
        $product    = wc_get_product( $product_id );
        $force      = $request->get_param( 'force' ) ?? false;

        if ( ! $product && ! $force ) {
            return new WP_Error( 'dokan_rest_product_invalid_id', __( 'Invalid product ID.', 'dokan-lite' ), [ 'status' => 404 ] );
        }

        $data = [
            'form_items'     => dokan()->product_editor->get_schema( $product_id ),
            'vendor_earning' => dokan()->commission->get_earning_by_product( $product_id ),
        ];

        $data = apply_filters( 'dokan_rest_prepare_product_editor_fields', $data, $product, $request );

        return rest_ensure_response( $data );
    }
}
