<?php
/**
 * Shipment REST Controller — Fulfillment admin API.
 *
 * Routes:
 *   GET    /titancart/v1/shipments                   List all shipments (paginated/filterable)
 *   POST   /titancart/v1/shipments                   Create a shipment
 *   GET    /titancart/v1/shipments/{id}              Get single shipment
 *   PUT    /titancart/v1/shipments/{id}              Update shipment
 *   DELETE /titancart/v1/shipments/{id}              Delete shipment
 *   GET    /titancart/v1/orders/{id}/shipments       Get shipments for an order
 *   GET    /titancart/v1/orders/{id}/unshipped       Get unshipped items for an order
 *   GET    /titancart/v1/shipments/statuses          Fulfillment statuses
 *   GET    /titancart/v1/shipments/carriers          Available carriers
 *   GET    /titancart/v1/shipments/needs-fulfillment Derived queue of shippable orders awaiting fulfillment (#11)
 *
 * All endpoints require manage_options.
 *
 * @package TitanCart\REST\Shipping
 */

namespace TitanCart\REST\Shipping;

use TitanCart\Modules\Shipping\ShipmentManager;
use TitanCart\Modules\Orders\OrderManager;
use TitanCart\Core\LogService;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

defined( 'ABSPATH' ) || exit;

class ShipmentController {

    private string $namespace = 'titancart/v1';
    private ShipmentManager $manager;

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

    public function register_routes(): void {

        // GET /shipments + POST /shipments
        register_rest_route( $this->namespace, '/shipments', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'list_shipments' ],
                'permission_callback' => [ $this, 'admin_permission' ],
                'args'                => [
                    'page'      => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
                    'per_page'  => [ 'type' => 'integer', 'default' => 20 ],
                    'order_id'  => [ 'type' => 'integer' ],
                    'status_id' => [ 'type' => 'integer' ],
                    'carrier'   => [ 'type' => 'string' ],
                    'search'    => [ 'type' => 'string' ],
                    'date_from' => [ 'type' => 'string' ],
                    'date_to'   => [ 'type' => 'string' ],
                ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_shipment' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // GET /shipments/{id} + PUT /shipments/{id} + DELETE /shipments/{id}
        register_rest_route( $this->namespace, '/shipments/(?P<id>\d+)', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_shipment' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'update_shipment' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'delete_shipment' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // GET /orders/{id}/shipments
        register_rest_route( $this->namespace, '/orders/(?P<id>\d+)/shipments', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_order_shipments' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // GET /orders/{id}/unshipped — returns order items with remaining unshipped qty
        register_rest_route( $this->namespace, '/orders/(?P<id>\d+)/unshipped', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_unshipped_items' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // GET /shipments/statuses
        register_rest_route( $this->namespace, '/shipments/statuses', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_statuses' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // GET /shipments/carriers
        register_rest_route( $this->namespace, '/shipments/carriers', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_carriers' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // GET /shipments/needs-fulfillment — derived queue of shippable orders
        // awaiting fulfillment (roadmap #11, option b). No id segment, so it
        // never collides with /shipments/{id} (digits only).
        register_rest_route( $this->namespace, '/shipments/needs-fulfillment', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'list_needs_fulfillment' ],
                'permission_callback' => [ $this, 'admin_permission' ],
                'args'                => [
                    'page'     => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
                    'per_page' => [ 'type' => 'integer', 'default' => 20 ],
                ],
            ],
        ] );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // HANDLERS
    // ══════════════════════════════════════════════════════════════════════════

    public function list_shipments( WP_REST_Request $request ): WP_REST_Response {
        $result = $this->manager->list_shipments( [
            'page'      => (int) $request->get_param( 'page' ),
            'per_page'  => (int) $request->get_param( 'per_page' ),
            'order_id'  => $request->get_param( 'order_id' ) ? (int) $request->get_param( 'order_id' ) : null,
            'status_id' => $request->get_param( 'status_id' ) ? (int) $request->get_param( 'status_id' ) : null,
            'carrier'   => $request->get_param( 'carrier' ),
            'search'    => $request->get_param( 'search' ),
            'date_from' => $request->get_param( 'date_from' ),
            'date_to'   => $request->get_param( 'date_to' ),
        ] );

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

    public function get_shipment( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $shipment = $this->manager->get_shipment( (int) $request['id'] );
        if ( ! $shipment ) {
            return new WP_Error( 'tc_not_found', 'Shipment not found.', [ 'status' => 404 ] );
        }
        // S285: provide a store-local value for the <input type="datetime-local">
        // edit picker, since shipped_at is now stored in UTC (Pattern #146).
        $shipment['shipped_at_input'] = $this->manager->utc_to_input( $shipment['shipped_at'] ?? null );
        return new WP_REST_Response( $shipment );
    }

    public function create_shipment( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $order_id = (int) $request->get_param( 'order_id' );
        $items    = $request->get_param( 'items' );

        if ( ! $order_id ) {
            return new WP_Error( 'tc_missing_order', 'order_id is required.', [ 'status' => 422 ] );
        }
        if ( ! is_array( $items ) || empty( $items ) ) {
            return new WP_Error( 'tc_missing_items', 'At least one item with order_item_id and quantity is required.', [ 'status' => 422 ] );
        }

        $shipment_id = $this->manager->create( [
            'order_id'           => $order_id,
            'items'              => $items,
            'tracking_number'    => $request->get_param( 'tracking_number' ) ?? '',
            'carrier'            => $request->get_param( 'carrier' ) ?? '',
            'shipping_method_id' => $request->get_param( 'shipping_method_id' ),
            'status_id'          => $request->get_param( 'status_id' ),
            'comments'           => $request->get_param( 'comments' ) ?? '',
            'shipped_at'         => $request->get_param( 'shipped_at' ),
        ] );

        if ( ! $shipment_id ) {
            return new WP_Error( 'tc_create_failed', 'Failed to create shipment. All items may already be shipped.', [ 'status' => 422 ] );
        }

        LogService::info( 'orders', 'create', 'Shipment #' . $shipment_id . ' created for order #' . $order_id, [
            'object_type' => 'shipment',
            'object_id'   => $shipment_id,
        ] );

        return new WP_REST_Response( $this->manager->get_shipment( $shipment_id ), 201 );
    }

    public function update_shipment( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id = (int) $request['id'];
        if ( ! $this->manager->get_shipment( $id ) ) {
            return new WP_Error( 'tc_not_found', 'Shipment not found.', [ 'status' => 404 ] );
        }

        $data = array_filter( [
            'tracking_number'    => $request->get_param( 'tracking_number' ),
            'carrier'            => $request->get_param( 'carrier' ),
            'status_id'          => $request->get_param( 'status_id' ) !== null ? (int) $request->get_param( 'status_id' ) : null,
            'comments'           => $request->get_param( 'comments' ),
            'shipped_at'         => $request->get_param( 'shipped_at' ),
            'shipping_method_id' => $request->get_param( 'shipping_method_id' ) !== null ? (int) $request->get_param( 'shipping_method_id' ) : null,
        ], fn( $v ) => $v !== null );

        $this->manager->update( $id, $data );

        LogService::info( 'orders', 'update', 'Shipment #' . $id . ' updated', [
            'object_type' => 'shipment',
            'object_id'   => $id,
        ] );

        return new WP_REST_Response( $this->manager->get_shipment( $id ) );
    }

    public function delete_shipment( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id = (int) $request['id'];
        if ( ! $this->manager->get_shipment( $id ) ) {
            return new WP_Error( 'tc_not_found', 'Shipment not found.', [ 'status' => 404 ] );
        }

        $this->manager->delete( $id );

        LogService::info( 'orders', 'delete', 'Shipment #' . $id . ' deleted', [
            'object_type' => 'shipment',
            'object_id'   => $id,
        ] );

        return new WP_REST_Response( [ 'deleted' => true, 'id' => $id ] );
    }

    public function get_order_shipments( WP_REST_Request $request ): WP_REST_Response {
        return new WP_REST_Response( $this->manager->get_by_order( (int) $request['id'] ) );
    }

    public function get_unshipped_items( WP_REST_Request $request ): WP_REST_Response {
        $order_id = (int) $request['id'];

        $om    = new OrderManager();
        $items = $om->get_items( $order_id );
        $shipped = $this->manager->get_shipped_quantities( $order_id );

        $unshipped = [];
        foreach ( $items as $item ) {
            $oi_id     = (int) $item['id'];
            $ordered   = (int) ( $item['quantity'] ?? 0 );
            $shipped_q = (int) ( $shipped[ $oi_id ] ?? 0 );
            $remaining = $ordered - $shipped_q;

            $unshipped[] = [
                'order_item_id' => $oi_id,
                'product_id'    => (int) $item['product_id'],
                'name'          => $item['name'] ?? '',
                'sku'           => $item['sku'] ?? '',
                'thumbnail'     => $item['thumbnail'] ?? null,
                'ordered'       => $ordered,
                'shipped'       => $shipped_q,
                'remaining'     => max( 0, $remaining ),
            ];
        }

        return new WP_REST_Response( $unshipped );
    }

    public function get_statuses( WP_REST_Request $request ): WP_REST_Response {
        return new WP_REST_Response( $this->manager->get_fulfillment_statuses() );
    }

    public function get_carriers( WP_REST_Request $request ): WP_REST_Response {
        return new WP_REST_Response( $this->manager->get_carriers() );
    }

    /**
     * GET /shipments/needs-fulfillment — derived queue of shippable orders
     * still awaiting fulfillment (roadmap #11, option b). No placeholder
     * shipment rows are created; an order leaves the queue once its shippable
     * items are fully shipped via the normal Create-Shipment flow.
     */
    public function list_needs_fulfillment( WP_REST_Request $request ): WP_REST_Response {
        $result = $this->manager->list_needs_fulfillment( [
            'page'     => (int) $request->get_param( 'page' ),
            'per_page' => (int) $request->get_param( 'per_page' ),
        ] );

        return new WP_REST_Response( $result );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // PERMISSIONS
    // ══════════════════════════════════════════════════════════════════════════

    public function admin_permission(): bool {
        return current_user_can( 'manage_options' );
    }
}
