<?php

declare(strict_types=1);

final class MyronInstallerState
{
    public function __construct(
        private readonly string $projectRoot,
        private readonly string $appRoot,
        private readonly ?string $envPath = null,
    ) {
    }

    public static function fromProjectRoot(string $projectRoot): self
    {
        return new self($projectRoot, $projectRoot . '/app');
    }

    public function envPath(): string
    {
        return $this->envPath ?? $this->projectRoot . '/.env';
    }

    public function isInstallerRequest(string $requestUri): bool
    {
        $path = parse_url($requestUri, PHP_URL_PATH);
        $requestPath = is_string($path) ? preg_replace('#/+#', '/', '/' . ltrim($path, '/')) : '/';

        $adminBase = $this->adminBasePath();
        $installBase = ($adminBase === '' ? '' : $adminBase) . '/install';

        return $requestPath === $installBase || str_starts_with((string) $requestPath, $installBase . '/');
    }

    public function installerUrl(): string
    {
        $adminBase = $this->adminBasePath();
        return ($adminBase === '' ? '' : $adminBase) . '/install/';
    }

    public function adminLoginUrl(): string
    {
        $adminBase = $this->adminBasePath();
        return ($adminBase === '' ? '' : $adminBase) . '/auth/login';
    }

    public function adminWorkspaceUrl(): string
    {
        $adminBase = $this->adminBasePath();
        return ($adminBase === '' ? '' : $adminBase) . '/content/pages';
    }

    public function adminBasePath(): string
    {
        $value = $this->envValue('MYRON_ADMIN_BASE_PATH');
        return $this->normalizeMountPath($value !== '' ? $value : '/app');
    }

    public function isInstalled(): bool
    {
        if (!$this->hasMinimumConfiguration()) {
            return false;
        }

        return $this->databaseHasUsers();
    }

    public function hasMinimumConfiguration(): bool
    {
        if (!is_file($this->envPath())) {
            return false;
        }

        if ($this->envValue('MYRON_APP_URL') === '' || $this->envValue('MYRON_APP_KEY') === '') {
            return false;
        }

        $driver = strtolower($this->envValue('MYRON_DB_DRIVER') ?: 'mysql');
        if ($driver === 'sqlite') {
            return $this->sqlitePath() !== '';
        }

        return $this->envValue('MYRON_DB_HOST') !== ''
            && $this->envValue('MYRON_DB_NAME') !== ''
            && $this->envValue('MYRON_DB_USER') !== '';
    }

    /**
     * @return array<string, string>
     */
    public function envValues(): array
    {
        $values = [];
        $path = $this->envPath();
        if (!is_file($path)) {
            return $values;
        }

        $lines = file($path, FILE_IGNORE_NEW_LINES);
        if ($lines === false) {
            return $values;
        }

        foreach ($lines as $line) {
            $trimmed = trim((string) $line);
            if ($trimmed === '' || str_starts_with($trimmed, '#')) {
                continue;
            }

            $parts = explode('=', $trimmed, 2);
            if (count($parts) !== 2) {
                continue;
            }

            $name = trim($parts[0]);
            if ($name === '') {
                continue;
            }

            $value = trim($parts[1]);
            if (
                (str_starts_with($value, '"') && str_ends_with($value, '"'))
                || (str_starts_with($value, "'") && str_ends_with($value, "'"))
            ) {
                $value = substr($value, 1, -1);
            }

            $values[$name] = $value;
        }

        return $values;
    }

    private function databaseHasUsers(): bool
    {
        try {
            $pdo = $this->probeConnection();
            $statement = $pdo->query('SELECT COUNT(*) FROM users');
            return $statement !== false && (int) $statement->fetchColumn() > 0;
        } catch (Throwable) {
            return false;
        }
    }

    private function probeConnection(): PDO
    {
        $driver = strtolower($this->envValue('MYRON_DB_DRIVER') ?: 'mysql');
        if ($driver === 'sqlite') {
            $path = $this->sqlitePath();
            if ($path === '' || !is_file($path)) {
                throw new RuntimeException('SQLite database is not available.');
            }

            $pdo = new PDO('sqlite:' . $path, null, null, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);
            $pdo->exec('PRAGMA foreign_keys = ON');
            return $pdo;
        }

        $charset = $this->envValue('MYRON_DB_CHARSET') ?: 'utf8mb4';
        $socket = $this->envValue('MYRON_DB_SOCKET');
        if ($socket !== '') {
            $dsn = sprintf(
                'mysql:unix_socket=%s;dbname=%s;charset=%s',
                $socket,
                $this->envValue('MYRON_DB_NAME'),
                $charset
            );
        } else {
            $dsn = sprintf(
                'mysql:host=%s;port=%s;dbname=%s;charset=%s',
                $this->envValue('MYRON_DB_HOST'),
                $this->envValue('MYRON_DB_PORT') ?: '3306',
                $this->envValue('MYRON_DB_NAME'),
                $charset
            );
        }

        return new PDO($dsn, $this->envValue('MYRON_DB_USER'), $this->envValue('MYRON_DB_PASS'), [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    }

    private function sqlitePath(): string
    {
        $path = $this->envValue('MYRON_DB_SQLITE_PATH');
        return $path !== '' ? $path : $this->appRoot . '/storage/myroncms.sqlite';
    }

    private function envValue(string $key): string
    {
        $values = $this->envValues();
        if (array_key_exists($key, $values)) {
            return trim((string) $values[$key]);
        }

        $runtime = getenv($key);
        return is_string($runtime) ? trim($runtime) : '';
    }

    private function normalizeMountPath(string $path): string
    {
        $trimmed = trim($path);
        if ($trimmed === '' || $trimmed === '/') {
            return '';
        }

        $normalized = '/' . trim($trimmed, '/');
        return preg_replace('#/+#', '/', $normalized) ?: '';
    }
}
