<?php
declare(strict_types=1);

namespace Celena\Plugin\Shop\Admin;

use Celena\Core\Database\Connection;
use Celena\Core\Http\Pagination;
use Celena\Core\Http\Request;
use Celena\Core\Http\Response;
use Celena\Core\Plugin\Hook;
use Celena\Core\Security\Auth;
use Celena\Core\Security\Csrf;
use Celena\Core\Security\Filter;
use Celena\Core\Template\Engine;
use Celena\Module\AdminBase\AdminController;
use Celena\Module\Settings\Options;
use Celena\Plugin\Shop\Support\CustomerRepo;
use Celena\Plugin\Shop\Support\ShopSettings;

/**
 * Заказы магазина: список, карточка, ручное создание, изменение деталей,
 * история событий. История пишется на каждое значимое действие (статус,
 * оплата, заметка) — admin видит «кто и когда что менял».
 *
 * Связь с клиентами автоматическая: при сохранении заказа upsertFromOrder()
 * заводит клиента, после чего recalcAggregates() пересчитывает агрегаты.
 */
final class OrdersAdminController extends AdminController
{
    private const STATUSES         = ['new', 'paid', 'shipped', 'delivered', 'cancelled'];
    private const PAYMENT_STATUSES = ['pending', 'paid', 'refunded', 'cancelled'];

    /** Подписи статусов берутся из shop.status.<code> и shop.payment.<code>
     *  через i18n-Translator. БД хранит только машинные коды. */
    private function tr(): \Celena\Core\I18n\Translator
    {
        return \Celena\Core\Kernel::container()->make(\Celena\Core\I18n\Translator::class);
    }
    private function statusLabel(string $code): string
    {
        $t = $this->tr()->translate('shop.status.' . $code);
        return $t === 'shop.status.' . $code ? $code : $t;
    }
    private function paymentLabel(string $code): string
    {
        $t = $this->tr()->translate('shop.payment.' . $code);
        return $t === 'shop.payment.' . $code ? $code : $t;
    }
    /** [{code, label}] для select-ов и табов. */
    private function statusOptions(): array
    {
        $out = [];
        foreach (self::STATUSES as $s) $out[] = ['code' => $s, 'label' => $this->statusLabel($s)];
        return $out;
    }
    private function paymentOptions(): array
    {
        $out = [];
        foreach (self::PAYMENT_STATUSES as $s) $out[] = ['code' => $s, 'label' => $this->paymentLabel($s)];
        return $out;
    }

    public function __construct(
        Engine $engine,
        Auth $auth,
        Options $options,
        private readonly Connection $conn,
    ) {
        parent::__construct($engine, $auth, $options);
    }

    public function index(Request $request): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        $shop = new ShopSettings($this->options);

        $tab    = (string) $request->query('tab', 'all');
        if (!in_array($tab, array_merge(['all'], self::STATUSES), true)) $tab = 'all';
        $search = trim((string) $request->query('q', ''));
        $page   = Filter::int($request->query('page', 1), 1);

        $applyFilters = function ($q) use ($tab, $search) {
            if ($tab !== 'all') $q->where('status', '=', $tab);
            if ($search !== '') {
                $like = '%' . mb_strtolower($search, 'UTF-8') . '%';
                $q->whereRaw(
                    '(LOWER(' . $this->conn->quoteId('number') . ') LIKE ? OR '
                    . 'LOWER(' . $this->conn->quoteId('customer_name') . ') LIKE ? OR '
                    . 'LOWER(' . $this->conn->quoteId('customer_email') . ') LIKE ? OR '
                    . 'LOWER(COALESCE(' . $this->conn->quoteId('customer_phone') . ", '')) LIKE ?)",
                    [$like, $like, $like, $like]
                );
            }
        };

        $countQ = $this->conn->builder('shop_orders');
        $applyFilters($countQ);
        $total = $countQ->count();

        $p = new Pagination($page, 25, $total);

        $listQ = $this->conn->builder('shop_orders');
        $applyFilters($listQ);
        $rows = $listQ->orderBy('created_at', 'desc')->orderBy('id', 'desc')
            ->limit($p->perPage)->offset($p->offset)->get();

        // Кол-во позиций в каждом заказе — одним запросом.
        $orderIds = array_map(static fn ($r) => (int) $r['id'], $rows);
        $itemsCount = [];
        if ($orderIds !== []) {
            try {
                $sql = 'SELECT ' . $this->conn->quoteId('order_id') . ' AS oid, COUNT(*) AS c '
                    . 'FROM ' . $this->conn->table_q('shop_order_items') . ' '
                    . 'WHERE ' . $this->conn->quoteId('order_id') . ' IN ('
                    . implode(',', array_map('intval', $orderIds)) . ') '
                    . 'GROUP BY ' . $this->conn->quoteId('order_id');
                foreach ($this->conn->run($sql, [])->fetchAll() as $r) {
                    $itemsCount[(int) $r['oid']] = (int) $r['c'];
                }
            } catch (\Throwable) {}
        }

        foreach ($rows as &$r) {
            $r['total_fmt']    = $shop->formatPrice((float) ($r['total'] ?? 0));
            $r['date_short']   = !empty($r['created_at'])
                ? date('d.m.Y', strtotime((string) $r['created_at'])) : '';
            $r['time_short']   = !empty($r['created_at'])
                ? date('H:i', strtotime((string) $r['created_at'])) : '';
            $r['items_count']  = $itemsCount[(int) $r['id']] ?? 0;
            $r['payment_status'] = (string) ($r['payment_status'] ?? 'pending');
            $r['source']         = (string) ($r['source'] ?? 'web');
            $r['status_label']         = $this->statusLabel((string) $r['status']);
            $r['payment_status_label'] = $this->paymentLabel((string) $r['payment_status']);
            $srcKey = 'shop.source.' . (string) $r['source'];
            $srcTranslated = $this->tr()->translate($srcKey);
            $r['source_label'] = $srcTranslated === $srcKey ? (string) $r['source'] : $srcTranslated;
        }
        unset($r);

        // Счётчики табов: один проход GROUP BY вместо N+1 запросов.
        $tabCountsMap = [];
        try {
            $rowsCnt = $this->conn->run(
                'SELECT ' . $this->conn->quoteId('status') . ' AS s, COUNT(*) AS c '
                . 'FROM ' . $this->conn->table_q('shop_orders') . ' '
                . 'GROUP BY ' . $this->conn->quoteId('status'),
                []
            )->fetchAll();
            foreach ($rowsCnt as $r) $tabCountsMap[(string) $r['s']] = (int) $r['c'];
        } catch (\Throwable) {}
        $tabCounts = ['all' => array_sum($tabCountsMap)];
        $statusesWithCount = [];
        foreach (self::STATUSES as $s) {
            $tabCounts[$s] = $tabCountsMap[$s] ?? 0;
            $statusesWithCount[] = [
                'code'      => $s,
                'label'     => $this->statusLabel($s),
                'count'     => $tabCounts[$s],
                'is_active' => $tab === $s,
            ];
        }

        $qsParts = [];
        foreach (['tab', 'q'] as $k) {
            $v = $request->query($k, '');
            if ($v !== '' && $v !== null) {
                $qsParts[] = rawurlencode($k) . '=' . rawurlencode((string) $v);
            }
        }
        $qs = $qsParts === [] ? '' : ('&' . implode('&', $qsParts));

        return $this->view('plugins/shop/admin/orders_index', [
            'orders'     => $rows,
            'pagination' => $p->toArray('/admin/shop/orders?page=:page' . $qs),
            'tab'        => $tab,
            'q'          => $search,
            'tab_counts' => $tabCounts,
            'statuses'   => self::STATUSES,
            'status_tabs' => $statusesWithCount,
            'total'      => $total,
        ], 'shop-orders', $this->tr()->translate('shop.orders_list.title'));
    }

    public function show(Request $request, array $params): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        $id = Filter::int($params['id'] ?? 0);
        $order = $this->conn->builder('shop_orders')->where('id', '=', $id)->first();
        if ($order === null) return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.order_not_found'), 'error');

        $shop = new ShopSettings($this->options);

        $items = $this->conn->builder('shop_order_items')->where('order_id', '=', $id)->get();
        foreach ($items as &$it) {
            $it['subtotal_fmt'] = $shop->formatPrice((float) $it['subtotal']);
            $it['price_fmt']    = $shop->formatPrice((float) $it['price']);
        }
        unset($it);

        // История событий
        $history = [];
        try {
            $history = $this->conn->builder('shop_order_history')
                ->where('order_id', '=', $id)
                ->orderBy('created_at', 'desc')
                ->limit(100)
                ->get();
            // Имя автора события
            $userIds = array_filter(array_map(static fn ($h) => (int) ($h['user_id'] ?? 0), $history));
            $userMap = [];
            if ($userIds !== []) {
                $users = $this->conn->builder('celena_users')->whereIn('id', array_values(array_unique($userIds)))->get();
                foreach ($users as $u) $userMap[(int) $u['id']] = (string) $u['name'];
            }
            foreach ($history as &$h) {
                $h['author']   = $userMap[(int) ($h['user_id'] ?? 0)] ?? 'Система';
                $h['date_fmt'] = !empty($h['created_at'])
                    ? date('d.m.Y H:i', strtotime((string) $h['created_at'])) : '';
            }
            unset($h);
        } catch (\Throwable) {}

        // Профиль клиента (если связан)
        $customer = null;
        if (!empty($order['customer_id'])) {
            $customer = $this->conn->builder('shop_customers')->where('id', '=', (int) $order['customer_id'])->first();
            if ($customer !== null) {
                $customer['total_spent_fmt'] = $shop->formatPrice((float) ($customer['total_spent'] ?? 0));
            }
        }

        // Доступные методы доставки/оплаты для редактора
        $shippingMethods = [];
        $paymentMethods = [];
        try {
            $shippingMethods = $this->conn->builder('shop_shipping_methods')
                ->where('is_active', '=', true)->orderBy('sort_order', 'asc')->get();
            $paymentMethods = $this->conn->builder('shop_payment_methods')
                ->where('is_active', '=', true)->orderBy('sort_order', 'asc')->get();
        } catch (\Throwable) {}

        // Форматы для вывода
        $order['total_fmt']         = $shop->formatPrice((float) $order['total']);
        $order['subtotal_fmt']      = $shop->formatPrice((float) ($order['subtotal'] ?? $order['total']));
        $order['discount_fmt']      = $shop->formatPrice((float) ($order['discount'] ?? 0));
        $order['tax_fmt']           = $shop->formatPrice((float) ($order['tax'] ?? 0));
        $order['shipping_cost_fmt'] = $shop->formatPrice((float) ($order['shipping_cost'] ?? 0));
        $order['has_discount']      = (float) ($order['discount'] ?? 0) > 0;
        $order['has_tax']           = (float) ($order['tax'] ?? 0) > 0;
        $order['has_shipping_cost'] = (float) ($order['shipping_cost'] ?? 0) > 0;
        $order['date_full']         = !empty($order['created_at'])
            ? date('d.m.Y H:i', strtotime((string) $order['created_at'])) : '';
        $order['paid_at_fmt']       = !empty($order['paid_at'])
            ? date('d.m.Y H:i', strtotime((string) $order['paid_at'])) : '';
        $order['shipped_at_fmt']    = !empty($order['shipped_at'])
            ? date('d.m.Y H:i', strtotime((string) $order['shipped_at'])) : '';
        $order['delivered_at_fmt']  = !empty($order['delivered_at'])
            ? date('d.m.Y H:i', strtotime((string) $order['delivered_at'])) : '';
        $order['payment_status']       = (string) ($order['payment_status'] ?? 'pending');
        $order['source']               = (string) ($order['source'] ?? 'web');
        $order['status_label']         = $this->statusLabel((string) $order['status']);
        $order['payment_status_label'] = $this->paymentLabel((string) $order['payment_status']);
        $srcKey = 'shop.source.' . (string) $order['source'];
        $srcTr = $this->tr()->translate($srcKey);
        $order['source_label']         = $srcTr === $srcKey ? (string) $order['source'] : $srcTr;

        // Добавляем человекочитаемые подписи к событиям истории.
        foreach ($history as &$h) {
            $event = (string) ($h['event'] ?? '');
            if ($event === 'status_changed') {
                $h['from_label'] = $this->statusLabel((string) ($h['from_value'] ?? ''));
                $h['to_label']   = $this->statusLabel((string) ($h['to_value'] ?? ''));
            } elseif ($event === 'payment_changed') {
                $h['from_label'] = $this->paymentLabel((string) ($h['from_value'] ?? ''));
                $h['to_label']   = $this->paymentLabel((string) ($h['to_value'] ?? ''));
            } else {
                $h['from_label'] = (string) ($h['from_value'] ?? '');
                $h['to_label']   = (string) ($h['to_value'] ?? '');
            }
        }
        unset($h);

        return $this->view('plugins/shop/admin/orders_show', [
            'order'             => $order,
            'items'             => $items,
            'history'           => $history,
            'customer'          => $customer,
            'statuses'          => self::STATUSES,
            'payment_statuses'  => self::PAYMENT_STATUSES,
            'status_options'    => $this->statusOptions(),
            'payment_options'   => $this->paymentOptions(),
            'shipping_methods'  => $shippingMethods,
            'payment_methods'   => $paymentMethods,
        ], 'shop-orders', $this->tr()->translate('shop.order_number') . ' ' . $order['number']);
    }

    public function setStatus(Request $request, array $params): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        if (!Csrf::validate((string) $request->input('_csrf'))) {
            return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.csrf'), 'error');
        }
        $id = Filter::int($params['id'] ?? 0);
        $status = Filter::in($request->input('status'), self::STATUSES, null);
        if ($status === null) return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.order_invalid_status'), 'error');

        $existing = $this->conn->builder('shop_orders')->where('id', '=', $id)->first();
        if ($existing === null) return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.order_not_found'), 'error');
        if ($existing['status'] === $status) {
            return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.status_unchanged'));
        }

        $update = ['status' => $status, 'updated_at' => date('Y-m-d H:i:s')];
        // Авто-отметки таймстампов по статусу.
        if ($status === 'shipped'   && empty($existing['shipped_at']))   $update['shipped_at']   = date('Y-m-d H:i:s');
        if ($status === 'delivered' && empty($existing['delivered_at'])) $update['delivered_at'] = date('Y-m-d H:i:s');
        $this->conn->builder('shop_orders')->where('id', '=', $id)->update($update);

        $this->log($id, 'status_changed', (string) $existing['status'], $status,
            (string) $request->input('comment', ''));

        // Пересчёт агрегатов клиента (отмена/восстановление меняет total_spent).
        if (!empty($existing['customer_id'])) {
            (new CustomerRepo($this->conn))->recalcAggregates((int) $existing['customer_id']);
        }

        Hook::action('shop.order.status_changed', [
            'id' => $id, 'number' => (string) $existing['number'],
            'from' => (string) $existing['status'], 'to' => $status,
            'email' => (string) $existing['customer_email'],
        ]);
        return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.status_changed', ['status' => $this->statusLabel($status)]));
    }

    public function setPaymentStatus(Request $request, array $params): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        if (!Csrf::validate((string) $request->input('_csrf'))) {
            return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.csrf'), 'error');
        }
        $id = Filter::int($params['id'] ?? 0);
        $status = Filter::in($request->input('payment_status'), self::PAYMENT_STATUSES, null);
        if ($status === null) return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.order_invalid_status'), 'error');

        $existing = $this->conn->builder('shop_orders')->where('id', '=', $id)->first();
        if ($existing === null) return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.order_not_found'), 'error');
        if (($existing['payment_status'] ?? '') === $status) {
            return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.payment_unchanged'));
        }

        $update = ['payment_status' => $status, 'updated_at' => date('Y-m-d H:i:s')];
        if ($status === 'paid' && empty($existing['paid_at'])) {
            $update['paid_at'] = date('Y-m-d H:i:s');
        }
        $this->conn->builder('shop_orders')->where('id', '=', $id)->update($update);

        $this->log($id, 'payment_changed',
            (string) ($existing['payment_status'] ?? 'pending'),
            $status, (string) $request->input('comment', ''));

        return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.payment_changed', ['status' => $this->paymentLabel($status)]));
    }

    /** Изменить детали доставки/оплаты/заметки заказа. */
    public function updateDetails(Request $request, array $params): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        if (!Csrf::validate((string) $request->input('_csrf'))) {
            return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.csrf'), 'error');
        }
        $id = Filter::int($params['id'] ?? 0);
        $existing = $this->conn->builder('shop_orders')->where('id', '=', $id)->first();
        if ($existing === null) return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.order_not_found'), 'error');

        $shipMethod = $this->resolveMethod('shop_shipping_methods', $request->input('shipping_method_id'));
        $payMethod  = $this->resolveMethod('shop_payment_methods',  $request->input('payment_method_id'));

        $shippingCost = (float) $request->input('shipping_cost', 0);
        $discount     = (float) $request->input('discount', 0);
        $subtotal     = (float) ($existing['subtotal'] ?? $existing['total']);
        $tax          = (float) ($existing['tax'] ?? 0);
        $total        = max(0.0, $subtotal + $shippingCost + $tax - $discount);

        $this->conn->builder('shop_orders')->where('id', '=', $id)->update([
            'customer_name'    => Filter::string($request->input('customer_name'), 191),
            'customer_email'   => Filter::email($request->input('customer_email')) ?: (string) $existing['customer_email'],
            'customer_phone'   => Filter::string($request->input('customer_phone'), 32),
            'customer_address' => Filter::string($request->input('customer_address'), 1000),
            'admin_note'       => Filter::string($request->input('admin_note'), 5000),
            'shipping_method_id'    => $shipMethod['id'],
            'shipping_method_title' => $shipMethod['title'],
            'shipping_cost'         => $shippingCost,
            'payment_method_id'     => $payMethod['id'],
            'payment_method_title'  => $payMethod['title'],
            'discount'              => $discount,
            'total'                 => $total,
            'updated_at'            => date('Y-m-d H:i:s'),
        ]);

        $this->log($id, 'note', null, null, 'Детали заказа изменены администратором');

        // Если у заказа уже есть customer_id — синхронизируем контакты и пересчитываем агрегаты.
        if (!empty($existing['customer_id'])) {
            (new CustomerRepo($this->conn))->upsertFromOrder([
                'email'   => (string) ($existing['customer_email'] ?? ''),
                'name'    => (string) $request->input('customer_name', ''),
                'phone'   => (string) $request->input('customer_phone', ''),
                'address' => (string) $request->input('customer_address', ''),
            ]);
            (new CustomerRepo($this->conn))->recalcAggregates((int) $existing['customer_id']);
        }

        return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.details_updated'));
    }

    public function addNote(Request $request, array $params): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        if (!Csrf::validate((string) $request->input('_csrf'))) {
            return $this->redirect('/admin/shop/orders', $this->tr()->translate('shop.flash.csrf'), 'error');
        }
        $id = Filter::int($params['id'] ?? 0);
        $note = trim((string) $request->input('note', ''));
        if ($note === '') return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.note_empty'), 'error');
        $this->log($id, 'note', null, null, $note);
        return $this->redirect('/admin/shop/orders/' . $id, $this->tr()->translate('shop.flash.note_added'));
    }

    /** Форма ручного создания заказа — выбор клиента (новый или существующий) + товары. */
    public function create(Request $request): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        $shippingMethods = [];
        $paymentMethods = [];
        $customers = [];
        try {
            $shippingMethods = $this->conn->builder('shop_shipping_methods')
                ->where('is_active', '=', true)->orderBy('sort_order', 'asc')->get();
            $paymentMethods = $this->conn->builder('shop_payment_methods')
                ->where('is_active', '=', true)->orderBy('sort_order', 'asc')->get();
            // Топ-30 клиентов для подсказки в селекте
            $customers = $this->conn->builder('shop_customers')
                ->orderBy('last_order_at', 'desc')
                ->orderBy('id', 'desc')
                ->limit(30)->get();
        } catch (\Throwable) {}

        return $this->view('plugins/shop/admin/orders_create', [
            'shipping_methods' => $shippingMethods,
            'payment_methods'  => $paymentMethods,
            'customers'        => $customers,
            'statuses'         => self::STATUSES,
            'status_options'   => $this->statusOptions(),
            'payment_options'  => $this->paymentOptions(),
        ], 'shop-orders', $this->tr()->translate('shop.order_create.title'));
    }

    public function storeManual(Request $request): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        if (!Csrf::validate((string) $request->input('_csrf'))) {
            return $this->redirect('/admin/shop/orders/create', $this->tr()->translate('shop.flash.csrf'), 'error');
        }

        // Товары приходят массивом item[product_id][] / item[price][] / item[quantity][] / item[title][]
        $itemsRaw = (array) $request->input('item', []);
        $productIds = array_map('intval', (array) ($itemsRaw['product_id'] ?? []));
        $prices     = (array) ($itemsRaw['price'] ?? []);
        $quantities = (array) ($itemsRaw['quantity'] ?? []);
        $titles     = (array) ($itemsRaw['title'] ?? []);
        $skus       = (array) ($itemsRaw['sku'] ?? []);

        $items = [];
        $subtotal = 0.0;
        for ($i = 0, $n = count($productIds); $i < $n; $i++) {
            $pid = (int) $productIds[$i];
            $qty = max(1, (int) ($quantities[$i] ?? 1));
            $price = (float) ($prices[$i] ?? 0);
            $title = trim((string) ($titles[$i] ?? ''));
            $sku   = trim((string) ($skus[$i] ?? ''));

            // Если выбран существующий товар — берём актуальные данные из БД (источник истины).
            if ($pid > 0) {
                $p = $this->conn->builder('shop_products')->where('id', '=', $pid)->first();
                if ($p !== null) {
                    if ($title === '') $title = (string) $p['title'];
                    if ($sku === '')   $sku   = (string) ($p['sku'] ?? '');
                    if ($price <= 0)   $price = (float) $p['price'];
                }
            }
            if ($title === '' || $price <= 0 || $qty <= 0) continue;

            $sub = $price * $qty;
            $items[] = [
                'product_id' => $pid,
                'title' => $title,
                'sku' => $sku,
                'price' => $price,
                'quantity' => $qty,
                'subtotal' => $sub,
            ];
            $subtotal += $sub;
        }

        if ($items === []) {
            return $this->redirect('/admin/shop/orders/create', $this->tr()->translate('shop.flash.manual_need_item'), 'error');
        }

        $email = Filter::email($request->input('customer_email'));
        $name  = Filter::string($request->input('customer_name'), 191);
        if ($email === null || $name === '') {
            return $this->redirect('/admin/shop/orders/create', $this->tr()->translate('shop.flash.manual_need_contacts'), 'error');
        }

        $shipMethod = $this->resolveMethod('shop_shipping_methods', $request->input('shipping_method_id'));
        $payMethod  = $this->resolveMethod('shop_payment_methods',  $request->input('payment_method_id'));
        $shippingCost = (float) $request->input('shipping_cost', 0);
        $discount     = (float) $request->input('discount', 0);
        $tax          = (float) $request->input('tax', 0);
        $total        = max(0.0, $subtotal + $shippingCost + $tax - $discount);

        $status = Filter::in($request->input('status'), self::STATUSES, 'new');
        $payStatus = Filter::in($request->input('payment_status'), self::PAYMENT_STATUSES, 'pending');

        $shop = new ShopSettings($this->options);
        $currency = $shop->currencyCode() ?: 'USD';
        $prefix = $shop->get('shop.order_number_prefix', 'ORD-');
        $number = $prefix . strtoupper(bin2hex(random_bytes(4)));
        $now = date('Y-m-d H:i:s');

        $repo = new CustomerRepo($this->conn);
        $customerId = $repo->upsertFromOrder([
            'email'   => $email,
            'name'    => $name,
            'phone'   => (string) $request->input('customer_phone', ''),
            'address' => (string) $request->input('customer_address', ''),
        ]);

        $orderId = (int) $this->conn->transaction(function () use (
            $number, $name, $email, $request, $items, $subtotal, $discount, $tax,
            $shippingCost, $total, $currency, $status, $payStatus, $now,
            $customerId, $shipMethod, $payMethod
        ) {
            $id = (int) $this->conn->builder('shop_orders')->insert([
                'number'           => $number,
                'user_id'          => null,
                'customer_id'      => $customerId ?: null,
                'customer_name'    => $name,
                'customer_email'   => $email,
                'customer_phone'   => Filter::string($request->input('customer_phone'), 32),
                'customer_address' => Filter::string($request->input('customer_address'), 1000),
                'comment'          => Filter::string($request->input('comment'), 1000),
                'admin_note'       => Filter::string($request->input('admin_note'), 5000),
                'subtotal'         => $subtotal,
                'discount'         => $discount,
                'tax'              => $tax,
                'shipping_cost'    => $shippingCost,
                'total'            => $total,
                'currency'         => $currency,
                'status'           => $status,
                'payment_status'   => $payStatus,
                'shipping_method_id'    => $shipMethod['id'],
                'shipping_method_title' => $shipMethod['title'],
                'payment_method_id'     => $payMethod['id'],
                'payment_method_title'  => $payMethod['title'],
                'source'           => 'admin',
                'paid_at'          => $payStatus === 'paid' ? $now : null,
                'created_at'       => $now,
                'updated_at'       => $now,
            ]);
            foreach ($items as $it) {
                $this->conn->builder('shop_order_items')->insert([
                    'order_id'      => $id,
                    'product_id'    => $it['product_id'] ?: null,
                    'product_title' => $it['title'],
                    'product_sku'   => $it['sku'],
                    'price'         => $it['price'],
                    'quantity'      => $it['quantity'],
                    'subtotal'      => $it['subtotal'],
                ]);
            }
            return $id;
        });

        $this->log($orderId, 'created', null, null, 'Создан вручную из админки');

        if ($customerId > 0) {
            $repo->recalcAggregates($customerId);
        }

        Hook::action('shop.order.created', [
            'id' => $orderId, 'number' => $number, 'total' => $total,
            'email' => $email, 'name' => $name, 'items_count' => count($items),
            'source' => 'admin',
        ]);

        return $this->redirect('/admin/shop/orders/' . $orderId, $this->tr()->translate('shop.flash.order_created_manual', ['number' => $number]));
    }

    /** AJAX: поиск товаров для подбора в ручном создании заказа. */
    public function searchProducts(Request $request): Response
    {
        if (!$this->auth->check()) return Response::json(['items' => []], 401);
        $q = trim((string) $request->query('q', ''));
        $items = [];
        if ($q !== '') {
            try {
                $like = '%' . mb_strtolower($q, 'UTF-8') . '%';
                $rows = $this->conn->builder('shop_products')
                    ->whereRaw(
                        '(LOWER(' . $this->conn->quoteId('title') . ') LIKE ? OR '
                        . 'LOWER(' . $this->conn->quoteId('sku') . ') LIKE ? OR '
                        . $this->conn->quoteId('id') . ' = ?)',
                        [$like, $like, ctype_digit($q) ? (int) $q : 0]
                    )
                    ->where('status', '=', 'active')
                    ->orderBy('title', 'asc')
                    ->limit(15)->get();
                foreach ($rows as $p) {
                    $items[] = [
                        'id'    => (int) $p['id'],
                        'title' => (string) $p['title'],
                        'sku'   => (string) ($p['sku'] ?? ''),
                        'price' => (float) $p['price'],
                        'stock' => (int) ($p['stock'] ?? 0),
                        'cover' => (string) ($p['cover'] ?? ''),
                    ];
                }
            } catch (\Throwable) {}
        }
        return Response::json(['items' => $items]);
    }

    /** AJAX: поиск клиента по email/имени/телефону. */
    public function searchCustomers(Request $request): Response
    {
        if (!$this->auth->check()) return Response::json(['items' => []], 401);
        $q = trim((string) $request->query('q', ''));
        $items = [];
        if ($q !== '') {
            try {
                $like = '%' . mb_strtolower($q, 'UTF-8') . '%';
                $rows = $this->conn->builder('shop_customers')
                    ->whereRaw(
                        '(LOWER(' . $this->conn->quoteId('name') . ') LIKE ? OR '
                        . 'LOWER(' . $this->conn->quoteId('email') . ') LIKE ? OR '
                        . 'LOWER(COALESCE(' . $this->conn->quoteId('phone') . ", '')) LIKE ?)",
                        [$like, $like, $like]
                    )
                    ->orderBy('last_order_at', 'desc')
                    ->limit(15)->get();
                foreach ($rows as $c) {
                    $items[] = [
                        'id'      => (int) $c['id'],
                        'name'    => (string) $c['name'],
                        'email'   => (string) $c['email'],
                        'phone'   => (string) ($c['phone'] ?? ''),
                        'address' => (string) ($c['address'] ?? ''),
                        'orders_count' => (int) ($c['orders_count'] ?? 0),
                    ];
                }
            } catch (\Throwable) {}
        }
        return Response::json(['items' => $items]);
    }

    /** Резолвит метод доставки/оплаты по id, возвращает ['id' => ?int, 'title' => ?string]. */
    private function resolveMethod(string $table, mixed $idRaw): array
    {
        $id = (int) $idRaw;
        if ($id <= 0) return ['id' => null, 'title' => null];
        try {
            $row = $this->conn->builder($table)->where('id', '=', $id)->first();
            if ($row !== null) return ['id' => $id, 'title' => (string) $row['title']];
        } catch (\Throwable) {}
        return ['id' => null, 'title' => null];
    }

    /** Записать событие в shop_order_history. */
    private function log(int $orderId, string $event, ?string $from, ?string $to, string $comment): void
    {
        try {
            $this->conn->builder('shop_order_history')->insert([
                'order_id' => $orderId,
                'user_id' => $this->auth->id(),
                'event' => $event,
                'from_value' => $from,
                'to_value' => $to,
                'comment' => $comment !== '' ? $comment : null,
                'created_at' => date('Y-m-d H:i:s'),
            ]);
        } catch (\Throwable) {}
    }
}
