<?php

declare(strict_types=1);

namespace App\Http\Controllers\Install;

use App\Http\Controllers\Controller;
use App\Services\Install\EnvChecker;
use App\Services\Install\EnvPatcher;
use App\Services\Install\SystemDetector;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

/**
 * EnvPatchController — 환경 자동 패치 (확장 설치 / 권한 수정 / 재검사).
 *
 * 라우트는 install.php 의 'installer.only' + 'throttle:installer' 미들웨어 그룹 아래에 있으므로:
 *   - install.lock 존재 → 403
 *   - 10/min/IP rate limit
 *
 * 모든 변경 작업은 install.lock 부재 (= 설치 진행 중) 일 때만 가능.
 */
final class EnvPatchController extends Controller
{
    public function __construct(
        private readonly SystemDetector $detector,
        private readonly EnvPatcher $patcher,
        private readonly EnvChecker $checker,
    ) {
    }

    /**
     * GET /install/env/detect — OS / PM / PHP 빌드 감지 결과 + EnvChecker 누락 항목.
     */
    public function detect(Request $request): JsonResponse
    {
        $env = $this->detector->detect();
        $check = $this->checker->run();
        $missing = $this->extractMissing($check['checks']);

        $buildNotes = (array) config('install-patches.build_notes', []);
        $note = $buildNotes[$env['php_build']] ?? null;
        if (is_string($note)) {
            $note = str_replace('{V}', (string) $env['php_major_minor'], $note);
        }

        return response()->json([
            'env'        => $env,
            'missing'    => $missing,
            'build_note' => $note,
        ]);
    }

    /**
     * POST /install/env/install-commands — 명령 텍스트 생성 (복사용).
     *
     * 본문: { items: [{ type: 'ext'|'dir', name: 'redis' }] }
     */
    public function installCommands(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'items'        => 'required|array|min:1|max:50',
            'items.*.type' => 'required|string|in:ext,dir',
            'items.*.name' => 'required|string|max:64|regex:/^[A-Za-z0-9_\-\/\.]+$/',
        ]);
        $commands = $this->patcher->getInstallCommands($validated['items']);
        return response()->json([
            'commands' => $commands,
        ]);
    }

    /**
     * POST /install/env/install — 자동 설치 실행.
     *
     * 본문: { extension: 'redis', dry_run?: bool, sudo_password?: string }
     */
    public function install(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'extension'     => 'required|string|max:32|regex:/^[A-Za-z0-9_\-]+$/',
            'dry_run'       => 'sometimes|boolean',
            'sudo_password' => 'sometimes|string|max:256',
        ]);

        $result = $this->patcher->executeInstall(
            extension: $validated['extension'],
            dryRun: (bool) ($validated['dry_run'] ?? false),
            sudoPassword: $validated['sudo_password'] ?? null,
        );

        // 즉시 폐기 (방어적)
        if (isset($validated['sudo_password'])) {
            $validated['sudo_password'] = str_repeat("\0", strlen((string) $validated['sudo_password']));
            unset($validated['sudo_password']);
        }

        $status = ($result['ok'] ?? false) ? 200 : 422;
        return response()->json($result, $status);
    }

    /**
     * POST /install/env/fix-permissions — 디렉토리 권한 일괄 수정.
     *
     * 본문: { dry_run?: bool }
     */
    public function fixPermissions(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'dry_run' => 'sometimes|boolean',
        ]);
        $result = $this->patcher->fixPermissions(dryRun: (bool) ($validated['dry_run'] ?? false));
        $status = ($result['ok'] ?? false) ? 200 : 422;
        return response()->json($result, $status);
    }

    /**
     * POST /install/env/recheck — EnvChecker 재실행 (FPM reload 이후 검증용).
     */
    public function recheck(Request $request): JsonResponse
    {
        $report = $this->checker->run();
        return response()->json([
            'stage'  => 1,
            'passed' => $report['passed'],
            'checks' => $report['checks'],
        ]);
    }

    /**
     * EnvChecker 의 fail/warn 항목 중 패치 가능한 것만 추출.
     *
     * @param  list<array{key:string,label:string,status:string,detail:string,required:bool}>  $checks
     * @return list<array{type:string,name:string,label:string,status:string,required:bool}>
     */
    private function extractMissing(array $checks): array
    {
        $out = [];
        foreach ($checks as $c) {
            if ($c['status'] === 'pass') {
                continue;
            }
            if (str_starts_with($c['key'], 'ext_')) {
                $out[] = [
                    'type'     => 'ext',
                    'name'     => substr($c['key'], 4),
                    'label'    => $c['label'],
                    'status'   => $c['status'],
                    'required' => $c['required'],
                ];
            } elseif (str_starts_with($c['key'], 'writable_')) {
                $out[] = [
                    'type'     => 'dir',
                    'name'     => substr($c['key'], 9),
                    'label'    => $c['label'],
                    'status'   => $c['status'],
                    'required' => $c['required'],
                ];
            }
        }
        return $out;
    }
}
