<?php
declare(strict_types=1);

namespace Celena\Plugin\Shop\Admin;

use Celena\Core\Database\Connection;
use Celena\Core\Template\Engine;
use Celena\Module\Settings\Options;
use Celena\Plugin\Shop\Support\ShopSettings;

/**
 * Виджет «Витрина магазина» для главного дашборда админки.
 * Регистрируется через хук `admin.dashboard.sections` (см. plugin.php).
 *
 * Считает:
 *   - выручка / заказы / средний чек / уникальные клиенты за 30 дней;
 *   - мини-бар по дням (последние 30);
 *   - триггеры внимания (заказы в обработке, нет в наличии, низкий остаток, отзывы на модерации);
 *   - срез каталога (активные товары, категории, бренды);
 *   - топ-5 товаров по числу отзывов;
 *   - 10 последних заказов.
 */
final class DashboardWidget
{
    private readonly ShopSettings $shop;

    public function __construct(
        private readonly Engine $engine,
        private readonly Connection $conn,
        Options $options,
    ) {
        $this->shop = new ShopSettings($options);
    }

    public function render(): string
    {
        $vars = $this->collect();
        try {
            return $this->engine->render('plugins/shop/admin/dashboard_widget', $vars);
        } catch (\Throwable $e) {
            return '<!-- shop dashboard widget failed: ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . ' -->';
        }
    }

    /** @return array<string,mixed> */
    private function collect(): array
    {
        $from30 = date('Y-m-d', strtotime('-29 days'));
        $today  = date('Y-m-d');

        // Выручка / кол-во заказов / уникальные клиенты — за 30 дней.
        $revenue30 = 0.0;
        $orders30  = 0;
        $clients30 = 0;
        $clientsAll = 0;
        $aov = 0.0;

        try {
            $sql = 'SELECT COUNT(*) AS c, COALESCE(SUM(' . $this->conn->quoteId('total') . '),0) AS s, '
                . 'COUNT(DISTINCT ' . $this->conn->quoteId('customer_email') . ') AS u '
                . 'FROM ' . $this->conn->table_q('shop_orders') . ' '
                . 'WHERE ' . $this->conn->quoteId('created_at') . ' >= ? '
                . 'AND ' . $this->conn->quoteId('status') . ' <> ?';
            $row = $this->conn->run($sql, [$from30 . ' 00:00:00', 'cancelled'])->fetch();
            $orders30  = (int) ($row['c'] ?? 0);
            $revenue30 = (float) ($row['s'] ?? 0);
            $clients30 = (int) ($row['u'] ?? 0);
            $aov       = $orders30 > 0 ? $revenue30 / $orders30 : 0.0;
        } catch (\Throwable) {
        }
        try {
            $clientsAll = (int) (
                $this->conn->run(
                    'SELECT COUNT(DISTINCT ' . $this->conn->quoteId('customer_email') . ') AS u '
                    . 'FROM ' . $this->conn->table_q('shop_orders')
                )->fetch()['u'] ?? 0
            );
        } catch (\Throwable) {
        }

        // Сегодня.
        $revenueToday = 0.0;
        $ordersToday  = 0;
        try {
            $sql = 'SELECT COUNT(*) AS c, COALESCE(SUM(' . $this->conn->quoteId('total') . '),0) AS s '
                . 'FROM ' . $this->conn->table_q('shop_orders') . ' '
                . 'WHERE ' . $this->conn->quoteId('created_at') . ' >= ? '
                . 'AND ' . $this->conn->quoteId('status') . ' <> ?';
            $row = $this->conn->run($sql, [$today . ' 00:00:00', 'cancelled'])->fetch();
            $ordersToday  = (int) ($row['c'] ?? 0);
            $revenueToday = (float) ($row['s'] ?? 0);
        } catch (\Throwable) {
        }

        // Бар-чарт по дням (30 точек).
        $byDay = [];
        $maxRev = 0.01;
        try {
            $sql = 'SELECT DATE(' . $this->conn->quoteId('created_at') . ') AS d, '
                . 'COUNT(*) AS c, COALESCE(SUM(' . $this->conn->quoteId('total') . '),0) AS s '
                . 'FROM ' . $this->conn->table_q('shop_orders') . ' '
                . 'WHERE ' . $this->conn->quoteId('created_at') . ' >= ? '
                . 'AND ' . $this->conn->quoteId('status') . ' <> ? '
                . 'GROUP BY DATE(' . $this->conn->quoteId('created_at') . ')';
            $rows = $this->conn->run($sql, [$from30 . ' 00:00:00', 'cancelled'])->fetchAll();
            $idx = [];
            foreach ($rows as $r) $idx[(string) $r['d']] = $r;
            for ($i = 29; $i >= 0; $i--) {
                $d = date('Y-m-d', strtotime("-$i days"));
                $rev = isset($idx[$d]) ? (float) $idx[$d]['s'] : 0.0;
                $cnt = isset($idx[$d]) ? (int) $idx[$d]['c'] : 0;
                if ($rev > $maxRev) $maxRev = $rev;
                $byDay[] = ['day' => $d, 'short' => date('d.m', strtotime($d)), 'count' => $cnt, 'rev' => $rev];
            }
            foreach ($byDay as &$d) {
                $d['pct'] = (int) round(($d['rev'] / $maxRev) * 100);
            }
            unset($d);
        } catch (\Throwable) {
        }

        // Триггеры внимания (наши формулировки — не копипаст).
        $alerts = [
            ['icon' => 'cart',     'label' => 'Заказы в работе',          'value' => $this->safeOrderCount(['new', 'paid']),       'href' => '/admin/shop/orders'],
            ['icon' => 'box',      'label' => 'Закончились на складе',    'value' => $this->safeProductCount('=', 0),              'href' => '/admin/shop/products?stock=out'],
            ['icon' => 'lightning','label' => 'Остаток на исходе (1–5)',  'value' => $this->safeProductCountBetween(1, 5),         'href' => '/admin/shop/products?stock=low'],
            ['icon' => 'comments', 'label' => 'Отзывы ждут проверки',     'value' => $this->safeReviewCount(false),                'href' => '/admin/shop/reviews?tab=pending'],
            ['icon' => 'tag',      'label' => 'Промокодов в обороте',     'value' => $this->safePromoActiveCount(),                'href' => '/admin/shop/promocodes'],
        ];

        // Каталог.
        $catalog = [
            ['label' => 'Активных товаров', 'value' => $this->safeProductCount('=', null, 'active'),
             'href' => '/admin/shop/products?tab=active'],
            ['label' => 'Категорий',        'value' => $this->safeCategoriesCount('shop_product'),
             'href' => '/admin/categories?entity=shop_product'],
            ['label' => 'Брендов',          'value' => $this->safeCount('shop_manufacturers'),
             'href' => '/admin/shop/manufacturers'],
        ];

        // Хиты по числу отзывов (или по rating_count). Форматирование цены — ниже,
        // через ShopSettings::formatPrice вместе с остальными суммами.
        $hits = [];
        try {
            $hits = $this->conn->builder('shop_products')
                ->orderBy('rating_count', 'desc')
                ->orderBy('rating_avg', 'desc')
                ->orderBy('id', 'desc')
                ->limit(5)->get();
        } catch (\Throwable) {
        }

        // Форматирование сумм согласно настройкам магазина.
        $fmt = fn (float $v): string => $this->shop->formatPrice($v);

        // Последние заказы.
        $recent = [];
        try {
            $recent = $this->conn->builder('shop_orders')
                ->orderBy('created_at', 'desc')
                ->limit(8)->get();
            foreach ($recent as &$r) {
                $r['total_fmt'] = $fmt((float) $r['total']);
            }
            unset($r);
        } catch (\Throwable) {
        }

        // Хиты — форматируем цену и проставляем 1-based позицию для нумерации.
        $i = 1;
        foreach ($hits as &$h) {
            $h['price_fmt'] = $fmt((float) $h['price']);
            $h['position']  = $i++;
        }
        unset($h);

        // Бар-чарт: подпись для tooltip.
        foreach ($byDay as &$d) {
            $d['rev_fmt'] = $fmt((float) ($d['rev'] ?? 0));
        }
        unset($d);

        return [
            'kpi' => [
                ['label' => 'Оборот за 30 дней', 'value' => $fmt($revenue30),
                 'sub'   => $fmt($revenueToday) . ' сегодня',                          'tone' => 'rev'],
                ['label' => 'Сделок за 30 дней', 'value' => (string) $orders30,
                 'sub'   => $ordersToday . ' сегодня',                                 'tone' => 'ord'],
                ['label' => 'Средний чек',       'value' => $aov > 0 ? $fmt($aov) : '—',
                 'sub'   => 'считаем по 30 дням',                                       'tone' => 'aov'],
                ['label' => 'Покупателей',       'value' => (string) $clientsAll,
                 'sub'   => '+' . $clients30 . ' за 30д',                              'tone' => 'cli'],
            ],
            'by_day'   => $byDay,
            'has_data' => $orders30 > 0,
            'alerts'   => $alerts,
            'catalog'  => $catalog,
            'hits'     => $hits,
            'recent'   => $recent,
        ];
    }

    private function safeOrderCount(array $statuses): int
    {
        try {
            return (int) $this->conn->builder('shop_orders')->whereIn('status', $statuses)->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function safeProductCount(string $op, ?int $value, ?string $status = null): int
    {
        try {
            $q = $this->conn->builder('shop_products');
            if ($value !== null) $q->where('stock', $op, $value);
            if ($status !== null) $q->where('status', '=', $status);
            return (int) $q->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function safeProductCountBetween(int $min, int $max): int
    {
        try {
            return (int) $this->conn->builder('shop_products')
                ->whereRaw($this->conn->quoteId('stock') . ' BETWEEN ? AND ?', [$min, $max])
                ->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function safeReviewCount(bool $approved): int
    {
        try {
            return (int) $this->conn->builder('shop_reviews')->where('is_approved', '=', $approved)->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function safePromoActiveCount(): int
    {
        try {
            return (int) $this->conn->builder('shop_promo_codes')->where('is_active', '=', true)->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function safeCount(string $table): int
    {
        try {
            return (int) $this->conn->builder($table)->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function safeCategoriesCount(string $entity): int
    {
        try {
            return (int) $this->conn->builder('celena_categories')
                ->where('entity_type', '=', $entity)->count();
        } catch (\Throwable) {
            return 0;
        }
    }
}
