<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Services\InstallerService;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

/**
 * Browser-only install wizard for shared hosting with no SSH/Composer/artisan
 * CLI. GET /install shows preflight + the setup form; POST /install does the
 * whole job in one request (test DB, write .env, migrate, seed, lock) and
 * renders the completion screen directly (no redirect) so the one-time
 * device tokens are never round-tripped through a session/cookie.
 */
class InstallController extends Controller
{
    public function index(InstallerService $installer)
    {
        if ($installer->isInstalled()) {
            return view('install.already-installed', ['lock' => $installer->lockData()]);
        }

        $preflight = $installer->preflight();

        return view('install.form', [
            'preflight' => $preflight['rows'],
            'preflightOk' => $preflight['ok'],
            'defaultAppUrl' => request()->getSchemeAndHttpHost(),
        ]);
    }

    /**
     * AJAX endpoint backing the wizard's "Test connection" button on the
     * Database step. Read-only (opens a PDO connection and lets it go out of
     * scope — never writes/migrates), gated pre-install the same way
     * store() is, and always returns JSON regardless of outcome.
     */
    public function testDb(Request $request, InstallerService $installer)
    {
        if ($installer->isInstalled()) {
            return response()->json(['ok' => false, 'message' => 'Already installed.'], 409);
        }

        $validator = validator($request->all(), [
            'db_host' => ['required', 'string', 'max:255'],
            'db_port' => ['required', 'numeric'],
            'db_database' => ['required', 'string', 'max:255'],
            'db_username' => ['required', 'string', 'max:255'],
            'db_password' => ['nullable', 'string', 'max:255'],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'ok' => false,
                'message' => collect($validator->errors()->all())->first() ?? 'Please fill in all required database fields.',
            ], 422);
        }

        $data = $validator->validated();

        $result = $installer->testDatabaseConnection(
            $data['db_host'],
            (string) $data['db_port'],
            $data['db_database'],
            $data['db_username'],
            $data['db_password'] ?? null
        );

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

    public function store(Request $request, InstallerService $installer)
    {
        // Idempotency / safety: never re-run install or regenerate tokens once locked.
        if ($installer->isInstalled()) {
            return view('install.already-installed', ['lock' => $installer->lockData()]);
        }

        $preflight = $installer->preflight();
        if (! $preflight['ok']) {
            return back()
                ->withErrors(['preflight' => 'One or more environment checks are still failing (see the check list) — fix those before installing.'])
                ->withInput();
        }

        $data = $request->validate([
            'app_name' => ['nullable', 'string', 'max:255'],
            'app_url' => ['required', 'string', 'max:255'],
            'db_host' => ['required', 'string', 'max:255'],
            'db_port' => ['required', 'numeric'],
            'db_database' => ['required', 'string', 'max:255'],
            'db_username' => ['required', 'string', 'max:255'],
            'db_password' => ['nullable', 'string', 'max:255'],
            'admin_name' => ['required', 'string', 'max:255'],
            'admin_email' => ['required', 'email', 'max:255'],
            'admin_password' => ['required', 'string', 'min:8', 'confirmed'],
            'site_count' => ['required', 'integer', 'min:1', 'max:20'],
        ]);

        $safeOldInput = $request->except(['db_password', 'admin_password', 'admin_password_confirmation']);

        // Step (c): test the DB connection before touching anything on disk.
        $dbTest = $installer->testDatabaseConnection(
            $data['db_host'],
            (string) $data['db_port'],
            $data['db_database'],
            $data['db_username'],
            $data['db_password'] ?? null
        );

        if (! $dbTest['ok']) {
            return back()->withErrors(['db' => $dbTest['message']])->withInput($safeOldInput);
        }

        // Step (d): write .env. If the app root isn't writable, stop here — do
        // NOT proceed to migrate/seed/lock, otherwise we'd end up "installed"
        // with a broken .env on the next request. Show the content to paste in manually instead.
        $envContents = $installer->buildEnvContents($data);
        $envWritten = $installer->writeEnvFile($envContents);

        if (! $envWritten) {
            return view('install.env-write-failed', [
                'envContents' => $envContents,
                'envPath' => base_path('.env'),
            ]);
        }

        // Step (e): migrate, against the just-submitted DB (applied to the
        // CURRENT request's config so Artisan::call uses it immediately —
        // the .env file on disk will be picked up by every future request).
        $installer->applyRuntimeDatabaseConfig(
            $data['db_host'],
            (string) $data['db_port'],
            $data['db_database'],
            $data['db_username'],
            $data['db_password'] ?? null
        );

        $migration = $installer->runMigrations();

        if (! $migration['ok']) {
            return back()
                ->withErrors(['migrate' => 'Migration failed: '.$migration['output']])
                ->withInput($safeOldInput);
        }

        // Step (f): seed production-essential data only (no sample employees).
        $seed = $installer->seedProductionData(
            (int) $data['site_count'],
            $data['admin_name'],
            $data['admin_email'],
            $data['admin_password']
        );

        // Step (h): write the install lock (also mints the shared secret used
        // by the no-cron task endpoints — see TaskController).
        $tasksToken = Str::random(40);
        $appUrl = rtrim($data['app_url'], '/');
        $installer->writeLock($appUrl, $tasksToken);

        // Step (g): show tokens ONCE. Nothing above ever wrote a plaintext
        // token file to disk — the completion view offers a client-side
        // (data: URI) download so the tokens never live in server storage.
        return view('install.complete', [
            'admin' => $seed['admin'],
            'sites' => $seed['sites'],
            'tasksToken' => $tasksToken,
            'appUrl' => $appUrl,
        ]);
    }
}
