<?php
namespace App\Services;

use PDO;
use PDOException;
use RuntimeException;

/**
 * Installer — provisions config.php + imports the skeleton schema.
 * Shared by the CLI (app:install) and the (locked) web wizard.
 * Uses filesystem-relative paths, never $_SERVER['DOCUMENT_ROOT'].
 */
class Installer
{
    private string $baseDir; // == api/

    public function __construct()
    {
        // app/Services/Installer.php -> up two levels -> api/
        $this->baseDir = dirname(__DIR__, 2);
    }

    public function lockFile(): string
    {
        return $this->baseDir . '/system/.installed';
    }

    public function isInstalled(): bool
    {
        return is_file($this->lockFile());
    }

    public function testConnection(array $db): bool
    {
        try {
            new PDO(
                "mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4",
                $db['user'], $db['pass'],
                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
            );
            return true;
        } catch (PDOException $e) {
            return false;
        }
    }

    public function importSchema(array $db): void
    {
        $sqlFile = $this->baseDir . '/install/dump.sql';
        if (!is_file($sqlFile)) {
            throw new RuntimeException("Schema dump not found: {$sqlFile}");
        }
        $mysqli = new \mysqli($db['host'], $db['user'], $db['pass'], $db['name']);
        if ($mysqli->connect_errno) {
            throw new RuntimeException('DB connect failed: ' . $mysqli->connect_error);
        }
        if (!$mysqli->multi_query((string) file_get_contents($sqlFile))) {
            throw new RuntimeException('Schema import failed: ' . $mysqli->error);
        }
        // Drain all result sets so the connection finishes cleanly.
        while ($mysqli->more_results() && $mysqli->next_result()) { /* noop */ }
        if ($mysqli->errno) {
            throw new RuntimeException('Schema import error: ' . $mysqli->error);
        }
        $mysqli->close();
    }

    public function generateKey(int $bytes = 32): string
    {
        return bin2hex(random_bytes($bytes)); // hex only: safe inside single-quoted PHP
    }

    public function writeConfig(array $c): void
    {
        $e = fn($v) => addslashes((string) $v); // escape operator-provided values
        $tpl = "<?php\n"
            . "// Generated by SeedProject installer. DO NOT COMMIT (gitignored).\n"
            . "define('URL', '" . $e($c['url']) . "');\n"
            . "define('SITE_BASE', '/api');\n"
            . "define('ASSETS', '/api/public/assets/');\n"
            . "define('LIBS', 'core/');\n"
            . "define('PROJECT_NAME', '" . $e($c['name']) . "');\n"
            . "define('PROJECT_LOGO', '/api/public/assets/imgs/SeedProject.png');\n"
            . "define('DEBUG', false);\n"
            . "define('SECUREAPI', false);\n\n"
            . "define('ADMIN_TOKEN', '" . $c['admin_token'] . "');\n"
            . "define('ALLOWED_ORIGINS', '" . $e($c['url']) . "');\n\n"
            . "define('EMAILUSER', 'ADDEMAILUSER');\n"
            . "define('EMAILPASSWORD', 'ADDEMAILPASSWORD');\n"
            . "define('EMAILHOST', 'ADDEMAILHOST');\n\n"
            . "define('DB_TYPE', 'mysql');\n"
            . "define('DB_HOST', '" . $e($c['db']['host']) . "');\n"
            . "define('DB_NAME', '" . $e($c['db']['name']) . "');\n"
            . "define('DB_USER', '" . $e($c['db']['user']) . "');\n"
            . "define('DB_PASS', '" . $e($c['db']['pass']) . "');\n\n"
            . "define('HASH_PASSWORD_KEY', '" . $c['hash_password_key'] . "');\n"
            . "define('HASH_API_KEY', '" . $c['hash_api_key'] . "');\n"
            . "define('TIMESTAMP', date('Y-m-d H:i:s'));\n"
            . "date_default_timezone_set('America/New_York');\n\n"
            . "\\Db::setConnectionInfo(DB_TYPE, DB_NAME, DB_USER, DB_PASS, DB_HOST);\n";
        file_put_contents($this->baseDir . '/config.php', $tpl);
    }

    public function lock(): void
    {
        file_put_contents($this->lockFile(), date('c') . "\n");
    }

    /**
     * Full install. $cfg = ['url','name','db'=>['host','name','user','pass']].
     */
    public function run(array $cfg): void
    {
        if (!$this->testConnection($cfg['db'])) {
            throw new RuntimeException('Database connection failed. Check credentials.');
        }
        $this->importSchema($cfg['db']);
        $cfg['admin_token']       = $this->generateKey(24);
        $cfg['hash_password_key'] = $this->generateKey(32);
        $cfg['hash_api_key']      = $this->generateKey(32);
        $this->writeConfig($cfg);
        $this->lock();

        // Apply migrations on top of the freshly imported baseline.
        $pdo = new PDO(
            "mysql:host={$cfg['db']['host']};dbname={$cfg['db']['name']};charset=utf8mb4",
            $cfg['db']['user'], $cfg['db']['pass'],
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );
        (new Migrator($pdo, $this->baseDir . '/db/migrations'))->migrate();
    }
}
