<?php

namespace App\Services;

use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;

/**
 * ModuleMenuService
 *
 * Responsible for merging module menu configs, filtering by permissions,
 * and enriching menu items with manifest-based asset URLs and version info.
 */
class ModuleMenuService
{
    /**
     * Get all module menu items filtered by user permissions and enriched with assets.
     *
     * @param \Illuminate\Contracts\Auth\Authenticatable|null $user
     * @return array
     */
    public function getMenuItems($user = null): array
    {
        // Cache key includes user ID and manifest versions for automatic cache invalidation
        $manifestHash = $this->getManifestHash();
        $cacheKey = 'module_menus_' . ($user ? $user->id : 'guest') . '_' . $manifestHash;

        $menus = $this->loadModuleMenus();
        $menus = $this->filterByPermissions($menus, $user);
        $menus = $this->enrichWithAssets($menus);
        $menus = $this->sortMenus($menus);
        return $menus;

        // return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($user) {
        //     $menus = $this->loadModuleMenus();
        //     $menus = $this->filterByPermissions($menus, $user);
        //     $menus = $this->enrichWithAssets($menus);
        //     $menus = $this->sortMenus($menus);

        //     return $menus;
        // });
    }

    /**
     * Load all menu.php config files from enabled modules.
     * If menu.php doesn't exist, generate menu dynamically from module.json.
     *
     * @return array
     */
    protected function loadModuleMenus(): array
    {
        $modulesPath = base_path('Modules');
        $menus = [];

        if (!File::exists($modulesPath)) {
            return $menus;
        }

        $modules = File::directories($modulesPath);

        foreach ($modules as $modulePath) {
            $moduleName = basename($modulePath);
            $menuConfigPath = $modulePath . '/resources/config/menu.php';

            // Check if module is enabled
            if (!$this->isModuleEnabled($moduleName)) {
                continue;
            }

            $menuConfig = null;

            // Try to load menu.php first
            if (File::exists($menuConfigPath)) {
                $menuConfig = require $menuConfigPath;
            } else {
                // Fallback: Generate menu dynamically from module.json
                $menuConfig = $this->generateMenuConfig($modulePath, $moduleName);
            }
            // Process menu configuration
            if (is_array($menuConfig)) {
                // Wrap single menu item in array if not already
                if (isset($menuConfig['module'])) {
                    $menuConfig = [$menuConfig];
                }


                foreach ($menuConfig as $menuItem) {
                    $menuItem['module'] = $menuItem['module'] ?? strtolower($moduleName);
                    $menuItem['status'] = 'enabled';
                    $menus[] = $menuItem;
                }
            }
        }

        return $menus;
    }

    /**
     * Generate menu configuration dynamically from module.json
     * Scans router folder and builds menu based on route files
     *
     * @param string $modulePath
     * @param string $moduleName
     * @return array|null
     */
    protected function generateMenuConfig(string $modulePath, string $moduleName): ?array
    {
        $moduleJsonPath = $modulePath . '/module.json';

        if (!File::exists($moduleJsonPath)) {
            return null;
        }

        $moduleJson = json_decode(File::get($moduleJsonPath), true);

        if (!$moduleJson) {
            return null;
        }

        // Use alias as the module name
        $moduleName = $moduleJson['alias'] ?? strtolower($moduleName);
        $moduleLabel = $moduleJson['name'] ?? ucfirst($moduleName);

        $routePrefix = Str::of($moduleLabel)
            ->kebab()          // convert camelCase or PascalCase to kebab-case → "user-profile"
            // ->plural()         // pluralize → "user-profiles"
            ->lower();         // ensure lowercase (optional if kebab handles it)

        $rootLangTrans = Str::of($moduleLabel)
            // ->plural()
            ->snake()
            ->lower();

        // Get version from module.json
        $version = $moduleJson['version'] ?? '1.0.0';

        // Scan router folder and build menu structure
        $routerPath = $modulePath . '/resources/assets/js/router';
        $menuData = $this->buildMenuFromRouterFiles($routerPath, $routePrefix, $moduleLabel);
        // Generate menu configuration from module.json
        return [
            'name' => $moduleName,
            'version' => $version,
            'module' => $moduleName, // This will be plural kebab-case
            'entry_key' => 'resources/assets/js/app.js',
            'label' => $moduleLabel,
            'icon' => $moduleJson['menu_icon'] ?? 'AppstoreOutlined',
            'route_prefix' => $moduleJson['route_prefix'] ?? $routePrefix,
            'route' => $menuData['route'] ?? '/admin/' . $routePrefix,
            'permission' => $menuData['permission'] ?? $moduleName . '.view',
            'order' => $moduleJson['order'] ?? 999,
            'children' => $menuData['children'] ?? [],
            'lang_trans' => $rootLangTrans
        ];
    }

    /**
     * Build menu structure by scanning router files
     *
     * @param string $routerPath
     * @param string $moduleName
     * @param string $moduleLabel
     * @return array
     */
    protected function buildMenuFromRouterFiles(string $routerPath, string $routePrefix, string $moduleLabel): array
    {
        $rootLangTrans = Str::of($moduleLabel)
            ->snake()
            ->lower();

        $menuData = [
            'route' => '/admin/' . $routePrefix,
            'permission' => $routePrefix . '_view',
            'children' => [],
            'lang_trans' => $rootLangTrans
        ];

        // Get all JS files in router folder
        $routerFiles = File::files($routerPath);
        $regularRoutes = [];
        $reportRoutes = [];
        $settingsRoutes = [];

        foreach ($routerFiles as $file) {
            $fileName = $file->getFilename();

            // Skip non-JS files
            if (pathinfo($fileName, PATHINFO_EXTENSION) !== 'js') {
                continue;
            }

            // Parse the route file
            $routeInfo = $this->parseRouteFile($file->getPathname());

            if (!$routeInfo) {
                continue;
            }

            // Skip this route if skip_menu is true
            if ($routeInfo['skip_menu'] ?? false) {
                continue;
            }

            $routeStringLikeModuleAlias = str_replace('-', '', $routePrefix);
            $fileBaseName = str_replace('-', '_', pathinfo($fileName, PATHINFO_FILENAME));

            // Calculate lang_trans based on route type
            $langTrans = $routeInfo['lang_trans'] ?? null;
            if (!$langTrans) {
                if ($routeInfo['is_front_website'] ?? false) {
                    $langTrans = 'front_website';
                } elseif ($routeInfo['is_settings'] ?? false) {
                    $langTrans = 'settings';
                } else {
                    $langTrans = $routeStringLikeModuleAlias . '_' . $fileBaseName;
                }
            }

            $routeItem = [
                'label' => $routeInfo['label'] ?? ucfirst(pathinfo($fileName, PATHINFO_FILENAME)),
                'route' => $routeInfo['path'] ?? '/admin/' . $routePrefix . '/' . pathinfo($fileName, PATHINFO_FILENAME),
                'permission' => $routeInfo['permission'] ?? $routePrefix . '_' . pathinfo($fileName, PATHINFO_FILENAME) . '_view',
                'order' => $routeInfo['order'] ?? 999,
                'lang_trans' => $langTrans,
                'is_front_website' => $routeInfo['is_front_website'] ?? false,
                'is_settings' => $routeInfo['is_settings'] ?? false,
                'is_report' => $routeInfo['is_report'] ?? false,
                'skip_menu' => $routeInfo['skip_menu'] ?? false
            ];

            // Separate routes into three categories: regular, reports, and settings
            if ($routeItem['is_settings']) {
                $settingsRoutes[] = $routeItem;
            } elseif ($routeItem['is_report']) {
                $reportRoutes[] = $routeItem;
            } else {
                $regularRoutes[] = $routeItem;
            }
        }

        // Sort regular routes by order
        usort($regularRoutes, function ($a, $b) {
            $orderA = $a['order'] ?? 999;
            $orderB = $b['order'] ?? 999;

            if ($orderA === $orderB) {
                return 0;
            }

            return $orderA <=> $orderB;
        });

        // Sort report routes by order (these will be inside the Reports submenu)
        usort($reportRoutes, function ($a, $b) {
            $orderA = $a['order'] ?? 999;
            $orderB = $b['order'] ?? 999;

            if ($orderA === $orderB) {
                return 0;
            }

            return $orderA <=> $orderB;
        });

        // Build final children array: regular routes, then reports submenu, then settings
        $childRoutes = $regularRoutes;

        // Add Reports submenu if there are any report routes
        if (!empty($reportRoutes)) {
            $childRoutes[] = [
                'label' => 'Reports',
                'lang_trans' => 'reports',
                'is_submenu' => true,
                'children' => $reportRoutes,
            ];
        }

        // Add settings routes at the end (settings always last, regardless of order)
        foreach ($settingsRoutes as $settingsRoute) {
            $childRoutes[] = $settingsRoute;
        }

        $menuData['children'] = $childRoutes;

        return $menuData;
    }

    /**
     * Parse a route file to extract menu information
     *
     * @param string $filePath
     * @return array|null
     */
    protected function parseRouteFile(string $filePath): ?array
    {
        $content = File::get($filePath);

        // Try to extract route information from the file
        // Look for meta object with menuKey, permission, etc.
        $routeInfo = [];

        // Extract path
        if (preg_match('/path:\s*[\'"]([^\'"]+)[\'"]/', $content, $matches)) {
            $routeInfo['path'] = $matches[1];
        }

        // Extract permission from meta
        if (preg_match('/permission:\s*[\'"]([^\'"]+)[\'"]/', $content, $matches)) {
            $routeInfo['permission'] = $matches[1];
        }

        // Extract order from meta
        if (preg_match('/order:\s*(\d+)/', $content, $matches)) {
            $routeInfo['order'] = (int) $matches[1];
        }

        // Extract menuKey/menuParent from meta
        if (preg_match('/menuKey:\s*[\'"]([^\'"]+)[\'"]/', $content, $matches)) {
            $routeInfo['label'] = ucfirst($matches[1]);
        } elseif (preg_match('/menuParent:\s*[\'"]([^\'"]+)[\'"]/', $content, $matches)) {
            $routeInfo['label'] = ucfirst($matches[1]);
        }

        // Extract title if available
        if (preg_match('/title:\s*[\'"]([^\'"]+)[\'"]/', $content, $matches)) {
            $routeInfo['label'] = $matches[1];
        }

        // Extract is_settings from meta (defaults to false if not found)
        if (preg_match('/is_settings:\s*(true|false)/', $content, $matches)) {
            $routeInfo['is_settings'] = $matches[1] === 'true';
        } else {
            $routeInfo['is_settings'] = false;
        }

        // Extract is_front_website from meta (defaults to false if not found)
        if (preg_match('/is_front_website:\s*(true|false)/', $content, $matches)) {
            $routeInfo['is_front_website'] = $matches[1] === 'true';
        } else {
            $routeInfo['is_front_website'] = false;
        }

        // Extract is_report from meta (defaults to false if not found)
        if (preg_match('/is_report:\s*(true|false)/', $content, $matches)) {
            $routeInfo['is_report'] = $matches[1] === 'true';
        } else {
            $routeInfo['is_report'] = false;
        }

        // Extract skip_menu from meta (defaults to false if not found)
        if (preg_match('/skip_menu:\s*(true|false)/', $content, $matches)) {
            $routeInfo['skip_menu'] = $matches[1] === 'true';
        } else {
            $routeInfo['skip_menu'] = false;
        }

        // Extract lang_trans from meta
        if (preg_match('/lang_trans:\s*[\'"]([^\'"]+)[\'"]/', $content, $matches)) {
            $routeInfo['lang_trans'] = $matches[1];
        }

        return !empty($routeInfo) ? $routeInfo : null;
    }

    /**
     * Check if module is enabled via modules_statuses.json.
     *
     * @param string $moduleName
     * @return bool
     */
    protected function isModuleEnabled(string $moduleName): bool
    {
        $statusFilePath = base_path('modules_statuses.json');

        if (!File::exists($statusFilePath)) {
            return false;
        }

        $moduleStatuses = json_decode(File::get($statusFilePath), true);
        return ($moduleStatuses[$moduleName] ?? false) === true;
    }

    /**
     * Filter menu items by user permissions.
     *
     * @param array $menus
     * @param \Illuminate\Contracts\Auth\Authenticatable|null $user
     * @return array
     */
    protected function filterByPermissions(array $menus, $user = null): array
    {
        return array_filter($menus, function ($menu) use ($user) {
            // TEMPORARILY DISABLED FOR TESTING
            // TODO: Re-enable permission checking in production
            return true;

            // If no permission required, include it
            if (empty($menu['permission'])) {
                return true;
            }

            // If no user, exclude protected items
            if (!$user) {
                return false;
            }

            // Check permission (adjust based on your auth system)
            // This example assumes a simple can() method; adapt to your Gate/Policy
            return $user->can($menu['permission']);
        });
    }

    /**
     * Enrich menu items with asset URLs and version from manifest.
     *
     * @param array $menus
     * @return array
     */
    protected function enrichWithAssets(array $menus): array
    {
        // If npm run dev command is running then
        // it will create hot file in public folder
        $hotFilePath = public_path('hot');
        $isDev = File::exists($hotFilePath);

        // We only fetch assets for production builds
        // if npm run dev command is running then
        // it will be loaded from blade file vite directive
        if (!$isDev) {
            foreach ($menus as &$menu) {
                // Skip if no module key (e.g., child menu items)
                if (!isset($menu['module'])) {
                    continue;
                }

                $module = $menu['module'];
                $entryKey = $menu['entry_key'] ?? 'resources/assets/js/app.js';


                // Production: read manifest.json
                $manifest = $this->readManifest($module);

                if (!$manifest) {
                    $menu['status'] = 'missing';
                    $menu['assets'] = ['js' => [], 'css' => []];
                    $menu['asset_version'] = null;
                    continue;
                }

                $entry = $manifest[$entryKey] ?? null;

                if (!$entry) {
                    $menu['status'] = 'missing';
                    $menu['assets'] = ['js' => [], 'css' => []];
                    $menu['asset_version'] = null;
                    continue;
                }

                $menu['assets'] = [
                    'js' => [asset('build-' . $module . '/' . $entry['file'])],
                    'css' => array_map(fn($css) => asset('build-' . $module . '/' . $css), $entry['css'] ?? []),
                ];
                // Use file hash from manifest as asset version
                $menu['asset_version'] = $entry['file'];

                // Children don't need assets, they're just navigation items
            }
        }

        return $menus;
    }

    /**
     * Read manifest.json for a given module.
     *
     * @param string $module
     * @return array|null
     */
    protected function readManifest(string $module): ?array
    {
        $manifestPath = public_path("build-{$module}/.vite/manifest.json");

        if (!File::exists($manifestPath)) {
            return null;
        }

        return json_decode(File::get($manifestPath), true);
    }

    /**
     * Get dev server port for a module (hardcoded or from config).
     *
     * @param string $module
     * @return int
     */
    protected function getModuleDevPort(string $module): int
    {
        // Map module names to dev ports
        // In production, read from module config or env
        $ports = [
            'blog' => 5174,
            'inventory' => 5175,
        ];

        return $ports[strtolower($module)] ?? 5174;
    }

    /**
     * Sort menus by order field.
     * For module children, preserves the structure: regular routes, reports submenu, settings (always at end)
     *
     * @param array $menus
     * @return array
     */
    protected function sortMenus(array $menus): array
    {
        // Sort top-level modules by order
        usort($menus, fn($a, $b) => ($a['order'] ?? 999) <=> ($b['order'] ?? 999));

        // Process children - preserve settings at end, only sort submenus internally
        foreach ($menus as &$menu) {
            if (!empty($menu['children'])) {
                // Separate settings from other children
                $regularChildren = [];
                $settingsChildren = [];

                foreach ($menu['children'] as &$child) {
                    // Sort nested submenu children (e.g., report items inside Reports submenu)
                    if (!empty($child['children']) && ($child['is_submenu'] ?? false)) {
                        usort($child['children'], fn($a, $b) => ($a['order'] ?? 999) <=> ($b['order'] ?? 999));
                    }

                    // Separate settings from regular items
                    if ($child['is_settings'] ?? false) {
                        $settingsChildren[] = $child;
                    } else {
                        $regularChildren[] = $child;
                    }
                }

                // Sort regular children by order
                usort($regularChildren, fn($a, $b) => ($a['order'] ?? 999) <=> ($b['order'] ?? 999));

                // Sort settings children by order (among themselves)
                usort($settingsChildren, fn($a, $b) => ($a['order'] ?? 999) <=> ($b['order'] ?? 999));

                // Merge: regular items first, then settings at the end
                $menu['children'] = array_merge($regularChildren, $settingsChildren);
            }
        }

        return $menus;
    }

    /**
     * Get a hash of all manifest file modification times.
     * This automatically invalidates cache when builds change manifest files.
     *
     * @return string
     */
    protected function getManifestHash(): string
    {
        $modulesPath = base_path('Modules');
        $timestamps = [];

        if (File::exists($modulesPath)) {
            $modules = File::directories($modulesPath);

            foreach ($modules as $modulePath) {
                $moduleName = strtolower(basename($modulePath));
                $manifestPath = public_path("build-{$moduleName}/.vite/manifest.json");

                if (File::exists($manifestPath)) {
                    $timestamps[] = File::lastModified($manifestPath);
                }
            }
        }

        // Return a hash of all timestamps, or 'none' if no manifests exist
        return empty($timestamps) ? 'none' : md5(implode('-', $timestamps));
    }

    /**
     * Clear menu cache.
     *
     * @return void
     */
    public function clearCache(): void
    {
        Cache::forget('module_menus_guest');
        // Clear for all users (in production, implement more sophisticated cache tagging)
        // For now, just flush cache keys matching pattern
        // Cache::tags(['module_menus'])->flush();
    }
}
