<?php
declare(strict_types=1);

namespace Celena\Plugin\Shop\Support;

use Celena\Core\Database\Connection;

/**
 * Хранилище клиентов магазина. Главный API:
 *   - upsertFromOrder(): найти/создать клиента по email при оформлении заказа.
 *   - recalcAggregates(): пересчитать orders_count / total_spent / last_order_at.
 *
 * Источник истины для агрегатов — таблица shop_orders. Любая логика, меняющая
 * заказы (ручное создание, смена статуса, удаление), должна вызвать
 * recalcAggregates() для затронутого customer_id.
 *
 * Сравнение по email регистронезависимое (LOWER); хранится в нижнем регистре.
 */
final class CustomerRepo
{
    public function __construct(private readonly Connection $conn)
    {
    }

    /**
     * Создаёт или обновляет клиента на основе данных заказа.
     * Возвращает customer_id. Поля name/phone/address перезаписываются, если непустые
     * (т.е. последний заказ — самые свежие данные).
     */
    public function upsertFromOrder(array $data): int
    {
        $email = $this->normalizeEmail((string) ($data['email'] ?? ''));
        if ($email === '') return 0;

        $now = date('Y-m-d H:i:s');
        $existing = $this->findByEmail($email);

        if ($existing === null) {
            $insert = [
                'email'        => $email,
                'user_id'      => isset($data['user_id']) && (int) $data['user_id'] > 0 ? (int) $data['user_id'] : null,
                'name'         => $this->str($data['name'] ?? '', 191),
                'phone'        => $this->str($data['phone'] ?? '', 32),
                'address'      => $this->str($data['address'] ?? '', 1000),
                'city'         => $this->str($data['city'] ?? '', 100),
                'country'      => $this->str($data['country'] ?? '', 100),
                'zip'          => $this->str($data['zip'] ?? '', 20),
                'note'         => $this->str($data['note'] ?? '', 5000),
                'orders_count' => 0,
                'total_spent'  => 0,
                'is_vip'       => false,
                'marketing_opt_in' => (bool) ($data['marketing_opt_in'] ?? false),
                'created_at'   => $now,
                'updated_at'   => $now,
            ];
            return (int) $this->conn->builder('shop_customers')->insert($insert);
        }

        $update = ['updated_at' => $now];
        // Перезаписываем только если в заказе пришло непустое значение.
        foreach (['name', 'phone', 'address', 'city', 'country', 'zip'] as $key) {
            $val = trim((string) ($data[$key] ?? ''));
            if ($val !== '') $update[$key] = $val;
        }
        if (isset($data['user_id']) && (int) $data['user_id'] > 0 && empty($existing['user_id'])) {
            $update['user_id'] = (int) $data['user_id'];
        }
        $this->conn->builder('shop_customers')->where('id', '=', (int) $existing['id'])->update($update);
        return (int) $existing['id'];
    }

    /** @return array<string,mixed>|null */
    public function findByEmail(string $email): ?array
    {
        $email = $this->normalizeEmail($email);
        if ($email === '') return null;
        $row = $this->conn->builder('shop_customers')->where('email', '=', $email)->first();
        return $row;
    }

    /** Пересчитать агрегаты клиента из shop_orders. Безопасно вызывать после любого изменения. */
    public function recalcAggregates(int $customerId): void
    {
        if ($customerId <= 0) return;
        try {
            $row = $this->conn->run(
                'SELECT COUNT(*) AS c, COALESCE(SUM(' . $this->conn->quoteId('total') . '),0) AS s, '
                . 'MAX(' . $this->conn->quoteId('created_at') . ') AS lo '
                . 'FROM ' . $this->conn->table_q('shop_orders') . ' '
                . 'WHERE ' . $this->conn->quoteId('customer_id') . ' = ? '
                . 'AND ' . $this->conn->quoteId('status') . ' <> ?',
                [$customerId, 'cancelled']
            )->fetch();
        } catch (\Throwable) {
            return;
        }
        $this->conn->builder('shop_customers')->where('id', '=', $customerId)->update([
            'orders_count'  => (int) ($row['c'] ?? 0),
            'total_spent'   => (float) ($row['s'] ?? 0),
            'last_order_at' => $row['lo'] ?? null,
            'updated_at'    => date('Y-m-d H:i:s'),
        ]);
    }

    private function normalizeEmail(string $email): string
    {
        $email = trim(mb_strtolower($email, 'UTF-8'));
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) return '';
        return $email;
    }

    private function str(mixed $value, int $max): string
    {
        $s = trim((string) ($value ?? ''));
        return $s === '' ? '' : mb_substr($s, 0, $max, 'UTF-8');
    }
}
