<?php

namespace App\Services\Installer;

/**
 * Surgical .env updater for the installer. Reads the existing .env line by
 * line, replaces matching keys, appends new keys at the end. Preserves
 * comments, blank lines, and unrelated keys — only touches what we know
 * about.
 *
 * We deliberately do NOT use `Dotenv\Dotenv` here because it doesn't
 * round-trip — it only reads. A full file rewrite via the SDK would clobber
 * comments and reorder keys. Hand-rolled regex stays minimal.
 */
class EnvWriter
{
    public function __construct(private string $path)
    {
    }

    /**
     * @param  array<string, string|int|bool|null>  $values
     */
    public function set(array $values): void
    {
        $contents = file_exists($this->path) ? file_get_contents($this->path) : '';
        $lines = $contents === '' ? [] : preg_split("/\r\n|\n|\r/", $contents);
        $seen = [];

        foreach ($lines as $i => $line) {
            $trimmed = ltrim($line);
            if ($trimmed === '' || str_starts_with($trimmed, '#')) {
                continue;
            }
            if (! preg_match('/^([A-Z_][A-Z0-9_]*)\s*=/', $trimmed, $m)) {
                continue;
            }
            $key = $m[1];
            if (! array_key_exists($key, $values)) {
                continue;
            }
            $lines[$i] = $this->formatLine($key, $values[$key]);
            $seen[$key] = true;
        }

        // Append missing keys at the end (no leading blank line if the file
        // is already empty; one separator otherwise).
        foreach ($values as $key => $value) {
            if (isset($seen[$key])) {
                continue;
            }
            if ($lines !== [] && end($lines) !== '') {
                $lines[] = '';
            }
            $lines[] = $this->formatLine($key, $value);
        }

        $output = implode("\n", $lines);
        if (! str_ends_with($output, "\n")) {
            $output .= "\n";
        }

        file_put_contents($this->path, $output, LOCK_EX);
    }

    private function formatLine(string $key, mixed $value): string
    {
        if (is_bool($value)) {
            return $key.'='.($value ? 'true' : 'false');
        }
        if ($value === null) {
            return $key.'=';
        }
        $stringValue = (string) $value;

        // Quote when the value contains spaces, hash, or trailing
        // whitespace — Laravel's parser treats unquoted hashes as
        // comment-starts, so DB passwords with `#` would silently truncate.
        $needsQuotes = $stringValue === ''
            || preg_match('/[\s#"\'\\\\]/', $stringValue) === 1;

        if (! $needsQuotes) {
            return $key.'='.$stringValue;
        }

        $escaped = str_replace(['\\', '"'], ['\\\\', '\\"'], $stringValue);

        return $key.'="'.$escaped.'"';
    }
}
