<?php
namespace Domain\Order;

class OrderRepository
{
    private const MAX_PER_PAGE = 100;

    private $db;

    public function __construct($db)
    {
        $this->db = $db;
    }

    /**
     * @return array{items: array<int, array<string, mixed>>, total: int}
     */
    public function listForAdmin(
        array $filters,
        string $sortColumn = 'date_order',
        string $sortDir = 'DESC',
        int $page = 1,
        int $perPage = 15
    ): array {
        $allowedSortColumns = [
            'id' => 'q1.id',
            'number' => 'q1.number',
            'date_order' => 'q1.date_order',
            'status' => 'q1.status',
            'summary' => 'q1.summary',
            'client' => 'q1.client',
            'order_email' => 'q1.order_email',
            'client_phone' => 'q1.client_phone',
            'transport' => 'q1.transport',
            'payment_method' => 'q1.payment_method',
            'total_orders' => 'shop_order.total_orders',
            'paid' => 'q1.paid',
        ];

        $sortSql = $allowedSortColumns[$sortColumn] ?? 'q1.date_order';
        $sortDir = strtoupper(trim($sortDir)) === 'ASC' ? 'ASC' : 'DESC';
        $page = max(1, $page);
        $perPage = min(self::MAX_PER_PAGE, max(1, $perPage));
        $offset = ($page - 1) * $perPage;

        $where = [];
        $params = [];

        $number = $this->normalizeTextFilter($filters['number'] ?? '');
        if ($number !== '') {
            $where[] = 'q1.number LIKE :number';
            $params[':number'] = '%' . $number . '%';
        }

        $dateFrom = $this->normalizeDateFilter($filters['date_from'] ?? '');
        if ($dateFrom !== null) {
            $where[] = 'q1.date_order >= :date_from';
            $params[':date_from'] = $dateFrom . ' 00:00:00';
        }

        $dateTo = $this->normalizeDateFilter($filters['date_to'] ?? '');
        if ($dateTo !== null) {
            $where[] = 'q1.date_order <= :date_to';
            $params[':date_to'] = $dateTo . ' 23:59:59';
        }

        $status = trim((string)($filters['status'] ?? ''));
        if ($status !== '' && is_numeric($status)) {
            $where[] = 'q1.status = :status';
            $params[':status'] = (int)$status;
        }

        $client = $this->normalizeTextFilter($filters['client'] ?? '');
        if ($client !== '') {
            $where[] = 'q1.client LIKE :client';
            $params[':client'] = '%' . $client . '%';
        }

        $address = $this->normalizeTextFilter($filters['address'] ?? '');
        if ($address !== '') {
            $where[] = 'q1.address LIKE :address';
            $params[':address'] = '%' . $address . '%';
        }

        $email = $this->normalizeTextFilter($filters['order_email'] ?? '');
        if ($email !== '') {
            $where[] = 'q1.order_email LIKE :order_email';
            $params[':order_email'] = '%' . $email . '%';
        }

        $phone = $this->normalizeTextFilter($filters['client_phone'] ?? '');
        if ($phone !== '') {
            $where[] = 'q1.client_phone LIKE :client_phone';
            $params[':client_phone'] = '%' . $phone . '%';
        }

        $transport = $this->normalizeTextFilter($filters['transport'] ?? '');
        if ($transport !== '') {
            $where[] = 'q1.transport LIKE :transport';
            $params[':transport'] = '%' . $transport . '%';
        }

        $payment = $this->normalizeTextFilter($filters['payment_method'] ?? '');
        if ($payment !== '') {
            $where[] = 'q1.payment_method LIKE :payment_method';
            $params[':payment_method'] = '%' . $payment . '%';
        }

        $whereSql = '';
        if (!empty($where)) {
            $whereSql = ' WHERE ' . implode(' AND ', $where);
        }

        $baseSql = "
            FROM (
                SELECT
                    id,
                    number,
                    date_order,
                    CONCAT(client_name, ' ', client_surname) AS client,
                    client_email AS order_email,
                    CONCAT(client_street, ', ', client_postal_code, ' ', client_city) AS address,
                    status,
                    client_phone,
                    transport,
                    payment_method,
                    summary,
                    paid
                FROM pp_shop_orders AS pso
            ) AS q1
            LEFT JOIN (
                SELECT
                    client_email,
                    COUNT(*) AS total_orders
                FROM pp_shop_orders
                WHERE client_name IS NOT NULL AND client_surname IS NOT NULL AND client_email IS NOT NULL
                GROUP BY client_email
            ) AS shop_order ON q1.order_email = shop_order.client_email
        ";

        $sqlCount = 'SELECT COUNT(0) ' . $baseSql . $whereSql;
        $stmtCount = $this->db->query($sqlCount, $params);
        $countRows = $stmtCount ? $stmtCount->fetchAll() : [];
        $total = 0;
        if (is_array($countRows) && isset($countRows[0]) && is_array($countRows[0])) {
            $firstRow = $countRows[0];
            $firstValue = reset($firstRow);
            $total = $firstValue !== false ? (int)$firstValue : 0;
        }

        $sql = '
            SELECT
                q1.*,
                COALESCE(shop_order.total_orders, 0) AS total_orders
            '
            . $baseSql
            . $whereSql
            . ' ORDER BY ' . $sortSql . ' ' . $sortDir . ', q1.id DESC'
            . ' LIMIT ' . $perPage . ' OFFSET ' . $offset;

        $stmt = $this->db->query($sql, $params);
        if (!$stmt) {
            return [
                'items' => [],
                'total' => $total,
            ];
        }

        $items = $stmt ? $stmt->fetchAll() : [];
        if (!is_array($items)) {
            $items = [];
        }

        foreach ($items as &$item) {
            $item['id'] = (int)($item['id'] ?? 0);
            $item['status'] = (int)($item['status'] ?? 0);
            $item['paid'] = (int)($item['paid'] ?? 0);
            $item['summary'] = (float)($item['summary'] ?? 0);
            $item['total_orders'] = (int)($item['total_orders'] ?? 0);
            $item['number'] = (string)($item['number'] ?? '');
            $item['date_order'] = (string)($item['date_order'] ?? '');
            $item['client'] = trim((string)($item['client'] ?? ''));
            $item['order_email'] = (string)($item['order_email'] ?? '');
            $item['address'] = trim((string)($item['address'] ?? ''));
            $item['client_phone'] = (string)($item['client_phone'] ?? '');
            $item['transport'] = (string)($item['transport'] ?? '');
            $item['payment_method'] = (string)($item['payment_method'] ?? '');
        }
        unset($item);

        return [
            'items' => $items,
            'total' => $total,
        ];
    }

    public function findForAdmin(int $orderId): array
    {
        if ($orderId <= 0) {
            return [];
        }

        $order = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]);
        if (!is_array($order)) {
            return [];
        }

        $order['id'] = (int)($order['id'] ?? 0);
        $order['status'] = (int)($order['status'] ?? 0);
        $order['paid'] = (int)($order['paid'] ?? 0);
        $order['summary'] = (float)($order['summary'] ?? 0);
        $order['transport_cost'] = (float)($order['transport_cost'] ?? 0);
        $order['products'] = $this->orderProducts($orderId);
        $order['statuses'] = $this->orderStatusHistory($orderId);

        return $order;
    }

    public function orderProducts(int $orderId): array
    {
        if ($orderId <= 0) {
            return [];
        }

        $rows = $this->db->select('pp_shop_order_products', '*', [
            'order_id' => $orderId,
        ]);

        return is_array($rows) ? $rows : [];
    }

    public function orderStatusHistory(int $orderId): array
    {
        if ($orderId <= 0) {
            return [];
        }

        $rows = $this->db->select('pp_shop_order_statuses', '*', [
            'order_id' => $orderId,
            'ORDER' => ['id' => 'DESC'],
        ]);

        return is_array($rows) ? $rows : [];
    }

    public function orderStatuses(): array
    {
        $data = $this->orderStatusData();
        return $data['names'];
    }

    /**
     * Zwraca nazwy i kolory statusów w jednym zapytaniu.
     *
     * @return array{names: array<int, string>, colors: array<int, string>}
     */
    public function orderStatusData(): array
    {
        $rows = $this->db->select('pp_shop_statuses', ['id', 'status', 'color'], [
            'ORDER' => ['o' => 'ASC'],
        ]);

        $names = [];
        $colors = [];

        if (!is_array($rows)) {
            return ['names' => $names, 'colors' => $colors];
        }

        foreach ($rows as $row) {
            $id = (int)($row['id'] ?? 0);
            if ($id < 0) {
                continue;
            }

            $names[$id] = (string)($row['status'] ?? '');

            $color = trim((string)($row['color'] ?? ''));
            if ($color !== '' && preg_match('/^#[0-9a-fA-F]{3,6}$/', $color)) {
                $colors[$id] = $color;
            }
        }

        return ['names' => $names, 'colors' => $colors];
    }

    public function nextOrderId(int $orderId): ?int
    {
        if ($orderId <= 0) {
            return null;
        }

        $next = $this->db->get('pp_shop_orders', 'id', [
            'id[>]' => $orderId,
            'ORDER' => ['id' => 'ASC'],
            'LIMIT' => 1,
        ]);

        if (!$next) {
            return null;
        }

        return (int)$next;
    }

    public function prevOrderId(int $orderId): ?int
    {
        if ($orderId <= 0) {
            return null;
        }

        $prev = $this->db->get('pp_shop_orders', 'id', [
            'id[<]' => $orderId,
            'ORDER' => ['id' => 'DESC'],
            'LIMIT' => 1,
        ]);

        if (!$prev) {
            return null;
        }

        return (int)$prev;
    }

    public function saveNotes(int $orderId, string $notes): bool
    {
        if ($orderId <= 0) {
            return false;
        }

        $this->db->update('pp_shop_orders', ['notes' => $notes], ['id' => $orderId]);
        $this->touchUpdatedAt($orderId);

        return true;
    }

    public function saveOrderByAdmin(
        int $orderId,
        string $clientName,
        string $clientSurname,
        string $clientStreet,
        string $clientPostalCode,
        string $clientCity,
        string $clientEmail,
        string $firmName,
        string $firmStreet,
        string $firmPostalCode,
        string $firmCity,
        string $firmNip,
        int $transportId,
        string $inpostPaczkomat,
        int $paymentMethodId
    ): bool {
        if ($orderId <= 0) {
            return false;
        }

        $transportName = $this->db->get('pp_shop_transports', 'name_visible', ['id' => $transportId]);
        $transportCost = $this->db->get('pp_shop_transports', 'cost', ['id' => $transportId]);
        $transportDescription = $this->db->get('pp_shop_transports', 'description', ['id' => $transportId]);
        $paymentMethodName = $this->db->get('pp_shop_payment_methods', 'name', ['id' => $paymentMethodId]);

        $this->db->update('pp_shop_orders', [
            'client_name' => $clientName,
            'client_surname' => $clientSurname,
            'client_street' => $clientStreet,
            'client_postal_code' => $clientPostalCode,
            'client_city' => $clientCity,
            'client_email' => $clientEmail,
            'firm_name' => $this->nullableString($firmName),
            'firm_street' => $this->nullableString($firmStreet),
            'firm_postal_code' => $this->nullableString($firmPostalCode),
            'firm_city' => $this->nullableString($firmCity),
            'firm_nip' => $this->nullableString($firmNip),
            'transport_id' => $transportId,
            'transport' => $transportName ?: null,
            'transport_cost' => $transportCost !== null ? $transportCost : 0,
            'transport_description' => $transportDescription ?: null,
            'inpost_paczkomat' => $inpostPaczkomat,
            'payment_method_id' => $paymentMethodId,
            'payment_method' => $paymentMethodName ?: null,
        ], [
            'id' => $orderId,
        ]);

        $this->db->update('pp_shop_orders', [
            'summary' => $this->calculateOrderSummaryByAdmin($orderId),
        ], [
            'id' => $orderId,
        ]);

        $this->touchUpdatedAt($orderId);

        return true;
    }

    public function calculateOrderSummaryByAdmin(int $orderId): float
    {
        if ($orderId <= 0) {
            return 0.0;
        }

        $rows = $this->db->select('pp_shop_order_products', [
            'price_brutto',
            'price_brutto_promo',
            'quantity',
        ], [
            'order_id' => $orderId,
        ]);

        $summary = 0.0;
        if (is_array($rows)) {
            foreach ($rows as $row) {
                $quantity = (float)($row['quantity'] ?? 0);
                $pricePromo = (float)($row['price_brutto_promo'] ?? 0);
                $price = (float)($row['price_brutto'] ?? 0);

                if ($pricePromo > 0) {
                    $summary += $pricePromo * $quantity;
                } else {
                    $summary += $price * $quantity;
                }
            }
        }

        $transportCost = (float)$this->db->get('pp_shop_orders', 'transport_cost', ['id' => $orderId]);

        return (float)$summary + $transportCost;
    }

    public function toggleTrustmateSend(int $orderId): ?int
    {
        if ($orderId <= 0) {
            return null;
        }

        $order = $this->db->get('pp_shop_orders', ['trustmate_send'], ['id' => $orderId]);
        if (!is_array($order)) {
            return null;
        }

        $newValue = ((int)($order['trustmate_send'] ?? 0) === 1) ? 0 : 1;
        $this->db->update('pp_shop_orders', ['trustmate_send' => $newValue], ['id' => $orderId]);

        return $newValue;
    }

    public function deleteOrder(int $orderId): bool
    {
        if ($orderId <= 0) {
            return false;
        }

        $this->db->delete('pp_shop_orders', ['id' => $orderId]);

        return true;
    }

    // --- Order product CRUD (admin) ---

    public function getOrderProduct(int $orderProductId): ?array
    {
        if ($orderProductId <= 0) {
            return null;
        }

        $row = $this->db->get('pp_shop_order_products', '*', ['id' => $orderProductId]);

        return is_array($row) ? $row : null;
    }

    public function addOrderProduct(int $orderId, array $data): ?int
    {
        if ($orderId <= 0) {
            return null;
        }

        $this->db->insert('pp_shop_order_products', [
            'order_id' => $orderId,
            'product_id' => (int)($data['product_id'] ?? 0),
            'parent_product_id' => (int)($data['parent_product_id'] ?? 0),
            'name' => (string)($data['name'] ?? ''),
            'attributes' => (string)($data['attributes'] ?? ''),
            'vat' => (float)($data['vat'] ?? 0),
            'price_brutto' => (float)($data['price_brutto'] ?? 0),
            'price_brutto_promo' => (float)($data['price_brutto_promo'] ?? 0),
            'quantity' => max(1, (int)($data['quantity'] ?? 1)),
            'message' => (string)($data['message'] ?? ''),
            'custom_fields' => (string)($data['custom_fields'] ?? ''),
        ]);

        $id = $this->db->id();

        return $id ? (int)$id : null;
    }

    public function updateOrderProduct(int $orderProductId, array $data): bool
    {
        if ($orderProductId <= 0) {
            return false;
        }

        $update = [];

        if (array_key_exists('quantity', $data)) {
            $update['quantity'] = max(1, (int)$data['quantity']);
        }
        if (array_key_exists('price_brutto', $data)) {
            $update['price_brutto'] = (float)$data['price_brutto'];
        }
        if (array_key_exists('price_brutto_promo', $data)) {
            $update['price_brutto_promo'] = (float)$data['price_brutto_promo'];
        }

        if (empty($update)) {
            return false;
        }

        $this->db->update('pp_shop_order_products', $update, ['id' => $orderProductId]);

        return true;
    }

    public function deleteOrderProduct(int $orderProductId): bool
    {
        if ($orderProductId <= 0) {
            return false;
        }

        $this->db->delete('pp_shop_order_products', ['id' => $orderProductId]);

        return true;
    }

    public function updateTransportCost(int $orderId, float $cost): void
    {
        if ($orderId <= 0) {
            return;
        }

        $this->db->update('pp_shop_orders', ['transport_cost' => $cost], ['id' => $orderId]);
    }

    // --- Frontend methods ---

    public function findIdByHash(string $hash)
    {
        $hash = trim($hash);
        if ($hash === '') {
            return null;
        }

        $id = $this->db->get('pp_shop_orders', 'id', ['hash' => $hash]);

        return $id ? (int)$id : null;
    }

    public function findHashById(int $orderId)
    {
        if ($orderId <= 0) {
            return null;
        }

        $hash = $this->db->get('pp_shop_orders', 'hash', ['id' => $orderId]);

        return $hash ? (string)$hash : null;
    }

    public function orderDetailsFrontend($orderId = null, $hash = '', $przelewy24Hash = '')
    {
        $order = null;

        if ($orderId) {
            $order = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]);
            if (is_array($order)) {
                $order['products'] = $this->db->select('pp_shop_order_products', '*', ['order_id' => $orderId]);
                if (!is_array($order['products'])) {
                    $order['products'] = [];
                }
            }
        }

        if ($hash) {
            $order = $this->db->get('pp_shop_orders', '*', ['hash' => $hash]);
            if (is_array($order)) {
                $order['products'] = $this->db->select('pp_shop_order_products', '*', ['order_id' => $order['id']]);
                if (!is_array($order['products'])) {
                    $order['products'] = [];
                }
            }
        }

        if ($przelewy24Hash) {
            $order = $this->db->get('pp_shop_orders', '*', ['przelewy24_hash' => $przelewy24Hash]);
            if (is_array($order)) {
                $order['products'] = $this->db->select('pp_shop_order_products', '*', ['order_id' => $order['id']]);
                if (!is_array($order['products'])) {
                    $order['products'] = [];
                }
            }
        }

        return is_array($order) ? $order : null;
    }

    public function generateOrderNumber()
    {
        $date = date('Y-m');

        $stmt = $this->db->query(
            'SELECT MAX( CONVERT( substring_index( substring_index( number, \'/\', -1 ), \' \', -1 ), UNSIGNED INTEGER) ) FROM pp_shop_orders WHERE date_order LIKE \'' . $date . '%\''
        );
        $results = $stmt ? $stmt->fetchAll() : [];

        $nr = 0;
        if (is_array($results) && count($results)) {
            foreach ($results as $row) {
                $nr = ++$row[0];
            }
        }

        if (!$nr) {
            $nr = 1;
        }

        if ($nr < 10) {
            $nr = '00' . $nr;
        } elseif ($nr < 100) {
            $nr = '0' . $nr;
        }

        return date('Y/m', strtotime($date)) . '/' . $nr;
    }

    public function createFromBasket(
        $client_id,
        $basket,
        $transport_id,
        $payment_id,
        $email,
        $phone,
        $name,
        $surname,
        $street,
        $postal_code,
        $city,
        $firm_name,
        $firm_street,
        $firm_postal_code,
        $firm_city,
        $firm_nip,
        $inpost_info,
        $orlen_point_id,
        $orlen_point_info,
        $coupon,
        $basket_message
    ) {
        global $lang_id, $settings;

        if ($client_id) {
            $email = (new \Domain\Client\ClientRepository($this->db))->clientEmail((int)$client_id);
        }

        if (!is_array($basket) || !$transport_id || !$payment_id || !$email || !$phone || !$name || !$surname) {
            return false;
        }

        $transport = ( new \Domain\Transport\TransportRepository( $this->db ) )->findActiveByIdCached( $transport_id );
        $payment_method = ( new \Domain\PaymentMethod\PaymentMethodRepository( $this->db ) )->findActiveById( (int)$payment_id );
        $productRepo = new \Domain\Product\ProductRepository($this->db);
        $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice($basket, $coupon, $lang_id, $productRepo);
        $order_number = $this->generateOrderNumber();
        $order_date = date('Y-m-d H:i:s');
        $hash = md5($order_number . time());

        if ($transport['delivery_free'] == 1 && $basket_summary >= $settings['free_delivery']) {
            $transport_cost = '0.00';
        } else {
            $transport_cost = $transport['cost'];
        }

        $this->db->insert('pp_shop_orders', [
            'number' => $order_number,
            'client_id' => $client_id ? $client_id : null,
            'date_order' => $order_date,
            'comment' => null,
            'client_name' => $name,
            'client_surname' => $surname,
            'client_email' => $email,
            'client_street' => $street,
            'client_postal_code' => $postal_code,
            'client_city' => $city,
            'client_phone' => $phone,
            'firm_name' => $firm_name ? $firm_name : null,
            'firm_street' => $firm_street ? $firm_street : null,
            'firm_postal_code' => $firm_postal_code ? $firm_postal_code : null,
            'firm_city' => $firm_city ? $firm_city : null,
            'firm_nip' => $firm_nip ? $firm_nip : null,
            'transport_id' => $transport_id,
            'transport' => $transport['name_visible'],
            'transport_cost' => $transport_cost,
            'transport_description' => $transport['description'],
            'orlen_point' => ($orlen_point_id) ? $orlen_point_id . ' | ' . $orlen_point_info : null,
            'inpost_paczkomat' => ($transport_id == 1 || $transport_id == 2) ? $inpost_info : null,
            'payment_method' => $payment_method['name'],
            'payment_method_id' => $payment_id,
            'hash' => $hash,
            'summary' => \Shared\Helpers\Helpers::normalize_decimal($basket_summary + $transport_cost),
            'coupon_id' => $coupon ? $coupon->id : null,
            'message' => $basket_message ? $basket_message : null,
            'apilo_order_status_date' => date('Y-m-d H:i:s'),
            'updated_at' => $order_date,
        ]);

        $order_id = $this->db->id();

        if (!$order_id) {
            return false;
        }

        if ($coupon) {
            (new \Domain\Coupon\CouponRepository($this->db))->incrementUsedCount((int)$coupon->id);
        }

        // ustawienie statusu zamówienia
        $this->db->insert('pp_shop_order_statuses', ['order_id' => $order_id, 'status_id' => 0, 'mail' => 1]);

        if (is_array($basket)) {
            $attributeRepo = new \Domain\Attribute\AttributeRepository($this->db);
            foreach ($basket as $basket_position) {
                $attributes = '';
                $product = $productRepo->findCached($basket_position['product-id'], $lang_id);

                if (is_array($basket_position['attributes'])) {
                    foreach ($basket_position['attributes'] as $row) {
                        $row = explode('-', $row);
                        $attribute = $attributeRepo->frontAttributeDetails((int)$row[0], $lang_id);
                        $value = $attributeRepo->frontValueDetails((int)$row[1], $lang_id);

                        if ($attributes) {
                            $attributes .= '<br>';
                        }
                        $attributes .= '<b>' . $attribute['language']['name'] . '</b>: ';
                        $attributes .= $value['language']['name'];
                    }
                }

                // custom fields
                $product_custom_fields = '';
                if (is_array($basket_position['custom_fields'])) {
                    foreach ($basket_position['custom_fields'] as $key => $val) {
                        $custom_field = $productRepo->findCustomFieldCached($key);
                        if ($product_custom_fields) {
                            $product_custom_fields .= '<br>';
                        }
                        $product_custom_fields .= '<b>' . $custom_field['name'] . '</b>: ' . $val;
                    }
                }

                $product_price_tmp = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position, $productRepo);

                // Cena promo = 0 gdy taka sama jak cena bazowa (brak realnej promocji/kuponu).
                // Porównujemy po zaokrągleniu do 2 miejsc, żeby uniknąć artefaktów float (IEEE 754).
                $effectivePromoPrice = round((float)$product_price_tmp['price_new'], 2);
                $effectiveBasePrice  = round((float)$product_price_tmp['price'], 2);
                $promoPrice = ($effectivePromoPrice > 0 && $effectivePromoPrice < $effectiveBasePrice) ? $effectivePromoPrice : 0;

                $this->db->insert('pp_shop_order_products', [
                    'order_id' => $order_id,
                    'product_id' => $basket_position['product-id'],
                    'parent_product_id' => $basket_position['parent_id'] ? $basket_position['parent_id'] : $basket_position['product-id'],
                    'name' => $product['language']['name'],
                    'attributes' => $attributes,
                    'vat' => $product['vat'],
                    'price_brutto' => $effectiveBasePrice,
                    'price_brutto_promo' => $promoPrice,
                    'quantity' => $basket_position['quantity'],
                    'message' => $basket_position['message'],
                    'custom_fields' => $product_custom_fields,
                ]);

                $product_quantity = $productRepo->getQuantity($basket_position['product-id']);
                if ($product_quantity != null) {
                    $this->db->update('pp_shop_products', ['quantity[-]' => $basket_position['quantity']], ['id' => $basket_position['product-id']]);
                } else {
                    $this->db->update('pp_shop_products', ['quantity[-]' => $basket_position['quantity']], ['id' => $basket_position['parent_id']]);
                }

                $this->db->update('pp_shop_products', ['quantity' => 0], ['quantity[<]' => 0]);
            }
        }

        if ($coupon && $coupon->is_one_time()) {
            $coupon->set_as_used();
        }

        $order = $this->orderDetailsFrontend($order_id);

        $mail_order = \Shared\Tpl\Tpl::view('shop-order/mail-summary', [
            'settings' => $settings,
            'order' => $order,
            'coupon' => $coupon,
        ]);

        $settings['ssl'] ? $base = 'https' : $base = 'http';

        $regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
        $mail_order = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $mail_order);

        $regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
        $mail_order = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $mail_order);

        \Shared\Helpers\Helpers::send_email($email, \Shared\Helpers\Helpers::lang('potwierdzenie-zamowienia-ze-sklepu') . ' ' . $settings['firm_name'], $mail_order);
        \Shared\Helpers\Helpers::send_email($settings['contact_email'], 'Nowe zamówienie / ' . $settings['firm_name'] . ' / ' . $order['number'] . ' - ' . $order['client_surname'] . ' ' . $order['client_name'], $mail_order);

        // zmiana statusu w realizacji jeżeli płatność przy odbiorze
        if ($payment_id == 3) {
            $this->updateOrderStatus($order_id, 4);
            $this->insertStatusHistory($order_id, 4, 1);
        }

        return $order_id;
    }

    // =========================================================================
    // Low-level helpers (used by OrderAdminService)
    // =========================================================================

    public function getDb()
    {
        return $this->db;
    }

    public function findRawById(int $orderId): ?array
    {
        if ($orderId <= 0) return null;
        $result = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]);
        return is_array($result) ? $result : null;
    }

    public function findRawByHash(string $hash): ?array
    {
        if ($hash === '') return null;
        $result = $this->db->get('pp_shop_orders', '*', ['hash' => $hash]);
        return is_array($result) ? $result : null;
    }

    public function findRawByPrzelewy24Hash(string $hash): ?array
    {
        if ($hash === '') return null;
        $result = $this->db->get('pp_shop_orders', '*', ['przelewy24_hash' => $hash]);
        return is_array($result) ? $result : null;
    }

    public function setAsPaid(int $orderId): void
    {
        $this->db->update('pp_shop_orders', ['paid' => 1], ['id' => $orderId]);
        $this->touchUpdatedAt($orderId);
    }

    public function setAsUnpaid(int $orderId): void
    {
        $this->db->update('pp_shop_orders', ['paid' => 0], ['id' => $orderId]);
        $this->touchUpdatedAt($orderId);
    }

    public function updateOrderStatus(int $orderId, int $status): bool
    {
        $result = (bool)$this->db->update('pp_shop_orders', ['status' => $status], ['id' => $orderId]);
        if ($result) {
            $this->touchUpdatedAt($orderId);
        }
        return $result;
    }

    public function insertStatusHistory(int $orderId, int $statusId, int $mail): void
    {
        $this->db->insert('pp_shop_order_statuses', [
            'order_id' => $orderId,
            'status_id' => $statusId,
            'mail' => $mail,
        ]);
    }

    public function updateApiloStatusDate(int $orderId, string $date): void
    {
        $this->db->update('pp_shop_orders', ['apilo_order_status_date' => $date], ['id' => $orderId]);
    }

    // =========================================================================
    // API methods (for ordersPRO)
    // =========================================================================

    public function listForApi(array $filters, int $page = 1, int $perPage = 50): array
    {
        $page = max(1, $page);
        $perPage = min(self::MAX_PER_PAGE, max(1, $perPage));
        $offset = ($page - 1) * $perPage;

        $where = [];
        $params = [];

        $status = trim((string)($filters['status'] ?? ''));
        if ($status !== '' && is_numeric($status)) {
            $where[] = 'o.status = :status';
            $params[':status'] = (int)$status;
        }

        $paid = trim((string)($filters['paid'] ?? ''));
        if ($paid !== '' && is_numeric($paid)) {
            $where[] = 'o.paid = :paid';
            $params[':paid'] = (int)$paid;
        }

        $dateFrom = $this->normalizeDateFilter($filters['date_from'] ?? '');
        if ($dateFrom !== null) {
            $where[] = 'o.date_order >= :date_from';
            $params[':date_from'] = $dateFrom . ' 00:00:00';
        }

        $dateTo = $this->normalizeDateFilter($filters['date_to'] ?? '');
        if ($dateTo !== null) {
            $where[] = 'o.date_order <= :date_to';
            $params[':date_to'] = $dateTo . ' 23:59:59';
        }

        $updatedSince = trim((string)($filters['updated_since'] ?? ''));
        if ($updatedSince !== '' && preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}$/', $updatedSince)) {
            $where[] = 'o.updated_at >= :updated_since';
            $params[':updated_since'] = $updatedSince;
        }

        $number = $this->normalizeTextFilter($filters['number'] ?? '');
        if ($number !== '') {
            $where[] = 'o.number LIKE :number';
            $params[':number'] = '%' . $number . '%';
        }

        $client = $this->normalizeTextFilter($filters['client'] ?? '');
        if ($client !== '') {
            $where[] = "(o.client_name LIKE :client OR o.client_surname LIKE :client2 OR o.client_email LIKE :client3)";
            $params[':client'] = '%' . $client . '%';
            $params[':client2'] = '%' . $client . '%';
            $params[':client3'] = '%' . $client . '%';
        }

        $whereSql = '';
        if (!empty($where)) {
            $whereSql = ' WHERE ' . implode(' AND ', $where);
        }

        $sqlCount = 'SELECT COUNT(0) FROM pp_shop_orders AS o' . $whereSql;
        $stmtCount = $this->db->query($sqlCount, $params);
        $countRows = $stmtCount ? $stmtCount->fetchAll() : [];
        $total = 0;
        if (is_array($countRows) && isset($countRows[0]) && is_array($countRows[0])) {
            $firstValue = reset($countRows[0]);
            $total = $firstValue !== false ? (int)$firstValue : 0;
        }

        $sql = 'SELECT o.id, o.number, o.date_order, o.updated_at, o.status, o.paid,'
            . ' o.client_name, o.client_surname, o.client_email, o.client_phone,'
            . ' o.client_street, o.client_postal_code, o.client_city,'
            . ' o.firm_name, o.firm_nip,'
            . ' o.transport, o.transport_cost, o.payment_method, o.summary'
            . ' FROM pp_shop_orders AS o'
            . $whereSql
            . ' ORDER BY o.updated_at DESC, o.id DESC'
            . ' LIMIT ' . $perPage . ' OFFSET ' . $offset;

        $stmt = $this->db->query($sql, $params);
        $items = ($stmt) ? $stmt->fetchAll() : [];
        if (!is_array($items)) {
            $items = [];
        }

        foreach ($items as &$item) {
            $item['id'] = (int)($item['id'] ?? 0);
            $item['status'] = (int)($item['status'] ?? 0);
            $item['paid'] = (int)($item['paid'] ?? 0);
            $item['summary'] = (float)($item['summary'] ?? 0);
            $item['transport_cost'] = (float)($item['transport_cost'] ?? 0);
        }
        unset($item);

        return [
            'items' => $items,
            'total' => $total,
            'page' => $page,
            'per_page' => $perPage,
        ];
    }

    public function findForApi(int $orderId): ?array
    {
        if ($orderId <= 0) {
            return null;
        }

        $order = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]);
        if (!is_array($order)) {
            return null;
        }

        $order['id'] = (int)($order['id'] ?? 0);
        $order['status'] = (int)($order['status'] ?? 0);
        $order['paid'] = (int)($order['paid'] ?? 0);
        $order['summary'] = (float)($order['summary'] ?? 0);
        $order['transport_cost'] = (float)($order['transport_cost'] ?? 0);
        $order['products'] = $this->orderProducts($orderId);
        $order['statuses'] = $this->orderStatusHistory($orderId);

        return $order;
    }

    public function touchUpdatedAt(int $orderId): void
    {
        if ($orderId <= 0) {
            return;
        }

        $this->db->update('pp_shop_orders', [
            'updated_at' => date('Y-m-d H:i:s'),
        ], [
            'id' => $orderId,
        ]);
    }

    private function nullableString(string $value): ?string
    {
        $value = trim($value);

        return $value === '' ? null : $value;
    }

    private function normalizeTextFilter($value): string
    {
        $value = trim((string)$value);
        if ($value === '') {
            return '';
        }

        if (strlen($value) > 255) {
            return substr($value, 0, 255);
        }

        return $value;
    }

    private function normalizeDateFilter($value): ?string
    {
        $value = trim((string)$value);
        if ($value === '') {
            return null;
        }

        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            return null;
        }

        return $value;
    }
}