<?php

namespace App\Services\Installer;

use App\Models\User;
use App\Support\EnvFileEditor;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Throwable;

class InstallerService
{
    public function __construct(private readonly EnvFileEditor $env) {}

    public function bootForInstaller(): void
    {
        $this->env->ensureExists();
        $this->env->ensureAppKey();

        if (! $this->isInstalled()) {
            config([
                'session.driver' => 'file',
                'cache.default' => config('cache.default') === 'database' ? 'file' : config('cache.default'),
            ]);
        }
    }

    public function isInstalled(): bool
    {
        return File::exists($this->lockPath()) || filled(env('INSTALLATION_DOMAIN_HASH'));
    }

    public function lockPath(): string
    {
        return (string) config('installer.lock_file', storage_path('app/remotematch-installed.json'));
    }

    /**
     * @return array<int, array{label: string, passed: bool, detail: string}>
     */
    public function requirementChecks(): array
    {
        return [
            [
                'label' => 'PHP version 8.3+',
                'passed' => version_compare(PHP_VERSION, '8.3.0', '>='),
                'detail' => PHP_VERSION,
            ],
            [
                'label' => 'OpenSSL extension',
                'passed' => extension_loaded('openssl'),
                'detail' => extension_loaded('openssl') ? 'Available' : 'Missing',
            ],
            [
                'label' => 'PDO MySQL extension',
                'passed' => extension_loaded('pdo_mysql'),
                'detail' => extension_loaded('pdo_mysql') ? 'Available' : 'Missing',
            ],
            [
                'label' => 'cURL extension',
                'passed' => extension_loaded('curl'),
                'detail' => extension_loaded('curl') ? 'Available' : 'Missing',
            ],
            [
                'label' => 'Fileinfo extension',
                'passed' => extension_loaded('fileinfo'),
                'detail' => extension_loaded('fileinfo') ? 'Available' : 'Missing',
            ],
            $this->writableCheck('Storage directory', storage_path()),
            $this->writableCheck('Bootstrap cache directory', base_path('bootstrap/cache')),
            $this->writableCheck('Environment file', base_path('.env')),
        ];
    }

    public function requirementsPass(): bool
    {
        return collect($this->requirementChecks())->every(fn (array $check): bool => $check['passed']);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function install(array $data): void
    {
        if ($this->isInstalled()) {
            throw new \RuntimeException('RemoteMatch is already installed.');
        }

        $this->testDatabase($data);

        $domain = $this->normalizeDomain(parse_url((string) $data['app_url'], PHP_URL_HOST) ?: '');
        $domainHash = $this->domainHash($domain);

        $this->env->set([
            'APP_NAME' => $data['app_name'],
            'APP_ENV' => 'production',
            'APP_DEBUG' => false,
            'APP_URL' => rtrim((string) $data['app_url'], '/'),
            'APP_TIMEZONE' => $data['timezone'],
            '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' => 'database',
            'CACHE_STORE' => 'database',
            'QUEUE_CONNECTION' => 'database',
            'DEFAULT_ADMIN_NAME' => $data['admin_name'],
            'DEFAULT_ADMIN_EMAIL' => $data['admin_email'],
            'DEFAULT_ADMIN_PASSWORD' => $data['admin_password'],
            'INSTALLATION_DOMAIN' => $domain,
            'INSTALLATION_DOMAIN_HASH' => $domainHash,
            'INSTALLER_DOMAIN_LOCK' => true,
        ]);

        config([
            'app.name' => $data['app_name'],
            'app.env' => 'production',
            'app.debug' => false,
            'app.url' => rtrim((string) $data['app_url'], '/'),
            'app.timezone' => $data['timezone'],
            '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'] ?? '',
            'session.driver' => 'file',
            'cache.default' => 'file',
            'queue.default' => 'database',
            'admin.default_name' => $data['admin_name'],
            'admin.default_email' => $data['admin_email'],
            'admin.default_password' => $data['admin_password'],
        ]);

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

        Artisan::call('migrate', ['--force' => true]);
        Artisan::call('db:seed', ['--force' => true]);
        Artisan::call('storage:link');

        User::query()
            ->where('email', (string) $data['admin_email'])
            ->where('role', 'admin')
            ->update([
                'must_change_password' => false,
                'password_set_at' => now(),
            ]);

        $this->createLock($domain, (string) $data['admin_email']);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function testDatabase(array $data): void
    {
        config([
            'database.connections.installer_mysql' => [
                'driver' => 'mysql',
                'host' => $data['db_host'],
                'port' => $data['db_port'],
                'database' => $data['db_database'],
                'username' => $data['db_username'],
                'password' => $data['db_password'] ?? '',
                'unix_socket' => '',
                'charset' => 'utf8mb4',
                'collation' => 'utf8mb4_unicode_ci',
                'prefix' => '',
                'prefix_indexes' => true,
                'strict' => true,
                'engine' => null,
            ],
        ]);

        DB::purge('installer_mysql');
        DB::connection('installer_mysql')->getPdo();
        DB::connection('installer_mysql')->select('select 1');
    }

    public function isCurrentDomainAllowed(Request $request): bool
    {
        if (! (bool) config('installer.domain_lock', true) || app()->environment('testing')) {
            return true;
        }

        $record = $this->installationRecord();

        if ($record === []) {
            return true;
        }

        $currentDomain = $this->normalizeDomain($request->getHost());
        $installedDomain = $this->normalizeDomain((string) ($record['domain'] ?? ''));

        if ($currentDomain === $installedDomain) {
            return true;
        }

        return $this->domainHash($currentDomain) === (string) ($record['domain_hash'] ?? '');
    }

    /**
     * @return array<string, mixed>
     */
    public function installationRecord(): array
    {
        if (File::exists($this->lockPath())) {
            $payload = json_decode(File::get($this->lockPath()), true);

            if (is_array($payload)) {
                return $payload;
            }
        }

        if (filled(env('INSTALLATION_DOMAIN_HASH'))) {
            return [
                'domain' => env('INSTALLATION_DOMAIN'),
                'domain_hash' => env('INSTALLATION_DOMAIN_HASH'),
            ];
        }

        return [];
    }

    public function domainHash(string $domain): string
    {
        return hash('sha256', $this->normalizeDomain($domain).'|'.(string) config('app.key'));
    }

    public function lockCurrentInstallation(?string $adminEmail = null): void
    {
        $domain = $this->normalizeDomain(parse_url((string) config('app.url'), PHP_URL_HOST) ?: '');

        if ($domain === '') {
            throw new \RuntimeException('APP_URL must contain a valid host before the installation can be locked.');
        }

        $domainHash = $this->domainHash($domain);

        $this->env->set([
            'INSTALLATION_DOMAIN' => $domain,
            'INSTALLATION_DOMAIN_HASH' => $domainHash,
            'INSTALLER_DOMAIN_LOCK' => true,
        ]);

        $this->createLock($domain, $adminEmail ?: (string) config('admin.default_email'));
    }

    public function normalizeDomain(string $domain): string
    {
        $domain = strtolower(trim($domain));
        $domain = preg_replace('/:\d+$/', '', $domain) ?: $domain;

        return str_starts_with($domain, 'www.') ? substr($domain, 4) : $domain;
    }

    private function createLock(string $domain, string $adminEmail): void
    {
        File::ensureDirectoryExists(dirname($this->lockPath()));

        File::put($this->lockPath(), json_encode([
            'installed_at' => now()->toIso8601String(),
            'domain' => $domain,
            'domain_hash' => $this->domainHash($domain),
            'admin_email' => $adminEmail,
            'app_version' => '1.0.0',
        ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    }

    /**
     * @return array{label: string, passed: bool, detail: string}
     */
    private function writableCheck(string $label, string $path): array
    {
        try {
            $passed = File::exists($path) && is_writable($path);
        } catch (Throwable) {
            $passed = false;
        }

        return [
            'label' => $label,
            'passed' => $passed,
            'detail' => $path,
        ];
    }
}
