<?php

declare(strict_types=1);

namespace App\Domain\Sysapi\Bridges;

use App\Domain\Audit\Services\AuditWriter;
use App\Domain\Sysapi\Actions\AllowedActions;
use App\Domain\Sysapi\Contracts\SysapiBridge;
use App\Domain\Sysapi\Contracts\SysapiResult;
use App\Domain\Sysapi\Exceptions\SysapiActionNotAllowedException;
use App\Domain\Sysapi\Exceptions\SysapiException;
use App\Domain\Sysapi\Exceptions\SysapiExecutionException;
use App\Domain\Sysapi\Exceptions\SysapiTimeoutException;
use Illuminate\Process\Exceptions\ProcessTimedOutException;
use Illuminate\Support\Facades\Process;

/**
 * Production sysapi implementation: sudo + bash wrapper scripts.
 *
 * Master plan §5 — HestiaCP modeli.
 *
 * Komut akışı:
 *   1. Komut adı whitelist regex'ten geçer (^[a-z][a-z0-9-]{2,30}$)
 *   2. /usr/local/onoxsoft/bin/onx-<command> script var ve executable mı kontrol
 *   3. JSON args stdin'den gönderilir
 *   4. sudo ile çalıştırılır (sudoers.d/onoxsoft whitelist'i sayesinde NOPASSWD)
 *   5. stdout'tan JSON parse edilir, SysapiResult'a sarılır
 *
 * Güvenlik:
 *   - exec()/shell_exec() KULLANILMAZ — sadece array form Process::run()
 *   - Argümanlar shell'e geçmez, script kendi parser'ında işler
 *   - Script kendi içinde regex/whitelist validation yapar (defense in depth)
 *
 * Sudoers örneği (/etc/sudoers.d/onoxsoft):
 *
 *   apache ALL=(root) NOPASSWD: /usr/local/onoxsoft/bin/onx-*
 *   Defaults!/usr/local/onoxsoft/bin/onx-* !requiretty
 *   Defaults!/usr/local/onoxsoft/bin/onx-* log_output, log_input
 */
final class SudoBashSysapi implements SysapiBridge
{
    /**
     * Komut adı whitelist regex.
     * Sadece küçük harf, rakam, tire. 3-30 karakter.
     */
    private const COMMAND_REGEX = '/^[a-z][a-z0-9-]{2,30}$/';

    public function __construct(
        private readonly string $binDir,
        private readonly string $sudoBinary = '/usr/bin/sudo',
    ) {}

    public function run(string $command, array $args = [], int $timeoutSeconds = 60): SysapiResult
    {
        // 1. Komut adı: regex + whitelist (iki katmanlı)
        if (! preg_match(self::COMMAND_REGEX, $command)) {
            throw SysapiException::invalidCommand($command);
        }
        if (! AllowedActions::contains($command)) {
            throw SysapiActionNotAllowedException::forAction($command);
        }

        // 2. Script yolu + path traversal koruması
        $script = "{$this->binDir}/onx-{$command}";
        $realScript = realpath($script);
        $realBinDir = realpath($this->binDir) ?: $this->binDir;

        if ($realScript === false || ! str_starts_with($realScript, $realBinDir.DIRECTORY_SEPARATOR)) {
            throw SysapiException::scriptNotFound($command, $script);
        }

        if (! is_file($realScript) || ! is_executable($realScript)) {
            throw SysapiException::scriptNotFound($command, $realScript);
        }

        // 3. JSON encode args
        try {
            $jsonInput = json_encode($args, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        } catch (\JsonException $e) {
            throw new SysapiException(
                "Sysapi args JSON encode edilemedi: {$e->getMessage()}",
                command: $command,
                previous: $e,
            );
        }

        // 4. sudo ile çalıştır (array form — shell escape yok)
        $startedAt = microtime(true);

        try {
            $process = Process::input($jsonInput)
                ->timeout($timeoutSeconds)
                ->run([$this->sudoBinary, '-n', $realScript]);
        } catch (ProcessTimedOutException $e) {
            throw SysapiTimeoutException::forCommand($command, $timeoutSeconds);
        }

        $duration = microtime(true) - $startedAt;
        $result = SysapiResult::fromProcess($process, $duration);

        // 5. Audit log (yapılandırılmışsa — hata audit'i engellemez)
        if (config('onoxsoft.sysapi.audit_log', true)) {
            try {
                AuditWriter::write(
                    action: 'sysapi:'.$command,
                    metadata: array_merge($result->toAuditLog(), [
                        'payload' => $args,
                    ]),
                    outcome: $result->successful ? 'success' : 'failure',
                    errorMessage: $result->failed() ? $result->stderr : null,
                );
            } catch (\Throwable) {
                // Audit log başarısızlığı sysapi çağrısını engellememeli
            }
        }

        // 6. Başarısız ise exception fırlat
        if ($result->failed()) {
            throw SysapiExecutionException::fromResult($command, $result);
        }

        return $result;
    }

    public function allowedActions(): array
    {
        return AllowedActions::list();
    }

    public function isReal(): bool
    {
        return true;
    }
}
