<?php

declare(strict_types=1);

final class MyronInstallerExecutionService
{
    public function __construct(
        private readonly string $projectRoot,
        private readonly string $appRoot,
        private readonly MyronInstallerState $state,
        private readonly MyronInstallerConfigService $config,
        private readonly MyronInstallerEnvWriter $envWriter,
    ) {
    }

    /**
     * @param array<string, mixed> $input
     * @return array{user:array<string,mixed>, adminUrl:string, publicUrl:string}
     */
    public function install(array $input): array
    {
        if ($this->state->isInstalled()) {
            throw new RuntimeException('MyronCMS is already installed.');
        }

        $databaseConfig = $this->config->normalizeDatabaseConfig($input);
        $installConfig = $this->config->normalizeInstallInput($input);
        $existingEnv = $this->state->envValues();

        $this->config->assertDatabaseConnection($databaseConfig);

        $updates = array_merge($databaseConfig, [
            'MYRON_APP_ENV' => trim((string) ($existingEnv['MYRON_APP_ENV'] ?? '')) !== ''
                ? trim((string) $existingEnv['MYRON_APP_ENV'])
                : 'production',
            'MYRON_APP_URL' => $installConfig['app_url'],
            'MYRON_APP_KEY' => trim((string) ($existingEnv['MYRON_APP_KEY'] ?? '')) !== ''
                ? trim((string) $existingEnv['MYRON_APP_KEY'])
                : $this->config->generateAppKey(),
            'MYRON_ADMIN_BASE_PATH' => $installConfig['admin_base_path'],
            'MYRON_PUBLIC_BASE_PATH' => $installConfig['public_base_path'],
        ]);

        $this->envWriter->write($updates);
        $this->applyRuntimeEnvironment($updates);

        $pdo = $this->config->createConnection($databaseConfig);
        MyronMigrationRunner::migrate($pdo);
        $this->saveSiteTitle($pdo, $installConfig['site_title']);
        $this->seedRuntimeContent();

        $auth = new MyronAuthRepository($pdo);
        $auth->ensureSeedData();
        if ($auth->hasUsers()) {
            throw new RuntimeException('An administrator account already exists. Installer cannot create another first user.');
        }

        $user = $auth->bootstrapFirstAdministrator(
            'Site Administrator',
            $installConfig['admin_email'],
            $installConfig['admin_password']
        );
        $auth->markLogin((string) $user['id']);

        return [
            'user' => $user,
            'adminUrl' => $this->state->adminWorkspaceUrl(),
            'publicUrl' => $installConfig['public_base_path'] === '/' ? '/' : $installConfig['public_base_path'] . '/',
        ];
    }

    private function seedRuntimeContent(): void
    {
        require_once $this->appRoot . '/system/bootstrap.php';

        myronEnsureDefinitionsRuntimeSeeded();
        myronContentRepository()->ensureSeedData();
    }

    /**
     * @param array<string, string> $updates
     */
    private function applyRuntimeEnvironment(array $updates): void
    {
        foreach ($updates as $key => $value) {
            putenv($key . '=' . $value);
            $_ENV[$key] = $value;
            $_SERVER[$key] = $value;
        }
    }

    private function saveSiteTitle(PDO $pdo, string $siteTitle): void
    {
        $statement = $pdo->prepare(
            'UPDATE site_settings
             SET site_title = :site_title,
                 updated_at = :updated_at
             WHERE id = 1'
        );
        $statement->execute([
            ':site_title' => $siteTitle,
            ':updated_at' => myronNow(),
        ]);
    }
}
