<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use PDO;
use Throwable;

class InstallNoxpayController extends Controller
{
    private string $lockFile;

    public function __construct()
    {
        $this->lockFile = storage_path('app/installnoxpay.lock');
    }

    public function show(Request $request): Response
    {
        return response($this->page($request, $this->isLocked() ? [
            'type' => 'warning',
            'title' => 'Installer sudah dikunci',
            'message' => 'NOXPAY sudah pernah diinstall. Hapus storage/app/installnoxpay.lock lewat File Manager CyberPanel jika benar-benar ingin menjalankan ulang installer.',
        ] : null));
    }

    public function install(Request $request): Response
    {
        if ($this->isLocked()) {
            return response($this->page($request, [
                'type' => 'warning',
                'title' => 'Installer sudah dikunci',
                'message' => 'Install ulang dibatalkan karena lock file sudah ada.',
            ]), 423);
        }

        $data = $this->input($request);
        $errors = $this->validateInput($data);

        if ($errors !== []) {
            return response($this->page($request, [
                'type' => 'error',
                'title' => 'Konfigurasi belum lengkap',
                'message' => implode(' ', $errors),
            ], $data), 422);
        }

        try {
            if ($data['create_database']) {
                $this->createDatabaseIfMissing($data);
            }

            $this->testDatabaseConnection($data);
            $this->writeEnv($data);
            $this->applyRuntimeDatabaseConfig($data);

            Artisan::call('config:clear');
            Artisan::call('cache:clear');

            if ($this->nativeTablesExist()) {
                $this->markNativeMigrationAsRun();
                $migrationOutput = 'Database sudah berisi tabel NOXPAY. Import SQL dilewati dan migration ditandai selesai.';
            } else {
                Artisan::call('migrate', ['--force' => true]);
                $migrationOutput = trim(Artisan::output());
            }

            Artisan::call('storage:link');

            file_put_contents($this->lockFile, 'Installed at '.date('Y-m-d H:i:s').PHP_EOL);

            return response($this->page($request, [
                'type' => 'success',
                'title' => 'Install selesai',
                'message' => 'Laravel sudah dikonfigurasi, APP_KEY dibuat, database dimigrasikan, dan installer dikunci.',
                'details' => $migrationOutput,
            ], $data));
        } catch (Throwable $throwable) {
            return response($this->page($request, [
                'type' => 'error',
                'title' => 'Install gagal',
                'message' => $throwable->getMessage(),
            ], $data), 500);
        }
    }

    private function isLocked(): bool
    {
        return is_file($this->lockFile);
    }

    /**
     * @return array<string, mixed>
     */
    private function input(Request $request): array
    {
        return [
            'app_name' => trim((string) $request->input('app_name', 'NOXPAY')),
            'app_url' => trim((string) $request->input('app_url', $request->getSchemeAndHttpHost())),
            'app_env' => trim((string) $request->input('app_env', 'production')),
            'app_debug' => $request->boolean('app_debug'),
            'db_host' => trim((string) $request->input('db_host', '127.0.0.1')),
            'db_port' => trim((string) $request->input('db_port', '3306')),
            'db_database' => trim((string) $request->input('db_database', 'noxp_asgapi')),
            'db_username' => trim((string) $request->input('db_username', 'noxp_asgapi')),
            'db_password' => (string) $request->input('db_password', ''),
            'create_database' => $request->boolean('create_database'),
        ];
    }

    /**
     * @param array<string, mixed> $data
     * @return list<string>
     */
    private function validateInput(array $data): array
    {
        $errors = [];

        foreach (['app_name', 'app_url', 'app_env', 'db_host', 'db_port', 'db_database', 'db_username'] as $field) {
            if ((string) $data[$field] === '') {
                $errors[] = strtoupper($field).' wajib diisi.';
            }
        }

        if (!ctype_digit((string) $data['db_port'])) {
            $errors[] = 'DB_PORT harus angka.';
        }

        return $errors;
    }

    /**
     * @param array<string, mixed> $data
     */
    private function createDatabaseIfMissing(array $data): void
    {
        $pdo = new PDO(
            sprintf('mysql:host=%s;port=%s;charset=utf8mb4', $data['db_host'], $data['db_port']),
            (string) $data['db_username'],
            (string) $data['db_password'],
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );

        $database = str_replace('`', '``', (string) $data['db_database']);
        $pdo->exec("CREATE DATABASE IF NOT EXISTS `{$database}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
    }

    /**
     * @param array<string, mixed> $data
     */
    private function testDatabaseConnection(array $data): void
    {
        new PDO(
            sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $data['db_host'], $data['db_port'], $data['db_database']),
            (string) $data['db_username'],
            (string) $data['db_password'],
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );
    }

    /**
     * @param array<string, mixed> $data
     */
    private function applyRuntimeDatabaseConfig(array $data): void
    {
        config([
            'database.default' => 'mysql',
            'database.connections.mysql.host' => $data['db_host'],
            'database.connections.mysql.port' => $data['db_port'],
            'database.connections.mysql.database' => $data['db_database'],
            'database.connections.mysql.username' => $data['db_username'],
            'database.connections.mysql.password' => $data['db_password'],
        ]);

        DB::purge('mysql');
        DB::reconnect('mysql');
    }

    private function nativeTablesExist(): bool
    {
        return DB::table('information_schema.tables')
            ->where('table_schema', DB::raw('database()'))
            ->whereIn('table_name', ['tb_agent', 'tb_user', 'tb_settings'])
            ->exists();
    }

    private function markNativeMigrationAsRun(): void
    {
        DB::statement('CREATE TABLE IF NOT EXISTS `migrations` (
            `id` int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
            `migration` varchar(255) NOT NULL,
            `batch` int NOT NULL
        )');

        $migration = '2026_05_10_000000_import_native_noxpay_schema';
        $exists = DB::table('migrations')->where('migration', $migration)->exists();

        if ($exists) {
            return;
        }

        $batch = ((int) DB::table('migrations')->max('batch')) + 1;

        DB::table('migrations')->insert([
            'migration' => $migration,
            'batch' => $batch,
        ]);
    }

    /**
     * @param array<string, mixed> $data
     */
    private function writeEnv(array $data): void
    {
        $envPath = base_path('.env');

        if (!is_file($envPath)) {
            copy(base_path('.env.example'), $envPath);
        }

        $env = (string) file_get_contents($envPath);
        $appKey = env('APP_KEY') ?: 'base64:'.base64_encode(random_bytes(32));

        $values = [
            'APP_NAME' => $data['app_name'],
            'APP_ENV' => $data['app_env'],
            'APP_KEY' => $appKey,
            'APP_DEBUG' => $data['app_debug'] ? 'true' : 'false',
            'APP_URL' => $data['app_url'],
            'APP_TIMEZONE' => 'Asia/Jakarta',
            'DB_CONNECTION' => 'mysql',
            'DB_HOST' => $data['db_host'],
            'DB_PORT' => $data['db_port'],
            'DB_DATABASE' => $data['db_database'],
            'DB_USERNAME' => $data['db_username'],
            'DB_PASSWORD' => $data['db_password'],
            'SESSION_DRIVER' => 'file',
            'CACHE_STORE' => 'file',
            'QUEUE_CONNECTION' => 'sync',
        ];

        foreach ($values as $key => $value) {
            $env = $this->setEnvValue($env, $key, (string) $value);
        }

        file_put_contents($envPath, $env);
    }

    private function setEnvValue(string $env, string $key, string $value): string
    {
        $escaped = preg_quote($key, '/');
        $line = $key.'='.$this->quoteEnvValue($value);

        if (preg_match("/^{$escaped}=.*$/m", $env) === 1) {
            return preg_replace("/^{$escaped}=.*$/m", $line, $env) ?? $env;
        }

        return rtrim($env).PHP_EOL.$line.PHP_EOL;
    }

    private function quoteEnvValue(string $value): string
    {
        if ($value === '') {
            return '""';
        }

        if (preg_match('/\s|#|"|\'/', $value) === 1) {
            return '"'.str_replace('"', '\"', $value).'"';
        }

        return $value;
    }

    /**
     * @param array<string, string>|null $notice
     * @param array<string, mixed>|null $old
     */
    private function page(Request $request, ?array $notice = null, ?array $old = null): string
    {
        $old ??= [
            'app_name' => env('APP_NAME', 'NOXPAY'),
            'app_url' => env('APP_URL', $request->getSchemeAndHttpHost()),
            'app_env' => env('APP_ENV', 'production'),
            'app_debug' => filter_var(env('APP_DEBUG', false), FILTER_VALIDATE_BOOL),
            'db_host' => env('DB_HOST', '127.0.0.1'),
            'db_port' => env('DB_PORT', '3306'),
            'db_database' => env('DB_DATABASE', 'noxp_asgapi'),
            'db_username' => env('DB_USERNAME', 'noxp_asgapi'),
            'db_password' => env('DB_PASSWORD', ''),
            'create_database' => false,
        ];

        $csrf = csrf_field();
        $noticeHtml = '';

        if ($notice !== null) {
            $class = $notice['type'] === 'success' ? 'success' : ($notice['type'] === 'warning' ? 'warning' : 'error');
            $details = isset($notice['details']) && $notice['details'] !== ''
                ? '<pre>'.e((string) $notice['details']).'</pre>'
                : '';
            $noticeHtml = '<div class="notice '.$class.'"><strong>'.e($notice['title']).'</strong><p>'.e($notice['message']).'</p>'.$details.'</div>';
        }

        $checkedDebug = !empty($old['app_debug']) ? 'checked' : '';
        $checkedCreate = !empty($old['create_database']) ? 'checked' : '';

        return '<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Install NOXPAY</title>
    <style>
        body{margin:0;background:#0f172a;color:#e5eefb;font-family:Arial,sans-serif}
        .wrap{max-width:880px;margin:0 auto;padding:36px 18px}
        .panel{background:#111c33;border:1px solid #243652;border-radius:10px;padding:24px}
        h1{margin:0 0 8px;font-size:26px} p{color:#a9b7cf;line-height:1.55}
        .grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
        label{display:block;font-weight:700;font-size:13px;margin-bottom:7px;color:#cfe1ff}
        input,select{width:100%;box-sizing:border-box;border:1px solid #334766;background:#091225;color:#fff;border-radius:8px;padding:12px}
        .full{grid-column:1/-1}.check{display:flex;align-items:center;gap:10px}.check input{width:auto}
        button{border:0;border-radius:8px;background:#3b82f6;color:white;font-weight:800;padding:13px 18px;cursor:pointer}
        .actions{margin-top:18px;display:flex;gap:10px;align-items:center}
        .notice{border-radius:8px;padding:14px 16px;margin:18px 0}.notice p{margin:6px 0 0}
        .success{background:#0f3b2e;border:1px solid #1f9d6a}.warning{background:#3b300f;border:1px solid #c18a20}.error{background:#3b1515;border:1px solid #df4444}
        pre{white-space:pre-wrap;background:#08101f;border:1px solid #263854;border-radius:8px;padding:12px;max-height:260px;overflow:auto}
        @media(max-width:720px){.grid{grid-template-columns:1fr}.full{grid-column:auto}}
    </style>
</head>
<body>
<main class="wrap">
    <section class="panel">
        <h1>Install NOXPAY Laravel</h1>
        <p>Isi konfigurasi server, lalu installer akan menulis .env, membuat APP_KEY, menjalankan migration, dan mengunci halaman ini.</p>
        '.$noticeHtml.'
        <form method="POST" action="/installnoxpay">
            '.$csrf.'
            <div class="grid">
                <div><label>APP_NAME</label><input name="app_name" value="'.e((string) $old['app_name']).'"></div>
                <div><label>APP_URL</label><input name="app_url" value="'.e((string) $old['app_url']).'"></div>
                <div><label>APP_ENV</label><select name="app_env"><option value="production" '.(((string) $old['app_env']) === 'production' ? 'selected' : '').'>production</option><option value="local" '.(((string) $old['app_env']) === 'local' ? 'selected' : '').'>local</option></select></div>
                <div class="check"><input type="checkbox" name="app_debug" value="1" '.$checkedDebug.'><label>Aktifkan debug</label></div>
                <div><label>DB_HOST</label><input name="db_host" value="'.e((string) $old['db_host']).'"></div>
                <div><label>DB_PORT</label><input name="db_port" value="'.e((string) $old['db_port']).'"></div>
                <div><label>DB_DATABASE</label><input name="db_database" value="'.e((string) $old['db_database']).'"></div>
                <div><label>DB_USERNAME</label><input name="db_username" value="'.e((string) $old['db_username']).'"></div>
                <div class="full"><label>DB_PASSWORD</label><input type="password" name="db_password" value="'.e((string) $old['db_password']).'"></div>
                <div class="full check"><input type="checkbox" name="create_database" value="1" '.$checkedCreate.'><label>Buat database jika belum ada</label></div>
            </div>
            <div class="actions">
                <button type="submit">Install Sekarang</button>
                <span>Pastikan user database punya akses CREATE/ALTER/INSERT.</span>
            </div>
        </form>
    </section>
</main>
</body>
</html>';
    }
}
