<?php
/**
 * Cart REST API Controller.
 *
 * Endpoints:
 *   GET    /titancart/v1/cart                  Get current cart
 *   POST   /titancart/v1/cart/items            Add item to cart
 *   PUT    /titancart/v1/cart/items/{id}       Update item quantity
 *   DELETE /titancart/v1/cart/items/{id}       Remove item
 *   DELETE /titancart/v1/cart                  Clear cart
 *   POST   /titancart/v1/cart/coupon           Apply coupon
 *   DELETE /titancart/v1/cart/coupon           Remove coupon
 *   PATCH  /titancart/v1/cart/notes            Update notes
 *   GET    /titancart/v1/cart/count            Item count only (lightweight)
 *
 * All cart endpoints allow unauthenticated access (guests have carts too).
 * The session key is read from the tc_session cookie automatically.
 *
 * @package TitanCart\REST\Cart
 */

namespace TitanCart\REST\Cart;

use TitanCart\Modules\Cart\CartManager;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

defined( 'ABSPATH' ) || exit;

class CartController extends WP_REST_Controller {

    protected $namespace = 'titancart/v1';
    protected $rest_base = 'cart';

    private CartManager $manager;

    public function __construct() {
        $this->manager = new CartManager();
    }

    public function register_routes(): void {
        // WP.org R1 🅱4 — permission_callback audit: every cart route below is
        // public BY DESIGN — guest visitors add to cart and check out without
        // an account (standard eCommerce). All reads/writes are scoped to the
        // CALLER'S OWN cart via the TitanCart cart-session cookie (CartSession)
        // — no route can address another visitor's cart. Per-route rationale:
        // docs/WPORG-REVIEWER-NOTES.md §REST.

        // GET /cart  — full cart.
        register_rest_route( $this->namespace, '/' . $this->rest_base, [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_cart' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — reads the caller's OWN cart-session cookie only; no route can address another visitor's cart.
            ],
            // DELETE /cart — clear all items.
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'clear_cart' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — clears the caller's OWN cart session only.
            ],
        ] );

        // GET /cart/count — fast item count.
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/count', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_count' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — item count for the caller's OWN cart session.
            ],
        ] );

        // POST /cart/items — add item.
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/items', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'add_item' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — adds to the caller's OWN cart session; price/stock resolved server-side, never from the request.
                'args'                => [
                    'product_id'   => [ 'type' => 'integer', 'required' => true, 'minimum' => 1 ],
                    'quantity'     => [ 'type' => 'integer', 'default'  => 1, 'minimum' => 1 ],
                    'variation_id' => [ 'type' => 'integer' ],
                    'options'      => [ 'type' => 'object' ],
                    'variation_combo' => [ 'type' => 'object' ],
                ],
            ],
        ] );

        // PUT /cart/items/{id} — update quantity.
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/items/(?P<id>[\d]+)', [
            'args' => [ 'id' => [ 'type' => 'integer' ] ],
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'update_item' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — updates a line in the caller's OWN cart session only.
                'args'                => [
                    'quantity' => [ 'type' => 'integer', 'required' => true, 'minimum' => 0 ],
                ],
            ],
            // DELETE /cart/items/{id} — remove one item.
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'remove_item' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — removes a line from the caller's OWN cart session only.
            ],
        ] );

        // POST /cart/coupon — apply coupon.
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/coupon', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'apply_coupon' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — coupon validity/limits enforced server-side against the caller's OWN cart session.
                'args'                => [
                    'code' => [ 'type' => 'string', 'required' => true ],
                ],
            ],
            // DELETE /cart/coupon.
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'remove_coupon' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — removes a coupon from the caller's OWN cart session only.
            ],
        ] );

        // PATCH /cart/notes.
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/notes', [
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'update_notes' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — order notes stored on the caller's OWN cart session; sanitized server-side.
                'args'                => [
                    'notes' => [ 'type' => 'string', 'default' => '' ],
                ],
            ],
        ] );

        // POST /cart/gift-card — add gift card to cart.
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/gift-card', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'add_gift_card' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — adds a gift-card PURCHASE to the caller's OWN cart session (a product line, not a redemption); amount bounds validated server-side.
                'args'                => [
                    'amount'          => [ 'type' => 'number',  'required' => true ],
                    'recipient_name'  => [ 'type' => 'string',  'default'  => '' ],
                    'recipient_email' => [ 'type' => 'string',  'required' => true ],
                    'sender_name'     => [ 'type' => 'string',  'default'  => '' ],
                    'message'         => [ 'type' => 'string',  'default'  => '' ],
                ],
            ],
        ] );

        // POST /cart/bundles/{id} — add an entire "Buy Together" bundle in one
        // atomic operation. Public access (guests have carts too); CartManager
        // performs all bundle / catalog-mode / guest-policy validation.
        // S126 — wires Bundles B12 cart side.
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/bundles/(?P<id>[\d]+)', [
            'args' => [
                'id' => [ 'type' => 'integer', 'required' => true, 'minimum' => 1 ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'add_bundle' ],
                'permission_callback' => '__return_true', // Public by design: guest cart — adds a bundle to the caller's OWN cart session; contents/pricing resolved server-side.
            ],
        ] );
    }

    /** GET /cart */
    public function get_cart( WP_REST_Request $request ): WP_REST_Response {
        $lang = sanitize_text_field( $request->get_param( 'lang' ) ?? 'en' );
        $cart = $this->manager->get_cart( null, $lang );
        return new WP_REST_Response( $cart );
    }

    /** GET /cart/count */
    public function get_count( WP_REST_Request $request ): WP_REST_Response {
        $count = $this->manager->get_item_count();
        return new WP_REST_Response( [ 'count' => $count ] );
    }

    /** POST /cart/items */
    public function add_item( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $product_id   = (int) $request->get_param( 'product_id' );

        // Age Verification — block age-restricted items for unverified visitors.
        // Server-side enforcement behind the client PDP gate; no-ops when the
        // extension is inactive or the product is unrestricted.
        $age_block = \TitanCart\Extensions\AgeVerificationExtension::guard_add_to_cart( $product_id );
        if ( $age_block !== null ) {
            return new WP_Error( 'tc_age_restricted', $age_block, [ 'status' => 403 ] );
        }

        $quantity     = max( 1, (int) $request->get_param( 'quantity' ) );
        $variation_id = $request->get_param( 'variation_id' )
            ? (int) $request->get_param( 'variation_id' )
            : null;
        $options      = $request->get_param( 'options' ) ?? [];
        $options      = is_array( $options ) ? $options : [];

        // ── Item meta (subscription plan, etc.) ────────────────────────────────
        $meta = [];
        $sub_plan_id = (int) ( $request->get_param( 'subscription_plan_id' ) ?? 0 );
        if ( $sub_plan_id > 0 ) {
            $meta['subscription_plan_id'] = $sub_plan_id;
        }

        // Shared variation selections (e.g. Size: Medium selected on a single product).
        $shared_variants = $request->get_param( 'shared_variants' );
        if ( is_array( $shared_variants ) && ! empty( $shared_variants ) ) {
            $meta['shared_variants'] = array_map( 'intval', $shared_variants );
        }

        // Variation-group member combination (KI-21). The PDP threads the loaded
        // member's {feature_id: variant_id} (from #tc-variations[data-current])
        // so CartManager can confirm the add came from a deliberate PDP
        // selection, not a bare card click. Card / quick-view surfaces render
        // "Choose Options" -> PDP instead of an add button.
        $variation_combo = $request->get_param( 'variation_combo' );
        if ( is_array( $variation_combo ) && ! empty( $variation_combo ) ) {
            $clean_combo = [];
            foreach ( $variation_combo as $fid => $vid ) {
                $fid = (int) $fid;
                $vid = (int) $vid;
                if ( $fid > 0 && $vid > 0 ) {
                    $clean_combo[ $fid ] = $vid;
                }
            }
            if ( ! empty( $clean_combo ) ) {
                $meta['variation_combo'] = $clean_combo;
            }
        }

        /**
         * Filter cart item meta before the item is added to the cart.
         *
         * Allows extensions to enrich cart item metadata (stored in
         * tc_cart_items.extra_data JSON, later copied to tc_order_items.meta
         * on order placement) based on the incoming request.
         *
         * Used by the Subscriptions extension to inject `pp_tier_sites` from
         * the selected plan's `site_limit` for PluginPress license provisioning.
         *
         * @param array            $meta    Cart item meta assembled so far.
         * @param WP_REST_Request  $request The full add-to-cart request.
         */
        $meta = apply_filters( 'titancart_cart_item_extra_data', $meta, $request );
        if ( ! is_array( $meta ) ) {
            $meta = [];
        }

        // JFY deal discount — apply via hook if valid.
        $jfy_discount = (int) ( $request->get_param( 'jfy_discount' ) ?? 0 );
        if ( $jfy_discount > 0 && $jfy_discount < 100 ) {
            self::$pending_jfy_discount = $jfy_discount;
            self::$pending_jfy_product  = $product_id;
            add_action( 'titancart_add_to_cart_pre', [ __CLASS__, 'apply_jfy_discount' ], 5, 5 );
        }

        $result = $this->manager->add_item( $product_id, $quantity, $options, $variation_id, null, $meta );

        // Clean up.
        self::$pending_jfy_discount = 0;
        self::$pending_jfy_product  = 0;
        remove_action( 'titancart_add_to_cart_pre', [ __CLASS__, 'apply_jfy_discount' ], 5 );

        if ( ! $result['success'] ) {
            return new WP_Error( 'tc_cart_error', $result['message'], [ 'status' => 422 ] );
        }

        return new WP_REST_Response( $result, 201 );
    }

    // ── JFY discount hook support ────────────────────────────────────────────

    private static int $pending_jfy_discount = 0;
    private static int $pending_jfy_product  = 0;

    /**
     * Hook into titancart_add_to_cart_pre to apply JFY deal discount.
     * Validates the discount % against the Customizer-stored configuration
     * to prevent price manipulation by crafting API requests.
     */
    public static function apply_jfy_discount( &$product_id, &$quantity, &$unit_price, &$options, $cart ): void {
        $discount = self::$pending_jfy_discount;
        $pid      = self::$pending_jfy_product;

        if ( $discount <= 0 || $discount >= 100 || $pid !== $product_id ) {
            return;
        }

        // Validate against Customizer config — only allow discounts that are actually configured.
        $allowed = self::get_allowed_jfy_discounts();

        if ( ! isset( $allowed[ $pid ] ) || (int) $allowed[ $pid ] !== $discount ) {
            return; // Discount not configured for this product — reject silently.
        }

        // Apply the discount.
        $unit_price = round( $unit_price * ( 1 - $discount / 100 ), 2 );
    }

    /**
     * Read all configured JFY discounts from the Customizer (theme_mods).
     * Returns [ product_id => discount_percent, ... ]
     */
    private static function get_allowed_jfy_discounts(): array {
        $discounts = [];

        // Deal slides (main deal 1 + 2).
        foreach ( [ 'tct_jfy_main', 'tct_jfy_main2' ] as $pfx ) {
            $pid  = (int) get_theme_mod( "{$pfx}_product_id", 0 );
            $disc = (int) get_theme_mod( "{$pfx}_discount", 0 );
            if ( $pid && $disc > 0 ) {
                $discounts[ $pid ] = $disc;
            }
        }

        // Side cards (1-3).
        for ( $i = 1; $i <= 3; $i++ ) {
            $pid  = (int) get_theme_mod( "tct_jfy_side_{$i}_product_id", 0 );
            $disc = (int) get_theme_mod( "tct_jfy_side_{$i}_discount", 0 );
            if ( $pid && $disc > 0 ) {
                $discounts[ $pid ] = $disc;
            }
        }

        return $discounts;
    }

    /** PUT /cart/items/{id} */
    public function update_item( $request ): WP_REST_Response|WP_Error {
        $item_id  = (int) $request->get_param( 'id' );
        $quantity = (int) $request->get_param( 'quantity' );

        $result = $this->manager->update_item_quantity( $item_id, $quantity );

        if ( ! $result['success'] ) {
            return new WP_Error( 'tc_cart_error', $result['message'], [ 'status' => 422 ] );
        }

        return new WP_REST_Response( $result );
    }

    /** DELETE /cart/items/{id} */
    public function remove_item( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $item_id = (int) $request->get_param( 'id' );
        $result  = $this->manager->remove_item( $item_id );

        if ( ! $result['success'] ) {
            return new WP_Error( 'tc_cart_error', $result['message'], [ 'status' => 422 ] );
        }

        return new WP_REST_Response( $result );
    }

    /** DELETE /cart */
    public function clear_cart( WP_REST_Request $request ): WP_REST_Response {
        $result = $this->manager->clear_cart();
        return new WP_REST_Response( $result );
    }

    /** POST /cart/coupon */
    public function apply_coupon( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $code   = sanitize_text_field( $request->get_param( 'code' ) );
        $result = $this->manager->apply_coupon( $code );

        if ( ! $result['success'] ) {
            return new WP_Error( 'tc_coupon_error', $result['message'], [ 'status' => 422 ] );
        }

        return new WP_REST_Response( $result );
    }

    /** DELETE /cart/coupon */
    public function remove_coupon( WP_REST_Request $request ): WP_REST_Response {
        $result = $this->manager->remove_coupon();
        return new WP_REST_Response( $result );
    }

    /** PATCH /cart/notes */
    public function update_notes( WP_REST_Request $request ): WP_REST_Response {
        $notes  = $request->get_param( 'notes' ) ?? '';
        $result = $this->manager->update_notes( $notes );
        return new WP_REST_Response( $result );
    }

    /** POST /cart/gift-card */
    public function add_gift_card( WP_REST_Request $request ): WP_REST_Response {
        $result = $this->manager->add_gift_card_item(
            (float) $request->get_param( 'amount' ),
            sanitize_text_field( $request->get_param( 'recipient_name' ) ?? '' ),
            sanitize_email( $request->get_param( 'recipient_email' ) ?? '' ),
            sanitize_text_field( $request->get_param( 'sender_name' ) ?? '' ),
            sanitize_textarea_field( $request->get_param( 'message' ) ?? '' )
        );
        $status = ! empty( $result['success'] ) ? 200 : 400;
        return new WP_REST_Response( $result, $status );
    }

    /**
     * POST /cart/bundles/{id} — add an entire bundle in one atomic call.
     *
     * Thin passthrough to CartManager::add_bundle. All validation
     * (bundle status, date window, catalog-mode, guest policy, per-item
     * stock and quantity rules) happens server-side inside the manager.
     * S126 — wires Bundles B12 cart side.
     */
    public function add_bundle( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $bundle_id = (int) $request->get_param( 'id' );
        $result    = $this->manager->add_bundle( $bundle_id );

        if ( empty( $result['success'] ) ) {
            return new WP_Error(
                'tc_bundle_error',
                (string) ( $result['message'] ?? __( 'Could not add bundle.', 'titancart-lite' ) ),
                [
                    'status' => 422,
                    'errors' => $result['errors'] ?? [],
                ]
            );
        }

        return new WP_REST_Response( $result, 201 );
    }
}
