<?php
declare(strict_types=1);

namespace Celena\Module\Dashboard;

use Celena\Core\Database\Connection;
use Celena\Core\Http\Request;
use Celena\Core\Http\Response;
use Celena\Core\Plugin\Hook;
use Celena\Core\Security\Auth;
use Celena\Core\Template\Engine;
use Celena\Module\AdminBase\AdminController as BaseAdminController;
use Celena\Module\Settings\Options;

/**
 * Главная страница админки. Универсальный дашборд:
 *   1. Пульс CMS — крупные метрики (контент, команда, плагины, хранилище).
 *   2. Срез контента — счётчики страниц/новостей/категорий/комментариев/баннеров.
 *   3. Требует внимания — пендинг-комменты и т.п.
 *   4. Активность системы — async-блок recent_activity.
 *   5. Быстрый старт.
 *
 * Плагины расширяют дашборд через фильтр `admin.dashboard.sections`,
 * каждая секция — массив { id, html, position: 'top|main|bottom', sort }.
 * Плагин shop добавляет свой блок «Витрина магазина».
 */
final class AdminController extends BaseAdminController
{
    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');
        }

        // 1. Пульс CMS.
        $usersTotal   = $this->safeCount('celena_users');
        $newsTotal    = $this->safeCount('celena_news');
        $pagesTotal   = $this->safeCount('celena_pages');
        $usersFresh   = $this->safeCountSince('celena_users', 'created_at', 30);
        $newsFresh    = $this->safeCountSince('celena_news', 'created_at', 30);
        $pagesFresh   = $this->safeCountSince('celena_pages', 'created_at', 30);

        $pluginsAll    = count(json_decode((string) $this->options->get('active_plugins', '[]'), true) ?: []);
        $pluginsKnown  = count(glob(dirname(__DIR__, 2) . '/plugins/*/manifest.json') ?: []);

        $storageBytes = $this->dirSize(dirname(__DIR__, 2) . '/storage');
        $storageHuman = $this->humanBytes($storageBytes);

        // 2. Срез контента.
        $contentRows = [
            ['label' => 'Новостей',     'value' => $newsTotal,                        'href' => '/admin/news'],
            ['label' => 'Страниц',      'value' => $pagesTotal,                       'href' => '/admin/pages'],
            ['label' => 'Категорий',    'value' => $this->safeCount('celena_categories'), 'href' => '/admin/categories'],
            ['label' => 'Комментариев', 'value' => $this->safeCount('celena_comments'),   'href' => '/admin/comments'],
            ['label' => 'Медиа',        'value' => $this->safeCount('celena_media'),      'href' => '/admin/media'],
            ['label' => 'Баннеров',     'value' => $this->safeCount('celena_banners'),    'href' => '/admin/banners'],
            ['label' => 'Меню',         'value' => $this->safeCount('celena_menus'),      'href' => '/admin/menus'],
            ['label' => 'Городов',      'value' => $this->safeCount('celena_cities'),     'href' => '/admin/cities'],
        ];

        // 3. Требует внимания.
        $todo = [];
        $pending = $this->safeCount('celena_comments', ['status', '=', 'pending']);
        if ($pending > 0) {
            $todo[] = [
                'icon'  => 'comments', 'label' => 'Комментарии на модерации',
                'value' => $pending,   'href'  => '/admin/comments?status=pending',
            ];
        }
        $emptyAlts = $this->countMediaWithoutAlt();
        if ($emptyAlts > 0) {
            $todo[] = [
                'icon'  => 'image', 'label' => 'Медиа без alt-текста',
                'value' => $emptyAlts, 'href' => '/admin/media',
            ];
        }
        $missingTranslations = $this->countMissingTranslations();
        if ($missingTranslations > 0) {
            $todo[] = [
                'icon'  => 'language', 'label' => 'Дыр в локалях',
                'value' => $missingTranslations, 'href' => '/admin/translations',
            ];
        }
        // Дефолтных задач, если всё чисто, не показываем — выведем баннер «всё в порядке».

        // Версия CMS (читаем из composer.json или заглушка).
        $version = $this->cmsVersion();

        // 5. Расширения от плагинов.
        $sections = Hook::filter('admin.dashboard.sections', []);
        // Сортируем по sort, по умолчанию 100.
        usort($sections, fn ($a, $b) => ((int) ($a['sort'] ?? 100)) <=> ((int) ($b['sort'] ?? 100)));
        $sectionsTop    = array_filter($sections, fn ($s) => ($s['position'] ?? 'main') === 'top');
        $sectionsMain   = array_filter($sections, fn ($s) => ($s['position'] ?? 'main') === 'main');
        $sectionsBottom = array_filter($sections, fn ($s) => ($s['position'] ?? 'main') === 'bottom');

        return $this->view('dashboard', [
            'pulse' => [
                ['label' => 'Контент',   'value' => (string) ($newsTotal + $pagesTotal),
                 'delta' => '+' . ($newsFresh + $pagesFresh) . ' за 30д',
                 'icon'  => 'news',  'tone' => 'a'],
                ['label' => 'Команда',   'value' => (string) $usersTotal,
                 'delta' => '+' . $usersFresh . ' за 30д',
                 'icon'  => 'users', 'tone' => 'b'],
                ['label' => 'Плагины',   'value' => $pluginsAll . ' / ' . $pluginsKnown,
                 'delta' => 'активных / установленных',
                 'icon'  => 'plugins', 'tone' => 'c'],
                ['label' => 'Хранилище', 'value' => $storageHuman,
                 'delta' => 'storage/',
                 'icon'  => 'box',   'tone' => 'd'],
            ],
            'content_rows'    => $contentRows,
            'todo'            => $todo,
            'todo_empty'      => $todo === [],
            'sections_top'    => $sectionsTop,
            'sections_main'   => $sectionsMain,
            'sections_bottom' => $sectionsBottom,
            'cms_version'     => $version,
            'now' => date('Y-m-d H:i'),
        ], 'dashboard', 'Главная');
    }

    /** Безопасный count с опц. условием. */
    private function safeCount(string $table, ?array $where = null): int
    {
        try {
            $q = $this->conn->builder($table);
            if ($where !== null) {
                [$col, $op, $val] = $where;
                $q->where($col, $op, $val);
            }
            return (int) $q->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function safeCountSince(string $table, string $column, int $days): int
    {
        try {
            $since = date('Y-m-d H:i:s', strtotime("-$days days"));
            return (int) $this->conn->builder($table)->where($column, '>=', $since)->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    private function countMediaWithoutAlt(): int
    {
        try {
            return (int) $this->conn->builder('celena_media')
                ->whereRaw('(' . $this->conn->quoteId('alt') . " IS NULL OR " . $this->conn->quoteId('alt') . " = '')")
                ->count();
        } catch (\Throwable) {
            return 0;
        }
    }

    /** Считает суммарные «дыры»: ключи, которые есть в одной локали ядра, но отсутствуют в других.
     *  JSON-локали поддерживают вложенные секции — рекурсивно расплющиваем в плоский dot-словарь. */
    private function countMissingTranslations(): int
    {
        $root = dirname(__DIR__, 2);
        $files = glob($root . '/core/languages/*.json') ?: [];
        if (count($files) < 2) return 0;
        $allKeys = [];
        $perLocale = [];
        foreach ($files as $f) {
            $data = json_decode((string) @file_get_contents($f), true);
            if (!is_array($data)) continue;
            $flat = $this->flattenLocale($data);
            $perLocale[$f] = $flat;
            foreach (array_keys($flat) as $k) $allKeys[$k] = true;
        }
        $holes = 0;
        foreach ($perLocale as $flat) {
            foreach (array_keys($allKeys) as $k) {
                if (!isset($flat[$k]) || $flat[$k] === '') $holes++;
            }
        }
        return $holes;
    }

    /** Плоский dot-словарь: { admin: { save: 'Сохранить' } } → { 'admin.save' => 'Сохранить' }. */
    private function flattenLocale(array $data, string $prefix = ''): array
    {
        $out = [];
        foreach ($data as $k => $v) {
            $key = $prefix === '' ? (string) $k : $prefix . '.' . $k;
            if (is_array($v)) {
                $out += $this->flattenLocale($v, $key);
            } else {
                $out[$key] = is_scalar($v) ? trim((string) $v) : '';
            }
        }
        return $out;
    }

    private function dirSize(string $path): int
    {
        if (!is_dir($path)) return 0;
        $size = 0;
        try {
            $it = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS)
            );
            foreach ($it as $f) {
                if ($f->isFile()) $size += $f->getSize();
            }
        } catch (\Throwable) {
        }
        return $size;
    }

    private function humanBytes(int $bytes): string
    {
        if ($bytes < 1024) return $bytes . ' B';
        $units = ['KB', 'MB', 'GB', 'TB'];
        $i = 0;
        $v = $bytes / 1024;
        while ($v >= 1024 && $i < count($units) - 1) {
            $v /= 1024;
            $i++;
        }
        return number_format($v, $v >= 100 ? 0 : 1, '.', '') . ' ' . $units[$i];
    }

    private function cmsVersion(): string
    {
        $root = dirname(__DIR__, 2);
        $composer = $root . '/composer.json';
        if (is_file($composer)) {
            $data = json_decode((string) file_get_contents($composer), true);
            if (is_array($data) && isset($data['version'])) return (string) $data['version'];
        }
        return '1.0.0-dev';
    }
}
