<?php

namespace App\Services;

use App\Enums\AppOperation;
use App\Enums\AppStatus;
use App\Enums\Status;
use App\Http\Responses\ApiResponse;
use App\Models\AdminMenu;
use App\Models\AdminPermission;
use App\Models\AppModel;
use App\Models\AppOperationLog;
use App\Models\ConfigGroup;
use App\Models\ConfigItem;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class AppManagerService
{
    protected HookManager $hookManager;

    protected AppMarketService $marketService;

    public function __construct(HookManager $hookManager, AppMarketService $marketService)
    {
        $this->hookManager = $hookManager;
        $this->marketService = $marketService;
    }

    public function getInstalledApps(): array
    {
        $apps = AppModel::installed()
            ->orderBy('id', 'asc')
            ->get()
            ->map(function ($app) {
                $appArray = $app->toArray();
                if (empty($appArray['author'])) {
                    $manifest = $app->manifest ?? [];
                    $appArray['author'] = $manifest['author'] ?? null;
                }
                if (empty($appArray['icon'])) {
                    $remoteIcon = $this->fetchRemoteIcon($app->app_id);
                    if ($remoteIcon) {
                        $appArray['icon'] = $remoteIcon;
                        $app->update(['icon' => $remoteIcon]);
                    }
                }
                $appArray['icon_url'] = url('/api/app/' . $app->app_id . '/icon');

                // 读取更新检查缓存（不发起 HTTP，仅判断是否有缓存的更新信息）
                $updateCacheKey = "market_app_update_{$app->app_id}_{$app->version}";
                $cachedUpdate = Cache::get($updateCacheKey);
                $appArray['has_update'] = !empty($cachedUpdate);

                return $appArray;
            })
            ->toArray();

        return ApiResponse::success($apps);
    }

    public function getAllAvailableApps(): array
    {
        $installedApps = AppModel::installed()->pluck('app_id')->toArray();
        $appsPath = app_path('Apps');
        $availableApps = [];
        $uninstalledApps = [];

        if (is_dir($appsPath)) {
            $dirs = scandir($appsPath);
            foreach ($dirs as $dir) {
                if ($dir === '.' || $dir === '..') {
                    continue;
                }

                $manifestPath = $appsPath . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'manifest.json';
                if (! file_exists($manifestPath)) {
                    continue;
                }

                $manifestContent = file_get_contents($manifestPath);
                $manifest = json_decode($manifestContent, true);

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

                $appId = $manifest['id'];
                $isInstalled = in_array($appId, $installedApps);

                $appData = [
                    'app_id' => $appId,
                    'name' => $manifest['name'] ?? $appId,
                    'description' => $manifest['description'] ?? '',
                    'version' => $manifest['version'] ?? '0.0.1',
                    'author' => $manifest['author'] ?? '',
                    'icon' => $manifest['icon'] ?? null,
                    'icon_url' => url('/api/app/' . $appId . '/icon'),
                    'is_installed' => $isInstalled,
                ];

                $availableApps[] = $appData;

                if (! $isInstalled) {
                    $uninstalledApps[] = $appData;
                }
            }
        }

        return ApiResponse::success([
            'total' => count($availableApps),
            'installed' => count($installedApps),
            'uninstalled' => count($uninstalledApps),
            'all' => $availableApps,
            'uninstalled_apps' => $uninstalledApps,
        ]);
    }

    protected function fetchRemoteIcon(string $appId): ?string
    {
        try {
            $result = $this->marketService->getAppDetail($appId);
            if ($result['code'] === 0 && ! empty($result['data']['icon'])) {
                return $result['data']['icon'];
            }
        } catch (\Throwable $e) {
        }

        return null;
    }

    public function getApp(string $appId): array
    {
        $app = AppModel::where('app_id', $appId)->first();

        if (! $app) {
            return ApiResponse::error(50006, '应用未安装');
        }

        $appArray = $app->toArray();
        if (empty($appArray['author'])) {
            $manifest = $app->manifest ?? [];
            $appArray['author'] = $manifest['author'] ?? null;
        }

        return ApiResponse::success($appArray);
    }

    public function enableApp(string $appId): array
    {
        $app = AppModel::where('app_id', $appId)->first();

        if (! $app) {
            return ApiResponse::error(50006, '应用未安装');
        }

        if ($app->status === AppStatus::ENABLED) {
            return ApiResponse::error(50007, '应用已处于启用状态');
        }

        if (! in_array($app->status, [AppStatus::INSTALLED, AppStatus::DISABLED])) {
            return ApiResponse::error(50007, '当前状态不允许启用');
        }

        // 检测前台路由冲突
        $routeConflictCheck = $this->checkHomeRoutesConflict($app);
        if ($routeConflictCheck !== null) {
            return $routeConflictCheck;
        }

        try {
            DB::beginTransaction();

            $manifest = $app->manifest ?? [];

            $this->enablePermissions($appId, $manifest);

            $this->enableMenus($appId, $manifest);

            $this->enableConfigGroups($appId, $manifest);

            $app->update(['status' => AppStatus::ENABLED]);

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            return ApiResponse::error(50001, '启用失败：'.$e->getMessage());
        }

        $this->registerAppHooks($app);

        AppOperationLog::create([
            'app_id' => $appId,
            'operation' => AppOperation::ENABLE->value,
            'version_from' => $app->version,
            'version_to' => $app->version,
            'operator_id' => Auth::guard('admin')->id(),
            'result' => 1,
        ]);

        $this->hookManager->doAction('app.enabled', $appId);

        // 清除路由缓存，使新启用的应用路由在下一次请求时注册
        $this->clearRouteCache();

        return ApiResponse::success($app, '应用已启用');
    }

    public function disableApp(string $appId): array
    {
        $app = AppModel::where('app_id', $appId)->first();

        if (! $app) {
            return ApiResponse::error(50006, '应用未安装');
        }

        if ($app->status !== AppStatus::ENABLED) {
            return ApiResponse::error(50007, '当前状态不允许禁用');
        }

        try {
            DB::beginTransaction();

            $this->disablePermissions($appId);

            $this->disableMenus($appId);

            $this->disableConfigGroups($appId);

            $app->update(['status' => AppStatus::DISABLED]);

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            return ApiResponse::error(50001, '禁用失败：'.$e->getMessage());
        }

        $this->hookManager->removeHooksByApp($appId);

        AppOperationLog::create([
            'app_id' => $appId,
            'operation' => AppOperation::DISABLE->value,
            'version_from' => $app->version,
            'version_to' => $app->version,
            'operator_id' => Auth::guard('admin')->id(),
            'result' => 1,
        ]);

        $this->hookManager->doAction('app.disabled', $appId);

        // 清除路由缓存，移除已禁用应用的路由
        $this->clearRouteCache();

        return ApiResponse::success($app, '应用已禁用');
    }

    public function getAppConfig(string $appId): array
    {
        $app = AppModel::where('app_id', $appId)->first();

        if (! $app) {
            return ApiResponse::error(50006, '应用未安装');
        }

        if ($app->status !== AppStatus::ENABLED) {
            return ApiResponse::error(50007, '应用未启用，无法获取配置');
        }

        $manifest = $app->manifest ?? [];
        $configGroups = $manifest['config_groups'] ?? [];
        $result = [];

        foreach ($configGroups as $groupData) {
            $groupCode = "app_".str_replace('.', '_', $appId)."_".($groupData['name'] ?? $groupData['title']);
            $group = ConfigGroup::where('code', $groupCode)->where('app_id', $appId)->where('status', Status::ENABLED)->first();

            if ($group) {
                $items = ConfigItem::where('group_id', $group->id)->where('status', Status::ENABLED)->orderBy('sort', 'asc')->get();
                $result[] = [
                    'title' => $groupData['title'],
                    'name' => $groupData['name'] ?? $groupData['title'],
                    'items' => $items->map(fn ($item) => [
                        'id' => $item->id,
                        'name' => $item->name,
                        'code' => $item->code,
                        'value' => $item->value,
                        'type' => $item->type,
                        'options' => $item->options,
                        'tips' => $item->tips,
                    ])->toArray(),
                ];
            }
        }

        return ApiResponse::success($result);
    }

    public function saveAppConfig(string $appId, array $config): array
    {
        $app = AppModel::where('app_id', $appId)->first();

        if (! $app) {
            return ApiResponse::error(50006, '应用未安装');
        }

        if ($app->status !== AppStatus::ENABLED) {
            return ApiResponse::error(50007, '应用未启用，无法保存配置');
        }

        $appGroupIds = ConfigGroup::where('app_id', $appId)->where('status', Status::ENABLED)->pluck('id');

        DB::beginTransaction();
        try {
            foreach ($config as $code => $value) {
                ConfigItem::where('code', $code)
                    ->whereIn('group_id', $appGroupIds)
                    ->update(['value' => $value]);
            }
            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            return ApiResponse::error(50001, '配置保存失败：'.$e->getMessage());
        }

        return ApiResponse::success(null, '配置保存成功');
    }

    public function checkUpdates(bool $lazy = false): array
    {
        $checkKey = 'app_update_last_check';
        $resultKey = 'app_update_last_result';

        // 懒加载模式：24h 内直接返回缓存结果，避免频繁请求市场
        if ($lazy && Cache::has($checkKey)) {
            $cached = Cache::get($resultKey);
            if ($cached !== null) {
                return $cached;
            }
        }

        $apps = AppModel::installed()->get();
        $marketService = app(AppMarketService::class);
        $updates = [];

        foreach ($apps as $app) {
            $updateInfo = $marketService->checkAppUpdate($app->app_id, $app->version);
            if ($updateInfo) {
                $updates[] = [
                    'app_id' => $app->app_id,
                    'name' => $app->name,
                    'current_version' => $app->version,
                    'latest_version' => $updateInfo['version'],
                    'next_version' => $updateInfo['next_version'] ?? $updateInfo['version'],
                    'changelog' => $updateInfo['changelog'] ?? null,
                    'available_versions' => $updateInfo['available_versions'] ?? [
                        ['version' => $updateInfo['version'], 'changelog' => $updateInfo['changelog'] ?? null],
                    ],
                ];
            }
        }

        $result = ApiResponse::success($updates);

        // 刷新懒加载节流缓存（24h）和结果缓存
        Cache::put($checkKey, now(), 86400);
        Cache::put($resultKey, $result, 86400);

        return $result;
    }

    public function getOperationLogs(array $params = []): array
    {
        $query = AppOperationLog::with('operator')
            ->orderBy('create_time', 'desc');

        if (! empty($params['app_id'])) {
            $query->where('app_id', $params['app_id']);
        }

        if (! empty($params['operation'])) {
            $query->where('operation', $params['operation']);
        }

        $perPage = $params['per_page'] ?? 15;
        $paginator = $query->paginate($perPage);

        return ApiResponse::paginate($paginator);
    }

    protected function registerAppHooks(AppModel $app): void
    {
        $manifest = $app->manifest ?? [];
        $hooksClass = $manifest['hooks'] ?? null;

        if (! $hooksClass) {
            return;
        }

        $fullClass = 'App\\Apps\\'.app_id_to_class_name($app->app_id)."\\{$hooksClass}";

        if (class_exists($fullClass)) {
            $instance = new $fullClass;
            if (method_exists($instance, 'register')) {
                $instance->register($this->hookManager);
            }
        }
    }

    protected function enablePermissions(string $appId, array $manifest): void
    {
        $disabledPerms = AdminPermission::where('app_id', $appId)
            ->where('status', Status::DISABLED)
            ->get();

        foreach ($disabledPerms as $perm) {
            $perm->update(['status' => Status::ENABLED]);
        }

        $existingCodes = AdminPermission::where('app_id', $appId)
            ->pluck('code')
            ->toArray();

        $permissions = $manifest['permissions'] ?? [];
        foreach ($permissions as $perm) {
            if (is_array($perm)) {
                $code = $perm['code'];
                $name = $perm['name'] ?? $code;
            } else {
                $code = $perm;
                $name = $perm;
            }

            if (! in_array($code, $existingCodes)) {
                AdminPermission::create([
                    'name' => $name,
                    'code' => $code,
                    'module' => $appId,
                    'app_id' => $appId,
                    'status' => Status::ENABLED,
                ]);
            }
        }
    }

    protected function disablePermissions(string $appId): void
    {
        AdminPermission::where('app_id', $appId)
            ->where('status', Status::ENABLED)
            ->update(['status' => Status::DISABLED]);
    }

    protected function enableMenus(string $appId, array $manifest): void
    {
        AdminMenu::where('app_id', $appId)
            ->where('status', Status::DISABLED)
            ->update(['status' => Status::ENABLED]);

        $hasMenus = AdminMenu::where('app_id', $appId)->exists();
        if (! $hasMenus) {
            $this->registerMenus($manifest);
        }
    }

    protected function disableMenus(string $appId): void
    {
        AdminMenu::where('app_id', $appId)
            ->where('status', Status::ENABLED)
            ->update(['status' => Status::DISABLED]);
    }

    protected function enableConfigGroups(string $appId, array $manifest): void
    {
        ConfigGroup::where('app_id', $appId)
            ->where('status', Status::DISABLED)
            ->update(['status' => Status::ENABLED]);

        $configGroupIds = ConfigGroup::where('app_id', $appId)->pluck('id');
        if ($configGroupIds->isNotEmpty()) {
            ConfigItem::whereIn('group_id', $configGroupIds)
                ->where('status', Status::DISABLED)
                ->update(['status' => Status::ENABLED]);
        }

        $hasConfig = ConfigGroup::where('app_id', $appId)->exists();
        if (! $hasConfig) {
            $this->registerConfigGroups($manifest);
        }
    }

    protected function disableConfigGroups(string $appId): void
    {
        ConfigGroup::where('app_id', $appId)
            ->where('status', Status::ENABLED)
            ->update(['status' => Status::DISABLED]);

        $configGroupIds = ConfigGroup::where('app_id', $appId)->pluck('id');
        if ($configGroupIds->isNotEmpty()) {
            ConfigItem::whereIn('group_id', $configGroupIds)
                ->where('status', Status::ENABLED)
                ->update(['status' => Status::DISABLED]);
        }
    }

    protected function registerMenus(array $manifest): void
    {
        $appId = $manifest['id'];
        $menus = $manifest['menus'] ?? [];

        foreach ($menus as $menuData) {
            $parentId = $this->resolveParentMenu($menuData, $appId);

            if ($parentId && isset($menuData['children'])) {
                foreach ($menuData['children'] as $child) {
                    $this->createMenuRecord($child, $parentId, $appId, 'admin');
                }
            } else {
                $this->createMenuRecord($menuData, $parentId, $appId, 'admin');
            }
        }

        $userMenus = $manifest['user_menus'] ?? [];
        foreach ($userMenus as $menuData) {
            $parentId = $this->resolveParentMenu($menuData, $appId);

            if ($parentId && isset($menuData['children'])) {
                foreach ($menuData['children'] as $child) {
                    $this->createMenuRecord($child, $parentId, $appId, 'user');
                }
            } else {
                $this->createMenuRecord($menuData, $parentId, $appId, 'user');
            }
        }

        $homeMenus = $manifest['home_menus'] ?? [];
        foreach ($homeMenus as $menuData) {
            $this->createMenuRecord($menuData, null, $appId, 'home');
        }
    }

    protected function resolveParentMenu(array $menuData, string $appId): ?int
    {
        if (! isset($menuData['parent'])) {
            return null;
        }

        $parent = $menuData['parent'];

        if (is_numeric($parent)) {
            $menu = AdminMenu::find((int) $parent);

            return $menu?->id;
        }

        if (str_starts_with($parent, '/')) {
            $menu = AdminMenu::where('path', $parent)->first();
            if ($menu) {
                return $menu->id;
            }
        }

        $menu = AdminMenu::where('name', $parent)->first();

        return $menu?->id;
    }

    protected function createMenuRecord(array $data, ?int $parentId, string $appId, string $terminalType = 'admin'): void
    {
        $path = $data['path'] ?? '';
        $existingMenu = AdminMenu::where('app_id', $appId)
            ->where('path', $path)
            ->where('parent_id', $parentId ?? 0)
            ->where('terminal_type', $terminalType)
            ->first();

        if ($existingMenu) {
            $menu = $existingMenu;
            $menu->update([
                'name' => $data['title'],
                'icon' => $data['icon'] ?? $menu->icon,
                'sort' => $data['order'] ?? $menu->sort,
                'status' => Status::ENABLED,
            ]);
        } else {
            $menu = AdminMenu::create([
                'parent_id' => $parentId ?? 0,
                'name' => $data['title'],
                'icon' => $data['icon'] ?? '',
                'path' => $path,
                'type' => isset($data['children']) ? 1 : 2,
                'visible' => 1,
                'sort' => $data['order'] ?? 0,
                'status' => Status::ENABLED,
                'terminal_type' => $terminalType,
                'app_id' => $appId,
            ]);
        }

        if (isset($data['children'])) {
            foreach ($data['children'] as $child) {
                $this->createMenuRecord($child, $menu->id, $appId, $terminalType);
            }
        }
    }

    protected function registerConfigGroups(array $manifest): void
    {
        $appId = $manifest['id'];
        $groups = $manifest['config_groups'] ?? [];

        foreach ($groups as $groupData) {
            $groupCode = "app_".str_replace('.', '_', $appId)."_".($groupData['name'] ?? $groupData['title']);

            $group = ConfigGroup::updateOrCreate(
                ['code' => $groupCode],
                [
                    'name' => $groupData['title'],
                    'app_id' => $appId,
                    'sort' => 0,
                    'status' => Status::ENABLED,
                ]
            );

            $items = $groupData['items'] ?? [];
            foreach ($items as $index => $itemData) {
                $itemCode = "app_".str_replace('.', '_', $appId)."_".$itemData['name'];

                $existing = ConfigItem::where('code', $itemCode)
                    ->where('group_id', $group->id)
                    ->first();

                if ($existing) {
                    $existing->update([
                        'name' => $itemData['title'],
                        'type' => $itemData['type'] ?? 'text',
                        'options' => $itemData['options'] ?? null,
                        'tips' => $itemData['tips'] ?? null,
                        'sort' => $index,
                        'status' => Status::ENABLED,
                    ]);
                } else {
                    ConfigItem::create([
                        'group_id' => $group->id,
                        'name' => $itemData['title'],
                        'code' => $itemCode,
                        'value' => $itemData['value'] ?? '',
                        'type' => $itemData['type'] ?? 'text',
                        'options' => $itemData['options'] ?? null,
                        'tips' => $itemData['tips'] ?? null,
                        'sort' => $index,
                        'status' => Status::ENABLED,
                    ]);
                }
            }
        }
    }

    /**
     * 检测前台路由冲突。
     *
     * 遍历所有已启用的应用，读取其 manifest 中的 home_routes 字段，
     * 与待启用应用的 home_routes 比对，发现相同路径时返回冲突详情。
     */
    protected function checkHomeRoutesConflict(AppModel $app): ?array
    {
        $manifest = $app->manifest ?? [];
        $newRoutes = $manifest['home_routes'] ?? [];
        if (empty($newRoutes)) {
            return null;
        }

        $conflicts = [];
        $appId = $app->app_id;

        // 仅检查已启用的应用（未启用的应用路由不会注册，不会冲突）
        $enabledApps = AppModel::where('status', AppStatus::ENABLED)
            ->where('app_id', '!=', $appId)
            ->get();

        foreach ($enabledApps as $otherApp) {
            $otherManifest = $otherApp->manifest ?? [];
            $otherRoutes = $otherManifest['home_routes'] ?? [];
            if (empty($otherRoutes)) {
                continue;
            }

            foreach ($newRoutes as $path => $title) {
                if (isset($otherRoutes[$path])) {
                    $conflicts[] = [
                        'path' => $path,
                        'current_app' => $otherManifest['name'] ?? $otherApp->app_id,
                        'current_title' => $otherRoutes[$path],
                        'incoming_app' => $manifest['name'] ?? $appId,
                        'incoming_title' => $title,
                    ];
                }
            }
        }

        if (empty($conflicts)) {
            return null;
        }

        // 构建冲突提示信息
        $lines = [];
        foreach ($conflicts as $i => $c) {
            $lines[] = ($i + 1) . ". 路由 {$c['path']}：已被「{$c['current_app']}」占用（{$c['current_title']}），与「{$c['incoming_app']}」冲突（{$c['incoming_title']}）";
        }
        $message = "检测到前台路由冲突：\n" . implode("\n", $lines) . "\n请先禁用冲突应用后再启用本应用。";

        return ApiResponse::error(50019, $message, ['conflicts' => $conflicts]);
    }

    /**
     * 清除路由缓存。
     *
     * 应用启用/禁用后，路由缓存（routes-v7.php）中可能包含过时或缺失的路由，
     * 必须清除以使 booted 回调中的动态路由注册生效。
     */
    protected function clearRouteCache(): void
    {
        @\unlink(base_path('bootstrap/cache/routes-v7.php'));
    }
}
