<?php

namespace App\Services\Module;

use App\Contracts\InstalledModuleRepositoryContract;
use App\Contracts\ModuleServiceProviderContract;
use App\Enums\ModuleStatus;
use App\Services\Permission\PermissionRegistry;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use RuntimeException;

class ModuleManager
{
    /** @var Collection<int, ModuleDefinition> */
    private Collection $modules;

    /** @var list<ModuleServiceProviderContract> */
    private array $booted = [];

    /** @var list<string> */
    private array $bootedSlugs = [];

    public function __construct(
        private readonly PermissionRegistry $permissionRegistry,
        private readonly InstalledModuleRepositoryContract $installedModules,
    ) {
        $this->modules = collect();
    }

    public function discover(?string $basePath = null): Collection
    {
        $basePath ??= base_path('modules');
        $this->modules = collect();

        if (! is_dir($basePath)) {
            return $this->modules;
        }

        foreach (File::directories($basePath) as $directory) {
            $manifestPath = $directory.'/module.json';

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

            $manifest = json_decode(File::get($manifestPath), true);

            if (! is_array($manifest) || empty($manifest['provider'])) {
                continue;
            }

            $slug = is_string($manifest['slug'] ?? null)
                ? $manifest['slug']
                : \Illuminate\Support\Str::slug(basename($directory));
            $this->modules->push(new ModuleDefinition($slug, $directory, $manifest));
        }

        return $this->modules;
    }

    public function bootAll(): void
    {
        $this->bootInstalled();
    }

    public function bootInstalled(): void
    {
        $installedUuids = $this->installedModules->all()
            ->where('status', ModuleStatus::Installed)
            ->pluck('module_uuid')
            ->all();

        foreach ($this->discover() as $definition) {
            $uuid = $definition->uuid();

            if ($uuid && in_array($uuid, $installedUuids, true)) {
                $this->boot($definition);
            }
        }
    }

    public function findByUuid(string $uuid): ?ModuleDefinition
    {
        return $this->discover()->first(fn (ModuleDefinition $definition) => $definition->uuid() === $uuid);
    }

    /**
     * @return list<string>
     */
    public function installedUuids(): array
    {
        return $this->installedModules->all()
            ->where('status', ModuleStatus::Installed)
            ->pluck('module_uuid')
            ->all();
    }

    public function boot(ModuleDefinition $definition): void
    {
        if (in_array($definition->slug, $this->bootedSlugs, true)) {
            return;
        }

        $providerClass = $definition->providerClass();

        if (! class_exists($providerClass)) {
            throw new RuntimeException("Module provider [{$providerClass}] not found.");
        }

        $provider = new $providerClass(app());

        if (! $provider instanceof ModuleServiceProviderContract) {
            throw new RuntimeException("Module provider [{$providerClass}] must implement ModuleServiceProviderContract.");
        }

        $provider->bootModule();
        $this->permissionRegistry->registerModule($definition->name(), $definition->permissions());
        $this->booted[] = $provider;
        $this->bootedSlugs[] = $definition->slug;
    }

    /**
     * @return list<array{route: string, icon: string, label: string}>
     */
    public function navigationItems(): array
    {
        return $this->navigationItemsForRequest(request());
    }

    /**
     * @return list<array{route: string, icon: string, label: string}>
     */
    public function navigationItemsForRequest(?\Illuminate\Http\Request $request = null): array
    {
        $request ??= request();
        $items = [];

        foreach ($this->booted as $provider) {
            $slug = $provider->getSlug();

            if (! $request->routeIs($slug) && ! $request->routeIs($slug.'.*')) {
                continue;
            }

            foreach ($provider->getNavigationItems() as $item) {
                if (! \Illuminate\Support\Facades\Route::has($item['route'])) {
                    continue;
                }

                $permission = $item['permission'] ?? null;

                if ($permission && auth()->check() && ! auth()->user()->can($permission)) {
                    continue;
                }

                $items[] = $item;
            }
        }

        return $items;
    }

    public function resolveEntryRouteName(string $moduleUuid): ?string
    {
        $definition = $this->findByUuid($moduleUuid);

        if (! $definition) {
            return null;
        }

        $slug = $definition->slug;
        $dashboardRoute = $slug.'.dashboard';

        if (\Illuminate\Support\Facades\Route::has($dashboardRoute)) {
            return $dashboardRoute;
        }

        if (! in_array($slug, $this->bootedSlugs, true)) {
            try {
                $this->boot($definition);
            } catch (\Throwable) {
                return null;
            }
        }

        if (\Illuminate\Support\Facades\Route::has($dashboardRoute)) {
            return $dashboardRoute;
        }

        foreach ($this->booted as $provider) {
            if ($provider->getSlug() !== $slug) {
                continue;
            }

            foreach ($provider->getNavigationItems() as $item) {
                if (\Illuminate\Support\Facades\Route::has($item['route'])) {
                    return $item['route'];
                }
            }
        }

        return null;
    }

    /**
     * @return list<array{name: string, description: string, route: string, icon: string}>
     */
    public function accessibleModulesForUser(?\App\Models\User $user = null): array
    {
        $user ??= auth()->user();

        if (! $user) {
            return [];
        }

        $items = [];

        foreach ($this->discover() as $definition) {
            $uuid = $definition->uuid();

            if (! $uuid || ! in_array($uuid, $this->installedUuids(), true)) {
                continue;
            }

            $permissionNames = array_keys($definition->permissions());

            if ($permissionNames !== [] && ! $user->hasAnyPermission(...$permissionNames)) {
                continue;
            }

            $entryRoute = $this->resolveEntryRouteName($uuid);

            if ($entryRoute === null) {
                continue;
            }

            $items[] = [
                'name' => $definition->name(),
                'description' => (string) ($definition->manifest['description'] ?? ''),
                'route' => $entryRoute,
                'icon' => (string) ($definition->manifest['icon'] ?? 'bi bi-box-seam'),
            ];
        }

        return $items;
    }

    /**
     * @return Collection<int, ModuleDefinition>
     */
    public function all(): Collection
    {
        return $this->modules->isEmpty() ? $this->discover() : $this->modules;
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function localMarketplaceCatalog(): array
    {
        return $this->discover()
            ->filter(fn (ModuleDefinition $definition) => $definition->uuid() !== null)
            ->map(fn (ModuleDefinition $definition) => $this->localMarketplaceCatalogEntry($definition))
            ->values()
            ->all();
    }

    /**
     * @return array<string, mixed>
     */
    public function localMarketplaceCatalogEntry(ModuleDefinition $definition): array
    {
        return [
            'uuid' => $definition->uuid(),
            'name' => $definition->name(),
            'slug' => $definition->slug,
            'description' => (string) ($definition->manifest['description'] ?? ''),
            'specs' => (string) ($definition->manifest['specs'] ?? ''),
            'category' => (string) ($definition->manifest['category'] ?? $definition->manifest['category_name'] ?? ''),
            'pricing_type' => (string) ($definition->manifest['pricing_type'] ?? 'one_time'),
            'price' => (float) ($definition->manifest['price'] ?? 0),
            'currency' => (string) ($definition->manifest['currency'] ?? config('platform.currency', 'INR')),
            'version' => $definition->version(),
        ];
    }
}
