<?php

namespace SoftminalB2B\Http\Controllers;

if (!defined('ABSPATH')) {
    exit;
}

use SoftminalB2B\Models\Group;
use SoftminalB2B\Registration\Applicant;

/**
 * The trade-customer roster: everyone assigned to a B2B group, however they got
 * there (approved through registration, or assigned by hand). Distinct from
 * Applications, which is the approval queue, and from a group's own member list,
 * which is scoped to one group.
 */
class CustomersController extends Controller
{
    public function index(\WP_REST_Request $request, $params = [])
    {
        $page    = max(1, (int) $request->get_param('page'));
        $perPage = min(100, max(1, (int) ($request->get_param('per_page') ?: 20)));
        $search  = sanitize_text_field((string) $request->get_param('search'));
        $groupId = (int) $request->get_param('group_id');

        // B2B customers are exactly the users carrying a group assignment.
        $meta = $groupId > 0
            ? [['key' => Group::USER_META_KEY, 'value' => (string) $groupId]]
            : [['key' => Group::USER_META_KEY, 'compare' => 'EXISTS']];

        $args = [
            'meta_query' => $meta, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
            'number'     => $perPage,
            'paged'      => $page,
            'orderby'    => 'registered',
            'order'      => 'DESC',
        ];

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

        $query   = new \WP_User_Query($args);
        $users   = $query->get_results();
        $userIds = array_map(static fn (\WP_User $u) => (int) $u->ID, $users);

        // Order count + lifetime spend for the whole page in ONE query, instead
        // of two order-subsystem calls per row (which each recompute from scratch
        // for any customer whose Woo `_money_spent` cache is cold — e.g. every
        // customer straight after a migration import).
        $stats = $this->orderStats($userIds);

        // Load the groups once for the page: the same list drives the filter
        // dropdown and resolves each row's group name in present(), instead of a
        // Group::find() per row.
        $groups     = Group::all();
        $groupNames = [];
        $options    = [];

        foreach ($groups as $group) {
            $groupNames[(int) $group->id] = (string) $group->name;
            $options[]                    = ['id' => (int) $group->id, 'name' => (string) $group->name];
        }

        return $this->success([
            'customers' => array_map(fn (\WP_User $u) => $this->present($u, $stats, $groupNames), $users),
            'total'     => (int) $query->get_total(),
            'page'      => $page,
            'per_page'  => $perPage,
            'groups'    => $options,
        ]);
    }

    /**
     * Move a customer to a different group (or to none — retail). This is the
     * one write the roster needs; it reuses the same assignment the approval
     * flow uses, so pricing recalculates identically.
     */
    public function updateGroup(\WP_REST_Request $request, $params = [])
    {
        $userId = (int) ($params['id'] ?? 0);

        if (!get_userdata($userId)) {
            return $this->notFound(__('That customer no longer exists.', 'softminal-b2b-wholesale-for-woocommerce'));
        }

        // Distinguish an intentional "move to retail" (null / '' / '0' / 0) from
        // a malformed value: a non-numeric group_id like "abc" casts to 0 and
        // would otherwise silently unassign the customer instead of erroring.
        $raw = $request->get_param('group_id');

        if ($raw === null || $raw === '' || $raw === '0' || $raw === 0) {
            $groupId = null;
        } elseif (is_numeric($raw) && (int) $raw > 0) {
            $groupId = (int) $raw;
        } else {
            return $this->error(__('That is not a valid customer group.', 'softminal-b2b-wholesale-for-woocommerce'), 422);
        }

        if ($groupId !== null && !Group::find($groupId)) {
            return $this->notFound(__('That customer group no longer exists.', 'softminal-b2b-wholesale-for-woocommerce'));
        }

        Group::assignUser($userId, $groupId);

        return $this->success(
            ['customer' => $this->present(get_userdata($userId), $this->orderStats([$userId]))],
            $groupId === null
                ? __('Customer moved to retail pricing.', 'softminal-b2b-wholesale-for-woocommerce')
                : __('Customer group updated.', 'softminal-b2b-wholesale-for-woocommerce')
        );
    }

    /**
     * @param array<int, array{orders:int, total_spent:string}> $stats keyed by user id
     * @param array<int, string>|null $groupNames group id => name for the page;
     *        null (the single-row updateGroup path) falls back to Group::find().
     * @return array<string, mixed>
     */
    private function present(\WP_User $user, array $stats, ?array $groupNames = null): array
    {
        $id      = (int) $user->ID;
        $groupId = (int) get_user_meta($id, Group::USER_META_KEY, true);

        if (!$groupId) {
            $groupName = null;
        } elseif ($groupNames !== null) {
            $groupName = $groupNames[$groupId] ?? null;
        } else {
            $group     = Group::find($groupId);
            $groupName = $group ? $group->name : null;
        }

        $business = Applicant::business($id);
        $stat     = $stats[$id] ?? ['orders' => 0, 'total_spent' => ''];

        return [
            'id'             => $id,
            'name'           => $user->display_name ?: $user->user_login,
            'email'          => $user->user_email,
            'company'        => (string) ($business['company_name'] ?? ''),
            'group_id'       => $groupId ?: null,
            'group_name'     => $groupName,
            'orders'         => $stat['orders'],
            'total_spent'    => $stat['total_spent'],
            'since'          => mysql2date('Y-m-d', $user->user_registered),
            'preview_url'    => \SoftminalB2B\Storefront\ViewAsCustomer::previewUrl($id),
        ];
    }

    /**
     * Order count and lifetime spend for a set of customers in a single
     * `wc_get_orders()` call (HPOS-safe), rather than two per-customer Woo calls
     * that recompute from scratch on a cold cache. Orders = every real order the
     * customer placed; spend = the total of their paid orders, matching Woo's
     * own `wc_get_customer_total_spent()` semantics.
     *
     * @param array<int, int> $userIds
     * @return array<int, array{orders:int, total_spent:string}>
     */
    private function orderStats(array $userIds): array
    {
        // Aggregating a page of customers' orders means hydrating every order they
        // ever placed; a customer with thousands makes this the slow part of the
        // roster load. Cache the presented result per (sorted) id set for a short
        // window — new orders show up within minutes, which is fine for a stats
        // column, and repeat loads / re-sorts of the same page are then free.
        $cacheIds = array_values(array_unique(array_map('intval', $userIds)));
        sort($cacheIds);
        $cacheKey = 'smb2b_cust_stats_' . md5(implode(',', $cacheIds));

        if (!empty($cacheIds)) {
            $cached = get_transient($cacheKey);
            if (is_array($cached)) {
                return $cached;
            }
        }

        $totals = [];
        foreach ($userIds as $uid) {
            $totals[(int) $uid] = ['orders' => 0, 'spent' => 0.0];
        }

        if (!empty($userIds) && function_exists('wc_get_orders')) {
            $paidStatuses = function_exists('wc_get_is_paid_statuses') ? wc_get_is_paid_statuses() : ['processing', 'completed'];

            $orders = wc_get_orders([
                'customer_id' => array_map('intval', array_values($userIds)),
                'type'        => 'shop_order',
                'status'      => array_keys(wc_get_order_statuses()),
                'limit'       => -1,
                'return'      => 'objects',
            ]);

            foreach ($orders as $order) {
                $cid = (int) $order->get_customer_id();
                if (!isset($totals[$cid])) {
                    continue;
                }
                $totals[$cid]['orders']++;
                if (in_array($order->get_status(), $paidStatuses, true)) {
                    $totals[$cid]['spent'] += (float) $order->get_total();
                }
            }
        }

        $canPrice = function_exists('wc_price');
        $out      = [];
        foreach ($totals as $id => $t) {
            $out[$id] = [
                'orders'      => (int) $t['orders'],
                'total_spent' => $canPrice
                    ? html_entity_decode(wp_strip_all_tags(wc_price($t['spent'])), ENT_QUOTES, 'UTF-8')
                    : '',
            ];
        }

        if (!empty($cacheIds)) {
            set_transient($cacheKey, $out, 5 * MINUTE_IN_SECONDS);
        }

        return $out;
    }
}
