<?php
/**
 * CustomersController — Admin REST API for customer management.
 *
 * Routes:
 *   GET    /customers          List with search, pagination, filtering
 *   GET    /customers/{id}     Single customer with addresses
 *   POST   /customers          Create customer (+ optional WP user)
 *   PUT    /customers/{id}     Update customer profile
 *   DELETE /customers/{id}     Soft-delete customer
 *   GET    /customer-groups              List groups
 *   GET    /customer-groups/{id}         Single group
 *   POST   /customer-groups              Create group
 *   PUT    /customer-groups/{id}         Update group
 *   DELETE /customer-groups/{id}         Delete group
 *
 * @package TitanCart\REST
 */

namespace TitanCart\REST;

defined( 'ABSPATH' ) || exit;

use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use TitanCart\Modules\Accounts\CustomerRepository;

class CustomersController {

    private const NS = 'titancart/v1';

    public function register_routes(): void {

        // ── Customers ────────────────────────────────────────────────────────
        \register_rest_route( self::NS, '/customers', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'list_customers' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
        ] );

        \register_rest_route( self::NS, '/customers/(?P<id>[\d]+)', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_customer' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
            [
                'methods'             => 'PUT, PATCH',
                'callback'            => [ $this, 'update_customer' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'delete_customer' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
        ] );

        \register_rest_route( self::NS, '/customers', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_customer' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
        ] );

        // ── Customer Groups ──────────────────────────────────────────────────
        \register_rest_route( self::NS, '/customer-groups', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'list_groups' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_group' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
        ] );

        \register_rest_route( self::NS, '/customer-groups/(?P<id>[\d]+)', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_group' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
            [
                'methods'             => 'PUT, PATCH',
                'callback'            => [ $this, 'update_group' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'delete_group' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
        ] );

        // ── Administrators ───────────────────────────────────────────────────
        \register_rest_route( self::NS, '/administrators', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'list_admins' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_admin' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
        ] );

        \register_rest_route( self::NS, '/administrators/(?P<id>[\d]+)', [
            [
                'methods'             => 'PUT, PATCH',
                'callback'            => [ $this, 'update_admin' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'delete_admin' ],
                'permission_callback' => [ $this, 'can_manage' ],
            ],
        ] );
    }

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

    // ═════════════════════════════════════════════════════════════════════════
    // CUSTOMERS
    // ═════════════════════════════════════════════════════════════════════════

    /**
     * GET /customers — paginated, searchable list.
     */
    public function list_customers( WP_REST_Request $request ): WP_REST_Response {
        $repo = new CustomerRepository();
        $result = $repo->get_list( [
            'search'   => sanitize_text_field( $request->get_param( 'search' ) ?? '' ),
            'status'   => sanitize_key( $request->get_param( 'status' ) ?? '' ),
            'sort'     => sanitize_key( $request->get_param( 'sort' ) ?? 'created_at' ),
            'order'    => sanitize_key( $request->get_param( 'order' ) ?? 'DESC' ),
            'page'     => max( 1, (int) ( $request->get_param( 'page' ) ?? 1 ) ),
            'per_page' => min( 100, max( 1, (int) ( $request->get_param( 'per_page' ) ?? 25 ) ) ),
        ] );

        return new WP_REST_Response( [
            'customers'   => $result['items'],
            'total'       => $result['total'],
            'total_pages' => $result['pages'],
        ] );
    }

    /**
     * GET /customers/{id} — single customer with addresses.
     */
    public function get_customer( WP_REST_Request $request ): WP_REST_Response {
        $repo     = new CustomerRepository();
        $customer = $repo->find( (int) $request->get_param( 'id' ) );

        if ( ! $customer ) {
            return new WP_REST_Response( [ 'error' => 'Customer not found' ], 404 );
        }

        $customer['addresses'] = $repo->get_addresses( (int) $customer['id'] );

        // Attach custom field definitions + per-customer values for the contact section.
        // Storefront-side surfacing is contact-only for now (S127.C-1); admin edit mirrors it.
        // Returns the visible-and-non-native rows from tc_customer_fields with each definition's
        // current value for this customer (empty string when no row exists yet in
        // tc_customer_field_values).
        $cf_mgr = new \TitanCart\Modules\Accounts\CustomerFieldsManager();
        $customer['custom_fields'] = $cf_mgr->fields_with_values( 'contact', (int) $customer['id'] );

        // Attach order history summary.
        // Joins tc_order_statuses to surface the status handle (canonical machine-readable slug
        // like "processing" / "complete" / "cancelled") under the legacy `status` field name,
        // which is what the admin SPA's CustomerEditor consumes at o.status
        // (titancart-admin.js:11834). tc_orders no longer carries a flat `status` column — it's
        // a status_id FK to tc_order_statuses; query previously selected the non-existent column
        // and silently emitted a wpdb error that corrupted the JSON response, surfacing as
        // "Failed to load customer" in the SPA.
        $db     = titancart_db();
        $orders = $db->getArray(
            "SELECT o.id, o.order_number, os.handle AS status, o.total, o.currency, o.created_at
             FROM {$db->table('orders')} o
             LEFT JOIN {$db->table('order_statuses')} os ON os.id = o.status_id
             WHERE o.customer_id = ?i AND o.deleted_at IS NULL
             ORDER BY o.created_at DESC
             LIMIT 10",
            (int) $customer['id']
        ) ?: [];
        $customer['recent_orders'] = $orders;

        return new WP_REST_Response( $customer );
    }

    /**
     * POST /customers — create new customer (+ optional WP user).
     */
    public function create_customer( WP_REST_Request $request ): WP_REST_Response {
        $body = $request->get_json_params();

        $email = sanitize_email( $body['email'] ?? '' );
        if ( ! is_email( $email ) ) {
            return new WP_REST_Response( [ 'error' => 'Valid email is required.' ], 400 );
        }

        $repo = new CustomerRepository();

        // Check for duplicate email
        if ( $repo->find_by_email( $email ) ) {
            return new WP_REST_Response( [ 'error' => 'A customer with this email already exists.' ], 409 );
        }

        // Date of birth (S276, #5 / KI-13). Shared validation with the storefront
        // profile editor; '' → null (no DOB), invalid → 400.
        $dob = \TitanCart\Modules\Accounts\AccountManager::sanitize_dob( $body['date_of_birth'] ?? '' );
        if ( $dob === false ) {
            return new WP_REST_Response( [ 'error' => 'Please enter a valid date of birth.' ], 400 );
        }

        $data = [
            'email'      => strtolower( $email ),
            'first_name' => sanitize_text_field( $body['first_name'] ?? '' ),
            'last_name'  => sanitize_text_field( $body['last_name'] ?? '' ),
            'phone'      => sanitize_text_field( $body['phone'] ?? '' ),
            'date_of_birth' => $dob,
            'status'     => in_array( $body['status'] ?? '', [ 'active', 'disabled', 'banned' ], true )
                ? $body['status'] : 'active',
            'language'   => sanitize_text_field( $body['language'] ?? 'en' ),
            'currency'   => sanitize_text_field( $body['currency'] ?? 'USD' ),
            'notes'      => sanitize_textarea_field( $body['notes'] ?? '' ),
            'created_at' => current_time( 'mysql' ),
        ];

        // Optionally create a WordPress user too
        $wp_user_id = 0;
        $password   = $body['password'] ?? '';
        if ( $password ) {
            if ( username_exists( $email ) || email_exists( $email ) ) {
                return new WP_REST_Response( [ 'error' => 'A WordPress user with this email already exists.' ], 409 );
            }
            $wp_user_id = wp_insert_user( [
                'user_login' => $email,
                'user_email' => $email,
                'user_pass'  => $password,
                'first_name' => $data['first_name'],
                'last_name'  => $data['last_name'],
                'role'       => 'subscriber',
            ] );
            if ( is_wp_error( $wp_user_id ) ) {
                return new WP_REST_Response( [ 'error' => $wp_user_id->get_error_message() ], 400 );
            }
            $data['wp_user_id'] = $wp_user_id;
            update_user_meta( $wp_user_id, 'tc_password_changed_at', time() );
        }

        $customer_id = $repo->create( $data );

        // Save address if provided
        $address = $body['address'] ?? null;
        if ( $address && ! empty( $address['address_1'] ) ) {
            $addr_data = [
                'customer_id' => $customer_id,
                'type'        => sanitize_key( $address['type'] ?? 'shipping' ),
                'is_default'  => 1,
                'first_name'  => sanitize_text_field( $address['first_name'] ?? $data['first_name'] ),
                'last_name'   => sanitize_text_field( $address['last_name'] ?? $data['last_name'] ),
                'company'     => sanitize_text_field( $address['company'] ?? '' ),
                'address_1'   => sanitize_text_field( $address['address_1'] ?? '' ),
                'address_2'   => sanitize_text_field( $address['address_2'] ?? '' ),
                'city'        => sanitize_text_field( $address['city'] ?? '' ),
                'state'       => sanitize_text_field( $address['state'] ?? '' ),
                'postcode'    => sanitize_text_field( $address['postcode'] ?? '' ),
                'country'     => sanitize_text_field( $address['country'] ?? '' ),
                'phone'       => sanitize_text_field( $address['phone'] ?? $data['phone'] ),
            ];
            $repo->create_address( $addr_data );
        }

        // Fire the same lifecycle action the storefront register path fires, so
        // back-office customer creation also drives webhooks (customer.created) and
        // any other registered listeners. Admin-created customers are active, so no
        // activation step. onCustomerCreated reads the tc_customers row, so the
        // payload is complete whether or not a WP user was created (wp_user_id may be 0).
        do_action( 'titancart_customer_registered', $customer_id, (int) $wp_user_id, $data, false );

        return new WP_REST_Response( [ 'id' => $customer_id ], 201 );
    }

    /**
     * PUT /customers/{id} — update customer profile.
     */
    public function update_customer( WP_REST_Request $request ): WP_REST_Response {
        $id   = (int) $request->get_param( 'id' );
        $body = $request->get_json_params();
        $repo = new CustomerRepository();

        $customer = $repo->find( $id );
        if ( ! $customer ) {
            return new WP_REST_Response( [ 'error' => 'Customer not found' ], 404 );
        }

        $allowed = [ 'first_name', 'last_name', 'phone', 'status', 'language', 'currency', 'notes' ];
        $data    = [];
        $mutated = false;
        foreach ( $allowed as $key ) {
            if ( isset( $body[ $key ] ) ) {
                $data[ $key ] = sanitize_text_field( $body[ $key ] );
            }
        }

        // Email change — check uniqueness
        if ( isset( $body['email'] ) ) {
            $new_email = sanitize_email( $body['email'] );
            if ( $new_email && $new_email !== $customer['email'] ) {
                $dup = $repo->find_by_email( $new_email );
                if ( $dup ) {
                    return new WP_REST_Response( [ 'error' => 'Email already in use.' ], 409 );
                }
                $data['email'] = strtolower( $new_email );
            }
        }

        // Date of birth (S276, #5 / KI-13). Dedicated block (not the text_field
        // loop) so it gets date validation and can be cleared to NULL.
        if ( isset( $body['date_of_birth'] ) ) {
            $dob = \TitanCart\Modules\Accounts\AccountManager::sanitize_dob( $body['date_of_birth'] );
            if ( $dob === false ) {
                return new WP_REST_Response( [ 'error' => 'Please enter a valid date of birth.' ], 400 );
            }
            $data['date_of_birth'] = $dob; // string YYYY-MM-DD or null (clear)
        }

        // TODO: Tax exempt requires adding tax_exempt column to tc_customers.
        // Uncomment once schema is updated:
        // if ( isset( $body['tax_exempt'] ) ) {
        //     $data['tax_exempt'] = $body['tax_exempt'] ? 1 : 0;
        // }

        if ( ! empty( $data ) ) {
            $repo->update( $id, $data );
            $mutated = true;
        }

        // Persist custom field values (contact section only — matches storefront-side surfacing).
        // Body shape: { ..., custom_fields: { "<field_id>": "<value>", ... } }
        // Empty-string values still UPSERT (admins can clear a previously-set Tax ID).
        if ( isset( $body['custom_fields'] ) && is_array( $body['custom_fields'] ) ) {
            $cf_mgr = new \TitanCart\Modules\Accounts\CustomerFieldsManager();
            $map    = [];
            foreach ( $body['custom_fields'] as $fid => $val ) {
                $fid_int = (int) $fid;
                if ( $fid_int > 0 ) {
                    $map[ $fid_int ] = is_scalar( $val ) ? (string) $val : '';
                }
            }
            if ( ! empty( $map ) ) {
                $cf_mgr->persist_section_values( $id, 'contact', $map );
                $mutated = true;
            }
        }

        // Update password if provided
        if ( ! empty( $body['password'] ) && ! empty( $customer['wp_user_id'] ) ) {
            wp_set_password( $body['password'], (int) $customer['wp_user_id'] );
            $mutated = true;
        }

        // Update address if provided
        $address = $body['address'] ?? null;
        if ( $address ) {
            $addr_fields = [ 'type', 'first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'phone' ];
            $addr_data   = [];
            foreach ( $addr_fields as $f ) {
                if ( isset( $address[ $f ] ) ) {
                    $addr_data[ $f ] = sanitize_text_field( $address[ $f ] );
                }
            }

            if ( ! empty( $address['id'] ) ) {
                // Update existing address
                $repo->update_address( (int) $address['id'], $id, $addr_data );
                $mutated = true;
            } elseif ( ! empty( $addr_data['address_1'] ) ) {
                // Create new address
                $addr_data['customer_id'] = $id;
                $addr_data['is_default']  = 1;
                $repo->create_address( $addr_data );
                $mutated = true;
            }
        }

        // Mirror the storefront profile-update action so admin edits also drive
        // webhooks (customer.updated) and other listeners. $data carries the core
        // fields that changed (it feeds the webhook's changed[] surfacing); fire only
        // when something actually changed so a no-op PUT stays silent.
        if ( $mutated ) {
            do_action( 'titancart_customer_profile_updated', $id, $data );
        }

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

    /**
     * DELETE /customers/{id} — full account removal.
     *
     * Mirrors the storefront's AccountController::delete_account flow so the
     * admin path produces identical end state. Without this:
     *
     *   - Only tc_customers got soft-deleted; tc_customer_addresses,
     *     tc_customer_field_values, wishlists, and carts kept dangling.
     *   - The wp_users row was never removed, so wp_users.user_email kept
     *     holding the email and `wp_create_user()` would refuse to let the
     *     same email be re-registered (`email_exists()` blocks it before
     *     anything else fires).
     *
     * Self-deletion is blocked: an admin can't delete the customer record
     * tied to their own WP user via this endpoint — they'd be locked out
     * of their own dashboard mid-request.
     */
    public function delete_customer( WP_REST_Request $request ): WP_REST_Response {
        $customer_id = (int) $request->get_param( 'id' );
        if ( $customer_id <= 0 ) {
            return new WP_REST_Response( [ 'success' => false, 'error' => __( 'Invalid customer id.', 'titancart-lite' ) ], 400 );
        }

        $db       = titancart_db();
        $customer = $db->getRow(
            "SELECT id, wp_user_id FROM {$db->table('customers')} WHERE id = ?i LIMIT 1",
            $customer_id
        );
        if ( ! $customer ) {
            return new WP_REST_Response( [ 'success' => false, 'error' => __( 'Customer not found.', 'titancart-lite' ) ], 404 );
        }

        $wp_user_id = (int) ( $customer['wp_user_id'] ?? 0 );

        // Don't let an admin delete their own customer record from this
        // endpoint — they'd lock themselves out mid-request and the
        // cascade below would yank their session out from under them.
        if ( $wp_user_id > 0 && $wp_user_id === get_current_user_id() ) {
            return new WP_REST_Response(
                [ 'success' => false, 'error' => __( 'You cannot delete your own customer record.', 'titancart-lite' ) ],
                403
            );
        }

        // 1. Soft-delete + anonymize tc_customers (atomic via repo helper).
        $repo = new CustomerRepository();
        $repo->soft_delete( $customer_id );

        // 2. Cascade-delete dependent rows that hold PII or stale references.
        //    Addresses + custom field values + wishlist + carts. Mirrors the
        //    storefront delete flow exactly.
        $db->query(
            "DELETE FROM {$db->table('customer_addresses')} WHERE customer_id = ?i",
            $customer_id
        );
        $db->query(
            "DELETE FROM {$db->table('customer_field_values')} WHERE customer_id = ?i",
            $customer_id
        );
        $db->query(
            "DELETE wi FROM {$db->table('wishlist_items')} wi
             INNER JOIN {$db->table('wishlists')} w ON w.id = wi.wishlist_id
             WHERE w.customer_id = ?i",
            $customer_id
        );
        $db->query(
            "DELETE FROM {$db->table('wishlists')} WHERE customer_id = ?i",
            $customer_id
        );
        $db->query(
            "DELETE ci FROM {$db->table('cart_items')} ci
             INNER JOIN {$db->table('carts')} c ON c.id = ci.cart_id
             WHERE c.customer_id = ?i",
            $customer_id
        );
        $db->query(
            "DELETE FROM {$db->table('carts')} WHERE customer_id = ?i",
            $customer_id
        );

        // 3. Hard-delete the WordPress user so user_email frees up for re-use.
        //    Reassign any authored content to user 1 (admin). We deliberately
        //    skip this step if the wp_users row is the admin (id=1) or
        //    another administrator — wp_delete_user happily nukes admins
        //    too and that's never what we want from this endpoint.
        if ( $wp_user_id > 0 && ! user_can( $wp_user_id, 'manage_options' ) ) {
            require_once ABSPATH . 'wp-admin/includes/user.php';
            wp_delete_user( $wp_user_id, 1 );
        }

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

    // ═════════════════════════════════════════════════════════════════════════
    // CUSTOMER GROUPS
    // ═════════════════════════════════════════════════════════════════════════

    /**
     * GET /customer-groups — list all groups.
     */
    public function list_groups( WP_REST_Request $request ): WP_REST_Response {
        $db  = titancart_db();
        $tG  = $db->table( 'usergroups' );
        $tGT = $db->table( 'usergroup_translations' );

        $rows = $db->getArray(
            "SELECT g.id, g.handle, g.status, COALESCE( gt.name, g.handle ) AS name
             FROM {$tG} g
             LEFT JOIN {$tGT} gt ON gt.group_id = g.id AND gt.language_code = 'en'
             ORDER BY g.id ASC"
        ) ?: [];

        // Count members per group
        $tC = $db->table( 'customers' );
        foreach ( $rows as &$row ) {
            // For now just return the row — member counting can be added when
            // we have a customer ↔ group link table.
            $row['type'] = 'Customer'; // Only the Customer type is needed for now
        }
        unset( $row );

        return new WP_REST_Response( $rows );
    }

    /**
     * GET /customer-groups/{id}
     */
    public function get_group( WP_REST_Request $request ): WP_REST_Response {
        $db  = titancart_db();
        $tG  = $db->table( 'usergroups' );
        $tGT = $db->table( 'usergroup_translations' );
        $id  = (int) $request->get_param( 'id' );

        $row = $db->getRow(
            "SELECT g.id, g.handle, g.status, COALESCE( gt.name, g.handle ) AS name
             FROM {$tG} g
             LEFT JOIN {$tGT} gt ON gt.group_id = g.id AND gt.language_code = 'en'
             WHERE g.id = ?i",
            $id
        );

        if ( ! $row ) {
            return new WP_REST_Response( [ 'error' => 'Group not found' ], 404 );
        }

        return new WP_REST_Response( $row );
    }

    /**
     * POST /customer-groups — create.
     */
    public function create_group( WP_REST_Request $request ): WP_REST_Response {
        $body   = $request->get_json_params();
        $name   = sanitize_text_field( $body['name'] ?? '' );
        $status = in_array( $body['status'] ?? '', [ 'active', 'disabled' ], true )
            ? $body['status'] : 'active';

        if ( ! $name ) {
            return new WP_REST_Response( [ 'error' => 'Group name is required.' ], 400 );
        }

        $handle = sanitize_title( $name );

        $db = titancart_db();
        $db->insert( 'usergroups', [
            'handle' => $handle,
            'status' => $status,
        ] );
        $group_id = $db->lastInsertId();

        $db->insert( 'usergroup_translations', [
            'group_id'      => $group_id,
            'language_code' => 'en',
            'name'          => $name,
        ] );

        return new WP_REST_Response( [ 'id' => $group_id ], 201 );
    }

    /**
     * PUT /customer-groups/{id} — update.
     */
    public function update_group( WP_REST_Request $request ): WP_REST_Response {
        $id   = (int) $request->get_param( 'id' );
        $body = $request->get_json_params();
        $db   = titancart_db();

        if ( isset( $body['status'] ) ) {
            $db->update( 'usergroups', [
                'status' => in_array( $body['status'], [ 'active', 'disabled' ], true ) ? $body['status'] : 'active',
            ], [ 'id' => $id ] );
        }

        if ( isset( $body['name'] ) ) {
            $name = sanitize_text_field( $body['name'] );
            $existing = $db->getField(
                "SELECT id FROM {$db->table('usergroup_translations')} WHERE group_id = ?i AND language_code = 'en'",
                $id
            );
            if ( $existing ) {
                $db->update( 'usergroup_translations', [ 'name' => $name ], [ 'group_id' => $id, 'language_code' => 'en' ] );
            } else {
                $db->insert( 'usergroup_translations', [ 'group_id' => $id, 'language_code' => 'en', 'name' => $name ] );
            }
            // Update handle too
            $db->update( 'usergroups', [ 'handle' => sanitize_title( $name ) ], [ 'id' => $id ] );
        }

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

    /**
     * DELETE /customer-groups/{id}
     */
    public function delete_group( WP_REST_Request $request ): WP_REST_Response {
        $id = (int) $request->get_param( 'id' );
        $db = titancart_db();
        $db->delete( 'usergroup_translations', [ 'group_id' => $id ] );
        $db->delete( 'usergroups', [ 'id' => $id ] );
        return new WP_REST_Response( [ 'deleted' => true ] );
    }

    // ═════════════════════════════════════════════════════════════════════════
    // ADMINISTRATORS (WordPress users with admin/editor roles)
    // ═════════════════════════════════════════════════════════════════════════

    /**
     * GET /administrators — list WordPress admin/editor users.
     */
    public function list_admins( WP_REST_Request $request ): WP_REST_Response {
        $search = sanitize_text_field( $request->get_param( 'search' ) ?? '' );

        $args = [
            'role__in' => [ 'administrator', 'editor' ],
            'orderby'  => 'display_name',
            'order'    => 'ASC',
            'number'   => 50,
        ];

        if ( $search ) {
            $args['search']         = '*' . $search . '*';
            $args['search_columns'] = [ 'user_login', 'user_email', 'display_name' ];
        }

        $query = new \WP_User_Query( $args );
        $users = $query->get_results();

        $rows = array_map( function( $u ) {
            return [
                'id'           => $u->ID,
                'email'        => $u->user_email,
                'display_name' => $u->display_name,
                'first_name'   => get_user_meta( $u->ID, 'first_name', true ),
                'last_name'    => get_user_meta( $u->ID, 'last_name', true ),
                'role'         => implode( ', ', $u->roles ),
                'registered'   => $u->user_registered,
                'last_login'   => get_user_meta( $u->ID, 'last_login', true ) ?: '',
            ];
        }, $users ?: [] );

        return new WP_REST_Response( [
            'administrators' => $rows,
            'total'          => $query->get_total(),
        ] );
    }

    /**
     * POST /administrators — create new WP admin user.
     */
    public function create_admin( WP_REST_Request $request ): WP_REST_Response {
        $body = $request->get_json_params();

        $email    = sanitize_email( $body['email'] ?? '' );
        $password = $body['password'] ?? '';

        if ( ! is_email( $email ) ) {
            return new WP_REST_Response( [ 'error' => 'Valid email is required.' ], 400 );
        }
        if ( ! $password ) {
            return new WP_REST_Response( [ 'error' => 'Password is required.' ], 400 );
        }
        if ( email_exists( $email ) ) {
            return new WP_REST_Response( [ 'error' => 'A user with this email already exists.' ], 409 );
        }

        $role = in_array( $body['role'] ?? '', [ 'administrator', 'editor' ], true )
            ? $body['role'] : 'administrator';

        $user_id = wp_insert_user( [
            'user_login'   => $email,
            'user_email'   => $email,
            'user_pass'    => $password,
            'first_name'   => sanitize_text_field( $body['first_name'] ?? '' ),
            'last_name'    => sanitize_text_field( $body['last_name'] ?? '' ),
            'display_name' => trim( ( $body['first_name'] ?? '' ) . ' ' . ( $body['last_name'] ?? '' ) ) ?: $email,
            'role'         => $role,
        ] );

        if ( is_wp_error( $user_id ) ) {
            return new WP_REST_Response( [ 'error' => $user_id->get_error_message() ], 400 );
        }

        if ( ! empty( $body['phone'] ) ) {
            update_user_meta( $user_id, 'phone', sanitize_text_field( $body['phone'] ) );
        }

        // Stamp password age and force-change flag for security settings.
        update_user_meta( $user_id, 'tc_password_changed_at', time() );
        if ( \TitanCart\Services\SettingsService::bool( 'security', 'force_admin_password_change', false ) ) {
            update_user_meta( $user_id, 'tc_must_change_password', '1' );
        }

        return new WP_REST_Response( [ 'id' => $user_id ], 201 );
    }

    /**
     * PUT /administrators/{id} — update admin user.
     */
    public function update_admin( WP_REST_Request $request ): WP_REST_Response {
        $id   = (int) $request->get_param( 'id' );
        $body = $request->get_json_params();
        $user = get_userdata( $id );

        if ( ! $user ) {
            return new WP_REST_Response( [ 'error' => 'User not found.' ], 404 );
        }

        $update = [ 'ID' => $id ];
        if ( isset( $body['first_name'] ) )  $update['first_name'] = sanitize_text_field( $body['first_name'] );
        if ( isset( $body['last_name'] ) )   $update['last_name']  = sanitize_text_field( $body['last_name'] );
        if ( isset( $body['display_name'] ) ) $update['display_name'] = sanitize_text_field( $body['display_name'] );
        if ( ! empty( $body['password'] ) )  $update['user_pass']   = $body['password'];

        if ( isset( $body['email'] ) ) {
            $new_email = sanitize_email( $body['email'] );
            if ( $new_email && $new_email !== $user->user_email ) {
                if ( email_exists( $new_email ) ) {
                    return new WP_REST_Response( [ 'error' => 'Email already in use.' ], 409 );
                }
                $update['user_email'] = $new_email;
            }
        }

        $result = wp_update_user( $update );
        if ( is_wp_error( $result ) ) {
            return new WP_REST_Response( [ 'error' => $result->get_error_message() ], 400 );
        }

        if ( isset( $body['phone'] ) ) {
            update_user_meta( $id, 'phone', sanitize_text_field( $body['phone'] ) );
        }

        // Role change
        if ( isset( $body['role'] ) && in_array( $body['role'], [ 'administrator', 'editor' ], true ) ) {
            $user->set_role( $body['role'] );
        }

        // Status — disable by setting role to subscriber (effectively removing admin access)
        if ( isset( $body['status'] ) && $body['status'] === 'disabled' && $id !== get_current_user_id() ) {
            $user->set_role( 'subscriber' );
        }

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

    /**
     * DELETE /administrators/{id} — remove admin (demote to subscriber).
     * Cannot delete yourself.
     */
    public function delete_admin( WP_REST_Request $request ): WP_REST_Response {
        $id = (int) $request->get_param( 'id' );

        if ( $id === get_current_user_id() ) {
            return new WP_REST_Response( [ 'error' => 'You cannot delete your own account.' ], 403 );
        }

        $user = get_userdata( $id );
        if ( ! $user ) {
            return new WP_REST_Response( [ 'error' => 'User not found.' ], 404 );
        }

        // Demote to subscriber instead of hard-deleting
        $user->set_role( 'subscriber' );

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