<?php

declare(strict_types=1);

namespace App\Framework\Process;

use App\Framework\Console\ExitCode;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Process\Exceptions\ProcessException;
use App\Framework\Process\Exceptions\ProcessTimeoutException;
use App\Framework\Process\ValueObjects\Command;

/**
 * Repräsentiert einen laufenden asynchronen Prozess.
 *
 * @internal
 */
final class SystemRunningProcess implements RunningProcess
{
    /**
     * @param resource $process
     * @param array<int, resource> $pipes
     */
    public function __construct(
        private                  $process,
        private readonly array   $pipes,
        private readonly Command $command,
        private readonly int     $pid,
        private readonly Logger  $logger
    ) {
    }

    public function isRunning(): bool
    {
        if (! is_resource($this->process)) {
            return false;
        }

        $status = proc_get_status($this->process);

        return $status['running'] ?? false;
    }

    public function getPid(): int
    {
        return $this->pid;
    }

    public function wait(?Duration $timeout = null): ProcessResult
    {
        $startTime = microtime(true);

        // Apply timeout if specified
        if ($timeout !== null) {
            $this->applyTimeout($timeout);
        }

        // Wait for process to finish by reading outputs
        $stdout = stream_get_contents($this->pipes[1]);
        $stderr = stream_get_contents($this->pipes[2]);

        // Close all pipes
        fclose($this->pipes[0]);
        fclose($this->pipes[1]);
        fclose($this->pipes[2]);

        // Get exit code
        $exitCodeValue = proc_close($this->process);

        // Check for timeout
        if ($timeout !== null) {
            $elapsed = microtime(true) - $startTime;
            if ($elapsed >= $timeout->toSeconds()) {
                throw ProcessTimeoutException::exceeded($this->command, $timeout);
            }
        }

        // Calculate runtime
        $runtimeSeconds = microtime(true) - $startTime;
        $runtime = Duration::fromSeconds($runtimeSeconds);

        // Map exit code
        $exitCode = $this->mapExitCode($exitCodeValue);

        $result = new ProcessResult(
            command: $this->command,
            stdout: $stdout ?: '',
            stderr: $stderr ?: '',
            exitCode: $exitCode,
            runtime: $runtime
        );

        $this->logger->debug(
            'Async process completed',
            LogContext::withData([
                'command' => $this->command->toString(),
                'pid' => $this->pid,
                'exit_code' => $exitCodeValue,
                'runtime_ms' => $runtime->toMilliseconds(),
            ])
        );

        return $result;
    }

    public function signal(int $signal = 15): void
    {
        if (! $this->isRunning()) {
            return;
        }

        $result = proc_terminate($this->process, $signal);

        if (! $result) {
            throw ProcessException::failedToSendSignal($this->pid, $signal);
        }

        $this->logger->debug(
            'Signal sent to process',
            LogContext::withData([
                'pid' => $this->pid,
                'signal' => $signal,
            ])
        );
    }

    public function terminate(): void
    {
        $this->signal(15); // SIGTERM
    }

    public function kill(): void
    {
        $this->signal(9); // SIGKILL
    }

    /**
     * Wendet ein Timeout auf die Pipes an.
     */
    private function applyTimeout(Duration $timeout): void
    {
        $timeoutSeconds = $timeout->toTimeoutSeconds();

        foreach ($this->pipes as $pipe) {
            if (is_resource($pipe)) {
                stream_set_timeout($pipe, $timeoutSeconds);
            }
        }
    }

    /**
     * Mappt einen numerischen Exit-Code zum ExitCode Enum.
     */
    private function mapExitCode(int $exitCodeValue): ExitCode
    {
        return match ($exitCodeValue) {
            0 => ExitCode::SUCCESS,
            1 => ExitCode::GENERAL_ERROR,
            2 => ExitCode::USAGE_ERROR,
            64 => ExitCode::COMMAND_NOT_FOUND,
            65 => ExitCode::INVALID_INPUT,
            66 => ExitCode::NO_INPUT,
            69 => ExitCode::UNAVAILABLE,
            70 => ExitCode::SOFTWARE_ERROR,
            71 => ExitCode::OS_ERROR,
            72 => ExitCode::OS_FILE_ERROR,
            73 => ExitCode::CANT_CREATE,
            74 => ExitCode::IO_ERROR,
            75 => ExitCode::TEMP_FAIL,
            76 => ExitCode::PROTOCOL_ERROR,
            77 => ExitCode::NO_PERMISSION,
            78 => ExitCode::CONFIG_ERROR,
            79 => ExitCode::DATABASE_ERROR,
            80 => ExitCode::RATE_LIMITED,
            126 => ExitCode::PERMISSION_DENIED,
            130 => ExitCode::INTERRUPTED,
            default => ExitCode::GENERAL_ERROR,
        };
    }
}
