<?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
    {
        $rows = $this->db->select('pp_shop_statuses', ['id', 'status'], [
            'ORDER' => ['o' => 'ASC'],
        ]);

        if (!is_array($rows)) {
            return [];
        }

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

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

        return $result;
    }

    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]);

        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,
        ]);

        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;
    }

    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;
    }
}