<?php

namespace App\Http\Controllers\Admin;

use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Models\Page;
use App\Models\BlogPost;
use App\Models\GalleryAlbum;
use App\Models\Event;
use App\Models\Download;
use App\Models\Media;
use App\Models\User;
use App\Models\Setting;
use App\Services\MaintenanceService;
use App\Services\Marketplace\UpdateChecker;

class DashboardController extends Controller
{
    public function index(Request $request, array $params = []): void
    {
        $basePath    = $this->app->basePath();
        $versionFile = $basePath . '/app/Config/version.php';
        $cmsVersion  = is_file($versionFile) ? require $versionFile : '–';

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

        // Single query for all counts + health metrics
        $counts = $db->fetch("
            SELECT
                (SELECT COUNT(*) FROM iw_pages) AS pages,
                (SELECT COUNT(*) FROM iw_blog_posts) AS posts,
                (SELECT COUNT(*) FROM iw_blog_posts WHERE status = 'published') AS posts_published,
                (SELECT COUNT(*) FROM iw_blog_posts WHERE status = 'draft') AS posts_draft,
                (SELECT COUNT(*) FROM iw_blog_posts WHERE status = 'scheduled') AS posts_scheduled,
                (SELECT COUNT(*) FROM iw_pages WHERE status = 'scheduled') AS pages_scheduled,
                (SELECT COUNT(*) FROM iw_gallery_albums) AS albums,
                (SELECT COUNT(*) FROM iw_events) AS events,
                (SELECT COUNT(*) FROM iw_events WHERE publish_status = 'published' AND start_date >= CURDATE()) AS events_upcoming,
                (SELECT COUNT(*) FROM iw_downloads) AS downloads,
                (SELECT COUNT(*) FROM iw_media) AS media,
                (SELECT COUNT(*) FROM iw_users) AS users,
                (SELECT COUNT(*) FROM iw_pages WHERE (seo_description IS NULL OR seo_description = '') AND status = 'published') AS pages_no_seo,
                (SELECT COUNT(*) FROM iw_blog_posts WHERE (featured_image IS NULL OR featured_image = '') AND status = 'published') AS posts_no_image,
                (SELECT COUNT(*) FROM iw_pages WHERE status = 'draft' AND updated_at < DATE_SUB(NOW(), INTERVAL 30 DAY))
                    + (SELECT COUNT(*) FROM iw_blog_posts WHERE status = 'draft' AND updated_at < DATE_SUB(NOW(), INTERVAL 30 DAY)) AS old_drafts
        ") ?: [];

        // Locale-aware date + time-based greeting
        $locale = current_locale();
        $hour   = (int) date('H');

        if ($locale === 'de') {
            $days   = ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'];
            $months = ['','Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
            $todayFormatted = $days[(int) date('w')] . ', ' . date('j') . '. ' . $months[(int) date('n')] . ' ' . date('Y');
            if ($hour < 12)      $greetingKey = 'greeting_morning';
            elseif ($hour < 17)  $greetingKey = 'greeting_afternoon';
            else                 $greetingKey = 'greeting_evening';
        } else {
            $todayFormatted = date('l, F j, Y');
            if ($hour < 12)      $greetingKey = 'greeting_morning';
            elseif ($hour < 17)  $greetingKey = 'greeting_afternoon';
            else                 $greetingKey = 'greeting_evening';
        }

        // Storage info
        $diskTotal    = @disk_total_space($basePath) ?: 0;
        $diskFree     = @disk_free_space($basePath) ?: 0;
        $diskUsed     = $diskTotal - $diskFree;
        $diskUsedPct  = $diskTotal > 0 ? round(($diskUsed / $diskTotal) * 100) : 0;
        $storageInfo  = [
            'used'     => $diskUsed,
            'total'    => $diskTotal,
            'used_pct' => $diskUsedPct,
            'used_fmt' => self::formatBytes($diskUsed),
            'total_fmt'=> self::formatBytes($diskTotal),
        ];

        // Plugin count from storage/plugins_active.json
        $pluginsFile      = $basePath . '/storage/plugins_active.json';
        $activePluginList = [];
        if (is_file($pluginsFile)) {
            $raw = file_get_contents($pluginsFile);
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                // Format can be ['plugin-a', 'plugin-b'] OR {'plugin-a': true, 'plugin-b': false}
                if (array_is_list($decoded)) {
                    $activePluginList = $decoded;
                } else {
                    $activePluginList = array_keys(array_filter($decoded, fn($v) => $v === true));
                }
            }
        }
        $activePluginCount = count($activePluginList);

        // Content Health checks
        $pagesNoSeo   = (int) ($counts['pages_no_seo'] ?? 0);
        $postsNoImage = (int) ($counts['posts_no_image'] ?? 0);
        $oldDrafts    = (int) ($counts['old_drafts'] ?? 0);
        $robotsExists = file_exists($basePath . '/public/robots.txt');

        $healthScore  = 100;
        $healthIssues = [];

        if ($pagesNoSeo > 0) {
            $healthScore -= 15;
            $healthIssues[] = [
                'type'   => 'warn',
                'label'  => sprintf($pagesNoSeo === 1 ? '1 Seite ohne SEO-Beschreibung' : '%d Seiten ohne SEO-Beschreibung', $pagesNoSeo),
                'link'   => admin_url('pages') . '?filter=no_seo',
                'action' => 'fix',
            ];
        }
        if ($postsNoImage > 0) {
            $healthScore -= 10;
            $healthIssues[] = [
                'type'   => 'warn',
                'label'  => sprintf($postsNoImage === 1 ? '1 Blog-Post ohne Beitragsbild' : '%d Blog-Posts ohne Beitragsbild', $postsNoImage),
                'link'   => admin_url('blog'),
                'action' => 'fix',
            ];
        }
        if ($oldDrafts > 0) {
            $healthScore -= 10;
            $healthIssues[] = [
                'type'   => 'warn',
                'label'  => sprintf($oldDrafts === 1 ? '1 Entwurf älter als 30 Tage' : '%d Entwürfe älter als 30 Tage', $oldDrafts),
                'link'   => admin_url('pages'),
                'action' => 'fix',
            ];
        }
        if (!$robotsExists) {
            $healthScore -= 10;
            $healthIssues[] = [
                'type'   => 'warn',
                'label'  => 'robots.txt fehlt',
                'link'   => admin_url('dashboard/create-robots'),
                'action' => 'create',
            ];
        }

        // Positive checks
        $homepageSlug = Setting::get('homepage', '');
        $homepageOk   = !empty($homepageSlug);
        if ($homepageOk) {
            $healthIssues[] = ['type' => 'ok', 'label' => 'Startseite festgelegt', 'link' => null, 'action' => null];
        }
        if ($robotsExists) {
            $healthIssues[] = ['type' => 'ok', 'label' => 'robots.txt vorhanden', 'link' => null, 'action' => null];
        }

        $healthScore = max(0, $healthScore);
        if ($healthScore >= 90)     $healthClass = 'good';
        elseif ($healthScore >= 60) $healthClass = 'warn';
        else                        $healthClass = 'bad';

        $issueCount = count(array_filter($healthIssues, fn($i) => $i['type'] === 'warn'));

        // Accessibility audit widget (B3) – cached for 1 hour
        $a11yIssues = [];
        $a11yEnabled = Setting::get('accessibility_audit_enabled', '1') === '1';
        if ($a11yEnabled) {
            $a11yIssues = $this->getCachedAccessibilityAudit($db);
        }

        // Dashboard layout (panel order + visibility)
        $defaultLayout = [
            'left'  => ['recent-posts', 'recent-pages'],
            'right' => ['events', 'health', 'accessibility', 'quick-draft', 'system'],
        ];
        $layoutJson  = Setting::get('dashboard_layout', '');
        $dashLayout  = $layoutJson ? (json_decode($layoutJson, true) ?: $defaultLayout) : $defaultLayout;

        // Ensure newly added panels appear even if the user has a saved layout
        $allSaved = array_merge($dashLayout['left'] ?? [], $dashLayout['right'] ?? []);
        $allDefault = array_merge($defaultLayout['left'], $defaultLayout['right']);
        foreach ($allDefault as $panel) {
            if (!in_array($panel, $allSaved, true)) {
                $dashLayout['right'][] = $panel;
            }
        }

        $hiddenJson    = Setting::get('dashboard_hidden_panels', '');
        $hiddenPanels  = $hiddenJson ? (json_decode($hiddenJson, true) ?: []) : [];

        // Marketplace updates (reads from cache – no network call)
        $marketplaceUpdates = [];
        try {
            $checker = new UpdateChecker($basePath);
            $marketplaceUpdates = $checker->getCached();
        } catch (\Throwable $ignored) {}

        // CMS Core update info (reads from cache – no network call)
        $cmsUpdateInfo = ['available' => false, 'version' => '', 'data' => null, 'checked_at' => ''];
        try {
            $cmsChecker   = new \App\Services\CmsUpdate\CmsUpdateChecker($basePath);
            $cmsUpdateInfo = $cmsChecker->getCached();
        } catch (\Throwable $ignored) {}

        // Integrity drift alert (set by IntegrityMonitor pseudo-cron)
        $integrityDrift = Setting::get('marketplace_integrity_drift', '');

        $data = [
            'pageTitle'          => __('admin.dashboard.title'),
            'todayFormatted'     => $todayFormatted,
            'greetingKey'        => $greetingKey,

            // primary stats
            'pageCount'          => (int) ($counts['pages'] ?? 0),
            'postCount'          => (int) ($counts['posts'] ?? 0),
            'postPublished'      => (int) ($counts['posts_published'] ?? 0),
            'postDraft'          => (int) ($counts['posts_draft'] ?? 0),
            'postScheduled'      => (int) ($counts['posts_scheduled'] ?? 0),
            'pagesScheduled'     => (int) ($counts['pages_scheduled'] ?? 0),
            'galleryCount'       => (int) ($counts['albums'] ?? 0),
            'eventCount'         => (int) ($counts['events'] ?? 0),
            'upcomingEventCount' => (int) ($counts['events_upcoming'] ?? 0),

            // secondary stats
            'downloadCount'      => (int) ($counts['downloads'] ?? 0),
            'mediaCount'         => (int) ($counts['media'] ?? 0),
            'userCount'          => (int) ($counts['users'] ?? 0),
            'activePluginCount'  => $activePluginCount,
            'storageInfo'        => $storageInfo,

            // content widgets
            'recentPosts'        => BlogPost::recent(5),
            'recentPages'        => Page::recentlyUpdated(5),
            'upcomingEvents'     => Event::upcomingCompact(3),

            // health widget
            'healthScore'        => $healthScore,
            'healthClass'        => $healthClass,
            'healthIssues'       => $healthIssues,
            'healthIssueCount'   => $issueCount,

            // system info
            'systemInfo'         => [
                'cms_version'  => $cmsVersion,
                'php_version'  => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION,
                'active_theme' => Setting::get('active_theme', 'default'),
                'maintenance'  => MaintenanceService::isActive($basePath),
            ],

            // accessibility audit (B3)
            'a11yEnabled'        => $a11yEnabled,
            'a11yIssues'         => $a11yIssues,

            // layout
            'dashLayout'         => $dashLayout,
            'hiddenPanels'       => $hiddenPanels,
            'cmsVersion'         => $cmsVersion,

            // integrity drift alert
            'integrityDrift'     => $integrityDrift,

            // marketplace update badge
            'marketplaceUpdates' => [
                'total'          => count($marketplaceUpdates),
                'security_count' => count(array_filter($marketplaceUpdates, fn($u) => $u['security'] ?? false)),
                'packages'       => $marketplaceUpdates,
            ],

            // CMS core update info (combined updates widget)
            'cmsUpdateInfo' => $cmsUpdateInfo,
            'updates' => [
                'core'    => $cmsUpdateInfo,
                'plugins' => array_values(array_filter($marketplaceUpdates, fn($p) => ($p['type'] ?? '') === 'plugin')),
                'themes'  => array_values(array_filter($marketplaceUpdates, fn($p) => ($p['type'] ?? '') === 'theme')),
            ],

            'auth'               => $this->auth(),
            'csrf'               => $this->csrf(),
        ];

        $view = $this->app->service('view');
        $html = $view->renderAdmin('dashboard/index', $data);
        Response::html($html);
    }

    /**
     * Create a default robots.txt in public/ and redirect back to dashboard.
     */
    public function createRobots(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $file = $this->app->basePath() . '/public/robots.txt';
        if (!file_exists($file)) {
            file_put_contents($file, "User-agent: *\nAllow: /\n\nSitemap: /sitemap.xml\n");
        }
        Response::redirect(admin_url() . '?success=robots_created');
    }

    /**
     * Quick-Draft: saves a new page or blog post as draft and redirects to editor.
     */
    public function quickDraft(Request $request, array $params = []): void
    {
        if (!$this->csrf()->validate($request->input('_csrf_token', ''))) {
            Response::redirect(admin_url() . '?error=csrf');
            return;
        }

        $title   = trim($request->input('title', ''));
        $content = trim($request->input('content', ''));
        $type    = $request->input('type', 'blog');

        if ($title === '') {
            Response::redirect(admin_url() . '?error=empty_title');
            return;
        }

        $slug = slugify($title);

        if ($type === 'page') {
            $id = Page::create([
                'title'      => $title,
                'slug'       => $slug,
                'content'    => $content,
                'status'     => 'draft',
                'template'   => 'default',
                'sort_order' => 99,
                'show_title' => 1,
            ]);
            Response::redirect(admin_url('pages/edit/' . (int) $id));
        } else {
            $id = BlogPost::create([
                'title'   => $title,
                'slug'    => $slug,
                'content' => $content,
                'status'  => 'draft',
            ]);
            Response::redirect(admin_url('blog/edit/' . (int) $id));
        }
    }

    /**
     * Save dashboard panel layout (column order) via AJAX.
     */
    public function saveLayout(Request $request, array $params = []): void
    {
        if (!$this->csrf()->validate($request->input('_csrf_token', ''))) {
            Response::json(['ok' => false, 'error' => 'CSRF']);
            return;
        }

        $layoutJson = $request->input('layout', '');
        $layout     = json_decode($layoutJson, true);

        if (!is_array($layout) || !isset($layout['left'], $layout['right'])) {
            Response::json(['ok' => false, 'error' => 'invalid']);
            return;
        }

        // Sanitize: only allow known panel IDs
        $allowed = ['recent-posts', 'recent-pages', 'events', 'health', 'accessibility', 'quick-draft', 'system'];
        $layout['left']  = array_values(array_intersect($layout['left'],  $allowed));
        $layout['right'] = array_values(array_intersect($layout['right'], $allowed));

        Setting::set('dashboard_layout', json_encode($layout), 'dashboard');
        Response::json(['ok' => true]);
    }

    /**
     * Toggle panel visibility via AJAX.
     */
    public function togglePanel(Request $request, array $params = []): void
    {
        if (!$this->csrf()->validate($request->input('_csrf_token', ''))) {
            Response::json(['ok' => false, 'error' => 'CSRF']);
            return;
        }

        $panelId = $request->input('panel', '');
        $visible = $request->input('visible', '1') === '1';

        $allowed = ['recent-posts', 'recent-pages', 'events', 'health', 'accessibility', 'quick-draft', 'system'];
        if (!in_array($panelId, $allowed, true)) {
            Response::json(['ok' => false, 'error' => 'unknown panel']);
            return;
        }

        $hiddenJson   = Setting::get('dashboard_hidden_panels', '');
        $hiddenPanels = $hiddenJson ? (json_decode($hiddenJson, true) ?: []) : [];

        if ($visible) {
            $hiddenPanels = array_values(array_diff($hiddenPanels, [$panelId]));
        } else {
            if (!in_array($panelId, $hiddenPanels, true)) {
                $hiddenPanels[] = $panelId;
            }
        }

        Setting::set('dashboard_hidden_panels', json_encode($hiddenPanels), 'dashboard');
        Response::json(['ok' => true, 'hidden' => $hiddenPanels]);
    }

    /**
     * Get cached accessibility audit results (TTL: 1 hour).
     * Avoids expensive DOMDocument parsing on every dashboard load.
     */
    private function getCachedAccessibilityAudit(\App\Core\Database $db): array
    {
        $cacheFile = $this->app->basePath() . '/storage/cache/a11y_audit.json';
        $ttl = 3600;

        if (is_file($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
            $cached = @file_get_contents($cacheFile);
            if ($cached !== false) {
                $data = json_decode($cached, true);
                if (is_array($data)) {
                    return $data;
                }
            }
        }

        $issues = $this->runAccessibilityAudit($db);

        $cacheDir = dirname($cacheFile);
        if (!is_dir($cacheDir)) {
            @mkdir($cacheDir, 0755, true);
        }
        @file_put_contents($cacheFile, json_encode($issues), LOCK_EX);

        return $issues;
    }

    /**
     * Scan published pages and blog posts for common accessibility issues.
     *
     * Uses DOMDocument to parse stored HTML content and check for:
     *  - Images without alt text
     *  - Empty links (no text or aria-label)
     *  - Heading hierarchy skips (e.g. h1 → h3)
     *
     * @return array List of issues with type, label, link, entity_type, entity_id
     */
    private function runAccessibilityAudit(\App\Core\Database $db): array
    {
        $issues = [];

        // Scan pages
        $pages = $db->fetchAll(
            "SELECT id, title, slug, content FROM iw_pages WHERE status = 'published' AND content != '' LIMIT 50"
        );
        foreach ($pages as $page) {
            $pageIssues = self::scanHtmlForA11y($page['content'] ?? '');
            foreach ($pageIssues as $issue) {
                $issue['entity_type'] = 'page';
                $issue['entity_id']   = $page['id'];
                $issue['entity_title'] = $page['title'];
                $issue['link']        = admin_url('pages/edit/' . $page['id']);
                $issues[] = $issue;
            }
        }

        // Scan blog posts
        $posts = $db->fetchAll(
            "SELECT id, title, slug, content FROM iw_blog_posts WHERE status = 'published' AND content != '' LIMIT 50"
        );
        foreach ($posts as $post) {
            $postIssues = self::scanHtmlForA11y($post['content'] ?? '');
            foreach ($postIssues as $issue) {
                $issue['entity_type'] = 'blog';
                $issue['entity_id']   = $post['id'];
                $issue['entity_title'] = $post['title'];
                $issue['link']        = admin_url('blog/edit/' . $post['id']);
                $issues[] = $issue;
            }
        }

        return $issues;
    }

    /**
     * Scan HTML content for accessibility issues.
     *
     * @return array[] Each with 'type' and 'label'
     */
    private static function scanHtmlForA11y(string $html): array
    {
        if (trim($html) === '') {
            return [];
        }

        $issues = [];

        libxml_use_internal_errors(true);
        $doc = new \DOMDocument();
        $doc->loadHTML('<?xml encoding="utf-8"?><div>' . $html . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        libxml_clear_errors();

        // Check images without alt text
        $images = $doc->getElementsByTagName('img');
        foreach ($images as $img) {
            $alt = $img->getAttribute('alt');
            if ($alt === '') {
                $src = $img->getAttribute('src');
                $issues[] = [
                    'type'  => 'missing_alt',
                    'label' => basename($src ?: 'image'),
                ];
            }
        }

        // Check empty links
        $links = $doc->getElementsByTagName('a');
        foreach ($links as $link) {
            $text      = trim($link->textContent);
            $ariaLabel = trim($link->getAttribute('aria-label'));
            $title     = trim($link->getAttribute('title'));
            if ($text === '' && $ariaLabel === '' && $title === '') {
                // Check for child images with alt
                $childImgs = $link->getElementsByTagName('img');
                $hasAlt = false;
                foreach ($childImgs as $ci) {
                    if ($ci->getAttribute('alt') !== '') {
                        $hasAlt = true;
                        break;
                    }
                }
                if (!$hasAlt) {
                    $href = $link->getAttribute('href');
                    $issues[] = [
                        'type'  => 'empty_link',
                        'label' => $href ?: '(no href)',
                    ];
                }
            }
        }

        // Check heading hierarchy
        $headings = [];
        $xpath = new \DOMXPath($doc);
        $hNodes = $xpath->query('//h1|//h2|//h3|//h4|//h5|//h6');
        foreach ($hNodes as $h) {
            $level = (int) substr($h->nodeName, 1);
            $headings[] = $level;
        }
        for ($i = 1; $i < count($headings); $i++) {
            if ($headings[$i] > $headings[$i - 1] + 1) {
                $issues[] = [
                    'type'  => 'heading_skip',
                    'label' => "h{$headings[$i-1]} → h{$headings[$i]}",
                ];
                break; // One skip warning per content block is enough
            }
        }

        return $issues;
    }

    private static function formatBytes(int $bytes, int $precision = 1): string
    {
        if ($bytes <= 0) return '0 B';
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = (int) floor(log($bytes, 1024));
        $i = min($i, count($units) - 1);
        return round($bytes / pow(1024, $i), $precision) . ' ' . $units[$i];
    }
}
