<?php declare(strict_types=1);
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace PHPUnit\Logging\OpenTestReporting;

use const DIRECTORY_SEPARATOR;
use const PHP_OS_FAMILY;
use function assert;
use function explode;
use function fclose;
use function function_exists;
use function getenv;
use function gethostname;
use function is_resource;
use function php_uname;
use function posix_geteuid;
use function posix_getpwuid;
use function preg_split;
use function proc_close;
use function proc_open;
use function str_contains;
use function str_replace;
use function str_starts_with;
use function stream_get_contents;
use function trim;

/**
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
 *
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
 */
final readonly class InfrastructureInformationProvider
{
    /**
     * @return non-empty-string
     */
    public function operatingSystem(): string
    {
        return php_uname();
    }

    /**
     * @return non-empty-string
     */
    public function hostName(): string
    {
        $candidate = gethostname();

        if ($candidate === false) {
            return 'unknown';
        }

        $candidate = trim($candidate);

        if ($candidate === '') {
            return 'unknown';
        }

        return $candidate;
    }

    /**
     * @return non-empty-string
     */
    public function userName(): string
    {
        if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
            $candidate = trim(posix_getpwuid(posix_geteuid())['name']);
        } elseif (PHP_OS_FAMILY === 'Windows') {
            $candidate = trim((string) getenv('USERNAME'));
        }

        if (!isset($candidate) || $candidate === '') {
            return 'unknown';
        }

        return $candidate;
    }

    /**
     * @return array{originUrl: non-empty-string, branch: non-empty-string, commit: non-empty-string, clean: bool, status: string}|false
     */
    public function gitInformation(): array|false
    {
        $buffer = $this->executeGitCommand('remote show -n');

        if ($buffer === false) {
            return false;
        }

        if (!str_contains($buffer, 'origin')) {
            return false;
        }

        $buffer = $this->executeGitCommand('remote show -n origin');

        if ($buffer === false) {
            return false;
        }

        $lines = preg_split("/\r\n|\n|\r/", $buffer);

        if (!isset($lines[1]) || !str_starts_with($lines[1], '  Fetch URL: ')) {
            return false;
        }

        $originUrl = trim(str_replace('  Fetch URL: ', '', $lines[1]));

        if (str_contains($originUrl, '@')) {
            $originUrl = explode('@', $originUrl)[1];
        }

        $branch = $this->executeGitCommand('rev-parse --abbrev-ref HEAD');

        if ($branch === false) {
            return false;
        }

        $commit = $this->executeGitCommand('rev-parse HEAD');

        if ($commit === false) {
            return false;
        }

        $status = $this->executeGitCommand('status --porcelain');

        if ($status === false) {
            return false;
        }

        return [
            'originUrl' => $originUrl,
            'branch'    => $branch,
            'commit'    => $commit,
            'clean'     => $status === '',
            'status'    => $status,
        ];
    }

    /**
     * @return false|non-empty-string
     */
    private function executeGitCommand(string $command): false|string
    {
        $command = 'git ' . $command;

        if (DIRECTORY_SEPARATOR === '/') {
            $command = 'LC_ALL=en_US.UTF-8 ' . $command;
        }

        $process = @proc_open(
            $command,
            [
                1 => ['pipe', 'w'],
                2 => ['pipe', 'w'],
            ],
            $pipes,
        );

        if (!is_resource($process)) {
            return false;
        }

        assert(isset($pipes[1]) && is_resource($pipes[1]));
        assert(isset($pipes[2]) && is_resource($pipes[2]));

        $result = trim((string) stream_get_contents($pipes[1]));

        fclose($pipes[1]);
        fclose($pipes[2]);

        $returnCode = proc_close($process);

        if ($returnCode !== 0) {
            return false;
        }

        return $result;
    }
}
