<?php

namespace SoftminalB2B\Http\Controllers;

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

use SoftminalB2B\Install;
use SoftminalB2B\Models\Group;
use SoftminalB2B\Models\PriceRule;
use SoftminalB2B\Support\Tier;

class GroupsController extends Controller
{
    public function index(\WP_REST_Request $request, $params = [])
    {
        // Member counts + a small preview of members per group in one grouped
        // query, and rule counts in another — rather than a WP_User_Query and a
        // COUNT per group (2N queries for N groups).
        $memberData = Group::memberSummary();
        $ruleCounts = $this->allRuleCounts();

        // Load every group's preview customers in a single WP_User_Query.
        $previewIds = [];
        foreach ($memberData as $data) {
            $previewIds = array_merge($previewIds, $data['ids']);
        }
        $users = Group::usersByIds($previewIds);

        $groups = [];

        foreach (Group::all() as $group) {
            $groups[] = $this->present($group, $memberData, $ruleCounts, $users);
        }

        return $this->success([
            'groups' => $groups,
            'tier'   => Tier::toArray(),
        ]);
    }

    public function show(\WP_REST_Request $request, $params = [])
    {
        $group = Group::find((int) ($params['id'] ?? 0));

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

        return $this->success(['group' => $this->present($group)]);
    }

    public function store(\WP_REST_Request $request, $params = [])
    {
        if (!Tier::canCreateGroup()) {
            return $this->error(Tier::groupLimitMessage(), 403);
        }

        $name = sanitize_text_field((string) $request->get_param('name'));

        if ($name === '') {
            return $this->error(__('Give the group a name your team will recognise, like "Wholesale".', 'softminal-b2b-wholesale-for-woocommerce'), 422);
        }

        $group = Group::create([
            'name'     => $name,
            'slug'     => Group::uniqueSlug($name),
            'settings' => $this->sanitizeSettings((array) $request->get_param('settings')),
        ]);

        return $this->success(
            ['group' => $this->present($group)],
            __('Group created.', 'softminal-b2b-wholesale-for-woocommerce')
        );
    }

    public function update(\WP_REST_Request $request, $params = [])
    {
        $id    = (int) ($params['id'] ?? 0);
        $group = Group::find($id);

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

        $attributes = [];

        if ($request->get_param('name') !== null) {
            $name = sanitize_text_field((string) $request->get_param('name'));

            if ($name === '') {
                return $this->error(__('The group still needs a name.', 'softminal-b2b-wholesale-for-woocommerce'), 422);
            }

            $attributes['name'] = $name;
            $attributes['slug'] = Group::uniqueSlug($name, $id);
        }

        if ($request->get_param('settings') !== null) {
            $attributes['settings'] = $this->sanitizeSettings((array) $request->get_param('settings'));
        }

        Group::update($id, $attributes);

        return $this->success(
            ['group' => $this->present(Group::find($id))],
            __('Group saved.', 'softminal-b2b-wholesale-for-woocommerce')
        );
    }

    /**
     * Deleting a group must not silently strand its members on B2B pricing or
     * leave orphaned rules behind — both are cleaned up here and the merchant
     * is told exactly what happened.
     */
    public function destroy(\WP_REST_Request $request, $params = [])
    {
        $id    = (int) ($params['id'] ?? 0);
        $group = Group::find($id);

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

        $memberIds = Group::memberIds($id);

        foreach ($memberIds as $userId) {
            Group::assignUser((int) $userId, null);
        }

        $rules = PriceRule::where('group_id', $id)->get();

        foreach ($rules as $rule) {
            $rule->delete();
        }

        $group->delete();

        return $this->success(
            [
                'removed_members' => count($memberIds),
                'removed_rules'   => count($rules),
            ],
            sprintf(
                /* translators: 1: number of customers, 2: number of price rules */
                __('Group deleted. %1$d customers moved back to retail pricing and %2$d price rules were removed.', 'softminal-b2b-wholesale-for-woocommerce'),
                count($memberIds),
                count($rules)
            )
        );
    }

    /**
     * Customers in a group, paginated — a wholesale group can hold thousands.
     */
    public function members(\WP_REST_Request $request, $params = [])
    {
        $id      = (int) ($params['id'] ?? 0);
        $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'));

        $result = Group::members($id, [
            'page'     => $page,
            'per_page' => $perPage,
            'search'   => $search,
        ]);

        return $this->success([
            'members'  => array_map([$this, 'presentUser'], $result['users']),
            'total'    => $result['total'],
            'page'     => $page,
            'per_page' => $perPage,
        ]);
    }

    /**
     * Assign or unassign customers. Accepts a list so bulk moves are one call
     * rather than one request per customer.
     */
    public function assign(\WP_REST_Request $request, $params = [])
    {
        $groupId = (int) ($params['id'] ?? 0);

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

        $userIds = array_filter(array_map('absint', (array) $request->get_param('user_ids')));

        if (empty($userIds)) {
            return $this->error(__('Choose at least one customer to move.', 'softminal-b2b-wholesale-for-woocommerce'), 422);
        }

        $assigned = 0;

        foreach ($userIds as $userId) {
            if (!get_userdata($userId)) {
                continue;
            }

            if (Group::assignUser($userId, $groupId > 0 ? $groupId : null)) {
                $assigned++;
            }
        }

        return $this->success(
            ['assigned' => $assigned],
            $groupId > 0
                ? sprintf(
                    /* translators: %d: number of customers */
                    _n('%d customer moved into this group.', '%d customers moved into this group.', $assigned, 'softminal-b2b-wholesale-for-woocommerce'),
                    $assigned
                )
                : sprintf(
                    /* translators: %d: number of customers */
                    _n('%d customer moved back to retail pricing.', '%d customers moved back to retail pricing.', $assigned, 'softminal-b2b-wholesale-for-woocommerce'),
                    $assigned
                )
        );
    }

    /**
     * Rule counts for every group at once: group id => count, matching the
     * unfiltered per-group count it replaces (active and paused rules alike).
     *
     * @return array<int, int>
     */
    private function allRuleCounts(): array
    {
        global $wpdb;

        $table = Install::tableName('price_rules');

        // $table is a plugin-owned identifier from Install::tableName(), not user input.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $rows = $wpdb->get_results( "SELECT group_id, COUNT(*) AS total FROM `{$table}` GROUP BY group_id", ARRAY_A );

        $counts = [];

        foreach ($rows ?: [] as $row) {
            $counts[(int) $row['group_id']] = (int) $row['total'];
        }

        return $counts;
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    private function sanitizeSettings(array $settings): array
    {
        return [
            'disallow_coupons' => !empty($settings['disallow_coupons']),
            'description'      => sanitize_text_field((string) ($settings['description'] ?? '')),
        ];
    }

    /**
     * When $memberCounts / $ruleCounts are supplied (the list endpoint), counts
     * are read from those pre-computed maps; otherwise a single group is counted
     * with one query each. Either way the group's row is never materialised just
     * to be counted.
     *
     * @param array<int, array{total: int, ids: array<int, int>}>|null $memberData group id => [total, ids]
     * @param array<int, int>|null                                     $ruleCounts group id => rule count
     * @param array<int, \WP_User>|null                                $users      preloaded preview users, keyed by id
     * @return array<string, mixed>
     */
    private function present(Group $group, ?array $memberData = null, ?array $ruleCounts = null, ?array $users = null): array
    {
        $id = (int) $group->id;

        if ($memberData !== null) {
            $entry         = $memberData[$id] ?? ['total' => 0, 'ids' => []];
            $memberCount   = $entry['total'];
            $memberPreview = [];

            foreach ($entry['ids'] as $userId) {
                if (isset($users[$userId])) {
                    $memberPreview[] = $this->presentPreviewUser($users[$userId]);
                }
            }
        } else {
            $memberCount   = Group::memberCount($id);
            $memberPreview = array_map([$this, 'presentPreviewUser'], Group::previewMembers($id));
        }

        $ruleCount = $ruleCounts !== null
            ? ($ruleCounts[$id] ?? 0)
            : PriceRule::where('group_id', $id)->count();

        return [
            'id'             => $id,
            'name'           => $group->name,
            'slug'           => $group->slug,
            'settings'       => is_array($group->settings) ? $group->settings : [],
            'member_count'   => $memberCount,
            'member_preview' => $memberPreview,
            'rule_count'     => $ruleCount,
            'created_at'     => $group->created_at,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function presentUser(\WP_User $user): array
    {
        return [
            'id'    => (int) $user->ID,
            'name'  => $user->display_name,
            'email' => $user->user_email,
        ];
    }

    /**
     * A lightweight member shape for the avatar preview: just what the stack
     * needs to draw an initialled circle (no email — that stays in the modal).
     *
     * @return array<string, mixed>
     */
    private function presentPreviewUser(\WP_User $user): array
    {
        return [
            'id'       => (int) $user->ID,
            'name'     => $user->display_name,
            'initials' => $this->initials($user->display_name),
        ];
    }

    /**
     * One or two uppercase letters for an avatar: first + last word initials,
     * falling back to a single letter for one-word names and "?" for empties.
     */
    private function initials(string $name): string
    {
        $parts = array_values(array_filter(preg_split('/\s+/', trim($name)) ?: []));

        if (empty($parts)) {
            return '?';
        }

        if (count($parts) === 1) {
            return mb_strtoupper(mb_substr($parts[0], 0, 1));
        }

        return mb_strtoupper(mb_substr($parts[0], 0, 1) . mb_substr($parts[count($parts) - 1], 0, 1));
    }
}
