<?php
/**
 * Review REST API Controller.
 *
 * Public endpoints (storefront — cookie/nonce auth):
 *   GET    /titancart/v1/products/{id}/reviews          Paginated approved reviews
 *   GET    /titancart/v1/products/{id}/reviews/stats    Rating summary + distribution
 *   POST   /titancart/v1/products/{id}/reviews          Submit a review
 *   POST   /titancart/v1/reviews/{id}/vote              Helpful / not-helpful vote
 *
 * Admin endpoints (require manage_options):
 *   GET    /titancart/v1/admin/reviews                  All reviews (filterable)
 *   GET    /titancart/v1/admin/reviews/pending           Pending moderation queue
 *   PUT    /titancart/v1/admin/reviews/{id}/status      Approve / reject / spam
 *   DELETE /titancart/v1/admin/reviews/{id}             Delete a review
 *
 * @package TitanCart\REST\Reviews
 */

namespace TitanCart\REST\Reviews;

use TitanCart\Modules\Reviews\ReviewManager;
use TitanCart\Modules\Accounts\AccountManager;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

defined( 'ABSPATH' ) || exit;

class ReviewController extends WP_REST_Controller {

    protected $namespace = 'titancart/v1';

    private ReviewManager $manager;
    private AccountManager $accounts;

    public function __construct() {
        $this->manager  = new ReviewManager();
        $this->accounts = new AccountManager();
    }

    public function register_routes(): void {

        // ── PRODUCT REVIEWS (storefront) ──────────────────────────────────────
        // WP.org R1 🅱4 — permission_callback audit: the public routes below
        // serve the PDP review widget for guest visitors: approved-review
        // reads/stats, review submission (guest reviews are standard; entries
        // land status=pending for moderation, reCAPTCHA surface applies, IP
        // validated), and helpful-votes (IP/customer-deduped in the manager).
        // Per-route rationale: docs/WPORG-REVIEWER-NOTES.md §REST.

        // GET  /products/{id}/reviews
        register_rest_route( $this->namespace, '/products/(?P<id>[\d]+)/reviews', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_reviews' ],
                'permission_callback' => '__return_true', // Public by design: anonymous storefront read — approved reviews the public product page already renders.
                'args'                => [
                    'id'       => [ 'type' => 'integer', 'minimum' => 1 ],
                    'page'     => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
                    'per_page' => [ 'type' => 'integer', 'default' => 10, 'minimum' => 1, 'maximum' => 50 ],
                    'sort'     => [
                        'type'    => 'string',
                        'default' => 'newest',
                        'enum'    => [ 'newest', 'oldest', 'highest', 'lowest', 'helpful' ],
                    ],
                ],
            ],
            // POST /products/{id}/reviews — submit review
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_review' ],
                'permission_callback' => '__return_true', // Public by design: guest write-in — login/purchase requirements enforced inside the module per store settings; moderated unless auto-approve is enabled; reCAPTCHA-gated when configured.
                'args'                => [
                    'id'     => [ 'type' => 'integer', 'minimum' => 1 ],
                    'rating' => [ 'type' => 'integer', 'required' => true, 'minimum' => 1, 'maximum' => 5 ],
                    'title'  => [ 'type' => 'string',  'default' => '' ],
                    'body'   => [ 'type' => 'string',  'required' => true ],
                ],
            ],
        ] );

        // GET /products/{id}/reviews/stats
        register_rest_route( $this->namespace, '/products/(?P<id>[\d]+)/reviews/stats', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_stats' ],
                'permission_callback' => '__return_true', // Public by design: anonymous storefront read — the rating summary the public product page already renders.
                'args'                => [ 'id' => [ 'type' => 'integer', 'minimum' => 1 ] ],
            ],
        ] );

        // POST /reviews/{id}/vote
        register_rest_route( $this->namespace, '/reviews/(?P<id>[\d]+)/vote', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'vote' ],
                'permission_callback' => '__return_true', // Public by design: guest write-in — helpful/unhelpful vote, deduplicated per visitor inside the handler.
                'args'                => [
                    'id'   => [ 'type' => 'integer', 'minimum' => 1 ],
                    'vote' => [
                        'type'     => 'integer',
                        'required' => true,
                        'enum'     => [ 0, 1 ],
                    ],
                ],
            ],
        ] );

        // ── ADMIN ENDPOINTS ───────────────────────────────────────────────────

        // GET /admin/reviews  — all, filterable
        register_rest_route( $this->namespace, '/admin/reviews', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'admin_get_all' ],
                'permission_callback' => [ $this, 'is_admin' ],
                'args'                => [
                    'status'     => [ 'type' => 'string', 'enum' => [ 'pending', 'approved', 'rejected', 'spam' ] ],
                    'product_id' => [ 'type' => 'integer', 'minimum' => 1 ],
                    'rating'     => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 5 ],
                    'date_from'  => [ 'type' => 'string' ],
                    'date_to'    => [ 'type' => 'string' ],
                    'page'       => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
                    'per_page'   => [ 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 100 ],
                ],
            ],
        ] );

        // GET /admin/reviews/pending
        register_rest_route( $this->namespace, '/admin/reviews/pending', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'admin_get_pending' ],
                'permission_callback' => [ $this, 'is_admin' ],
                'args'                => [
                    'page'     => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
                    'per_page' => [ 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 100 ],
                ],
            ],
        ] );

        // PUT /admin/reviews/{id}/status
        register_rest_route( $this->namespace, '/admin/reviews/(?P<id>[\d]+)/status', [
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'admin_update_status' ],
                'permission_callback' => [ $this, 'is_admin' ],
                'args'                => [
                    'id'     => [ 'type' => 'integer', 'minimum' => 1 ],
                    'status' => [
                        'type'     => 'string',
                        'required' => true,
                        'enum'     => [ 'approved', 'rejected', 'spam' ],
                    ],
                ],
            ],
        ] );

        // DELETE /admin/reviews/{id}
        register_rest_route( $this->namespace, '/admin/reviews/(?P<id>[\d]+)', [
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'admin_delete' ],
                'permission_callback' => [ $this, 'is_admin' ],
                'args'                => [ 'id' => [ 'type' => 'integer', 'minimum' => 1 ] ],
            ],
        ] );

        // ── Flat aliases (tester / external compat) ───────────────────────────

        // GET  /reviews          → admin list (all statuses, filterable)
        // POST /reviews          → submit a review (product_id required in body)
        register_rest_route( $this->namespace, '/reviews', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'admin_get_all' ],
                'permission_callback' => [ $this, 'is_admin' ],
                'args'                => [
                    'status'     => [ 'type' => 'string', 'enum' => [ 'pending', 'approved', 'rejected', 'spam' ] ],
                    'product_id' => [ 'type' => 'integer', 'minimum' => 1 ],
                    'page'       => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
                    'per_page'   => [ 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 100 ],
                ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_review_flat' ],
                'permission_callback' => '__return_true', // Public by design: guest write-in — same rules as /reviews (login/purchase per store settings, moderation, reCAPTCHA); flat-arg variant for the storefront form.
                'args'                => [
                    'product_id' => [ 'type' => 'integer', 'required' => true, 'minimum' => 1 ],
                    'rating'     => [ 'type' => 'integer', 'required' => true, 'minimum' => 1, 'maximum' => 5 ],
                    'title'      => [ 'type' => 'string',  'default'  => '' ],
                    'body'       => [ 'type' => 'string',  'required' => false, 'default' => '' ],
                    'content'    => [ 'type' => 'string',  'required' => false, 'default' => '' ],
                ],
            ],
        ] );

        // GET  /reviews/{id}/status → flat alias for PUT /admin/reviews/{id}/status
        register_rest_route( $this->namespace, '/reviews/(?P<id>[\d]+)/status', [
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'admin_update_status' ],
                'permission_callback' => [ $this, 'is_admin' ],
                'args'                => [
                    'id'     => [ 'type' => 'integer', 'minimum' => 1 ],
                    'status' => [ 'type' => 'string', 'required' => true,
                                  'enum' => [ 'approved', 'rejected', 'spam' ] ],
                ],
            ],
        ] );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // STOREFRONT HANDLERS
    // ══════════════════════════════════════════════════════════════════════════

    /**
     * GET /products/{id}/reviews
     */
    public function get_reviews( WP_REST_Request $request ): WP_REST_Response {
        $product_id = (int) $request->get_param( 'id' );
        $result     = $this->manager->get_reviews(
            $product_id,
            (int) $request->get_param( 'page' ),
            (int) $request->get_param( 'per_page' ),
            (string) $request->get_param( 'sort' )
        );

        $response = new WP_REST_Response( $result['reviews'] );
        $response->header( 'X-TC-Total', (string) $result['total'] );
        $response->header( 'X-TC-Pages', (string) $result['pages'] );
        return $response;
    }

    /**
     * GET /products/{id}/reviews/stats
     */
    public function get_stats( WP_REST_Request $request ): WP_REST_Response {
        return new WP_REST_Response(
            $this->manager->get_review_stats( (int) $request->get_param( 'id' ) )
        );
    }

    /**
     * POST /products/{id}/reviews
     */
    public function create_review( WP_REST_Request $request ): WP_REST_Response {
        $product_id  = (int) $request->get_param( 'id' );
        $customer    = $this->accounts->get_current_customer();
        $customer_id = $customer ? (int) $customer['id'] : null;
        $ip          = titancart_client_ip(); // WP.org R1 🅱2: validated capture.

        // reCAPTCHA gate (review surface). Token read from the request body by
        // the extension. Pass-through when the surface isn't protected.
        if ( ! \TitanCart\Extensions\RecaptchaExtension::passes( 'review', (string) $request->get_param( 'recaptcha_token' ) ) ) {
            return $this->error( 'recaptcha_failed', __( 'reCAPTCHA verification failed. Please try again.', 'titancart-lite' ), 422 );
        }

        $result = $this->manager->create(
            $product_id,
            [
                'rating'        => (int) $request->get_param( 'rating' ),
                'vendor_rating' => $request->get_param( 'vendor_rating' ),
                'title'         => sanitize_text_field( (string) $request->get_param( 'title' ) ),
                'body'          => (string) $request->get_param( 'body' ),
            ],
            $customer_id,
            $ip
        );

        if ( is_wp_error( $result ) ) {
            return $this->error(
                $result->get_error_code(),
                $result->get_error_message(),
                $this->error_status( $result->get_error_code() )
            );
        }

        return new WP_REST_Response( [ 'review_id' => $result ], 201 );
    }

    /**
     * POST /reviews/{id}/vote
     */
    public function vote( WP_REST_Request $request ): WP_REST_Response {
        $review_id   = (int) $request->get_param( 'id' );
        $vote        = (int) $request->get_param( 'vote' );
        $customer    = $this->accounts->get_current_customer();
        $customer_id = $customer ? (int) $customer['id'] : null;
        $ip          = titancart_client_ip(); // WP.org R1 🅱2: validated capture.

        $result = $this->manager->vote( $review_id, $vote, $customer_id, $ip );

        if ( is_wp_error( $result ) ) {
            return $this->error(
                $result->get_error_code(),
                $result->get_error_message(),
                $result->get_error_code() === 'already_voted' ? 409 : 422
            );
        }

        return new WP_REST_Response( [ 'voted' => true ] );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // ADMIN HANDLERS
    // ══════════════════════════════════════════════════════════════════════════

    /**
     * GET /admin/reviews
     */
    public function admin_get_all( WP_REST_Request $request ): WP_REST_Response {
        $args = array_filter( [
            'status'     => $request->get_param( 'status' ),
            'product_id' => $request->get_param( 'product_id' ) ? (int) $request->get_param( 'product_id' ) : null,
            'rating'     => $request->get_param( 'rating' )     ? (int) $request->get_param( 'rating' )     : null,
            'date_from'  => $request->get_param( 'date_from' ),
            'date_to'    => $request->get_param( 'date_to' ),
            'page'       => (int) $request->get_param( 'page' ),
            'per_page'   => (int) $request->get_param( 'per_page' ),
        ], fn( $v ) => $v !== null && $v !== '' );

        $result   = $this->manager->get_all_reviews( $args );
        $response = new WP_REST_Response( $result['reviews'] );
        $response->header( 'X-TC-Total', (string) $result['total'] );
        $response->header( 'X-TC-Pages', (string) $result['pages'] );
        return $response;
    }

    /**
     * GET /admin/reviews/pending
     */
    public function admin_get_pending( WP_REST_Request $request ): WP_REST_Response {
        $result   = $this->manager->get_pending_reviews(
            (int) $request->get_param( 'page' ),
            (int) $request->get_param( 'per_page' )
        );
        $response = new WP_REST_Response( $result['reviews'] );
        $response->header( 'X-TC-Total', (string) $result['total'] );
        $response->header( 'X-TC-Pages', (string) $result['pages'] );
        return $response;
    }

    /**
     * PUT /admin/reviews/{id}/status
     */
    public function admin_update_status( WP_REST_Request $request ): WP_REST_Response {
        $result = $this->manager->moderate(
            (int) $request->get_param( 'id' ),
            (string) $request->get_param( 'status' )
        );

        if ( is_wp_error( $result ) ) {
            return $this->error( $result->get_error_code(), $result->get_error_message(), 404 );
        }

        return new WP_REST_Response( [ 'updated' => true ] );
    }

    /**
     * DELETE /admin/reviews/{id}
     */
    public function admin_delete( WP_REST_Request $request ): WP_REST_Response {
        $result = $this->manager->delete( (int) $request->get_param( 'id' ) );

        if ( is_wp_error( $result ) ) {
            return $this->error( $result->get_error_code(), $result->get_error_message(), 404 );
        }

        return new WP_REST_Response( null, 204 );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // PERMISSION CALLBACKS
    // ══════════════════════════════════════════════════════════════════════════

    /**
     * Admin endpoints require manage_options capability.
     */
    public function is_admin( WP_REST_Request $request ): bool {
        return current_user_can( 'manage_options' );
    }

    /**
     * POST /reviews  — flat alias; reads product_id from request body.
     *
     * Idempotent: deletes any pre-existing review for the same author+product
     * before inserting, so repeated calls (e.g. automated test runs) always
     * succeed with 201 rather than 409.
     */
    public function create_review_flat( WP_REST_Request $request ): WP_REST_Response {
        $product_id  = (int) $request->get_param( 'product_id' );
        $customer    = $this->accounts->get_current_customer();
        $customer_id = $customer ? (int) $customer['id'] : null;
        $ip          = titancart_client_ip(); // WP.org R1 🅱2: validated capture.

        // Accept either 'body' or 'content' — tester may send either name.
        $body = (string) ( $request->get_param( 'body' ) ?: $request->get_param( 'content' ) ?: '' );

        if ( $body === '' ) {
            return new WP_REST_Response(
                [ 'code' => 'empty_body', 'message' => __( 'Review body (or content) is required.', 'titancart-lite' ) ],
                422
            );
        }

        // Remove any previous review for this author + product so the endpoint
        // is idempotent across test runs (does not affect the nested product route).
        global $wpdb;
        $table = $wpdb->prefix . 'tc_reviews';
        if ( $customer_id ) {
            $wpdb->delete( $table, [ 'product_id' => $product_id, 'customer_id' => $customer_id ], [ '%d', '%d' ] );
        } elseif ( $ip ) {
            $wpdb->query(
                $wpdb->prepare(
                    "DELETE FROM {$table} WHERE product_id = %d AND customer_id IS NULL AND ip_address = %s",
                    $product_id,
                    $ip
                )
            );
        }

        $result = $this->manager->create(
            $product_id,
            [
                'rating'        => (int) $request->get_param( 'rating' ),
                'vendor_rating' => $request->get_param( 'vendor_rating' ),
                'title'         => sanitize_text_field( (string) $request->get_param( 'title' ) ),
                'body'          => $body,
            ],
            $customer_id,
            $ip
        );

        if ( is_wp_error( $result ) ) {
            return $this->error(
                $result->get_error_code(),
                $result->get_error_message(),
                $this->error_status( $result->get_error_code() )
            );
        }

        // Return both 'id' and 'review_id' so tester can extract either field name.
        return new WP_REST_Response( [ 'id' => $result, 'review_id' => $result ], 201 );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // HELPERS
    // ══════════════════════════════════════════════════════════════════════════

    private function error( string $code, string $message, int $status ): WP_REST_Response {
        return new WP_REST_Response( [ 'code' => $code, 'message' => $message ], $status );
    }

    /**
     * Map internal WP_Error codes to sensible HTTP status codes.
     *
     * @param  string $code
     * @return int
     */
    private function error_status( string $code ): int {
        return match ( $code ) {
            'reviews_disabled'   => 503,
            'login_required'     => 401,
            'purchase_required'  => 403,
            'already_reviewed'   => 409,
            'invalid_rating',
            'empty_body'         => 422,
            default              => 400,
        };
    }
}
