<?php

declare(strict_types=1);

namespace StaticPHP\Doctor\Item;

use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Attribute\Doctor\FixItem;
use StaticPHP\Doctor\CheckResult;
use StaticPHP\Exception\EnvironmentException;
use StaticPHP\Util\System\LinuxUtil;

class LinuxToolCheck
{
    public const TOOLS_ALPINE = [
        'make', 'bison', 're2c', 'flex', 'gperf',
        'git', 'autoconf', 'automake', 'gettext-dev',
        'tar', 'unzip', 'gzip',
        'bzip2', 'cmake', 'gcc',
        'g++', 'patch', 'binutils-gold',
        'libtoolize', 'which',
    ];

    public const TOOLS_DEBIAN = [
        'make', 'bison', 're2c', 'flex', 'gperf',
        'git', 'autoconf', 'automake', 'autopoint',
        'tar', 'unzip', 'gzip', 'gcc', 'g++',
        'bzip2', 'cmake', 'patch',
        'xz', 'libtoolize', 'which',
    ];

    public const TOOLS_RHEL = [
        'perl', 'make', 'bison', 're2c', 'flex', 'gperf',
        'git', 'autoconf', 'automake',
        'tar', 'unzip', 'gzip', 'gcc', 'g++',
        'bzip2', 'cmake', 'patch', 'which',
        'xz', 'libtool', 'gettext-devel', 'file',
    ];

    public const TOOLS_ARCH = [
        'base-devel', 'cmake', 'gperf',
    ];

    private const PROVIDED_COMMAND = [
        'perl' => '/usr/share/perl5/FindBin.pm',
        'binutils-gold' => 'ld.gold',
        'base-devel' => 'automake',
        'gettext-devel' => 'gettextize',
        'gettext-dev' => 'gettextize',
        'perl-IPC-Cmd' => '/usr/share/perl5/vendor_perl/IPC/Cmd.pm',
        'perl-Time-Piece' => '/usr/lib64/perl5/Time/Piece.pm',
    ];

    /** @noinspection PhpUnused */
    #[CheckItem('if necessary tools are installed', limit_os: 'Linux', level: 999)]
    public function checkCliTools(): ?CheckResult
    {
        $distro = LinuxUtil::getOSRelease();

        $required = match ($distro['dist']) {
            'alpine' => self::TOOLS_ALPINE,
            'redhat' => self::TOOLS_RHEL,
            'centos' => array_merge(self::TOOLS_RHEL, ['perl-IPC-Cmd', 'perl-Time-Piece']),
            'arch', 'manjaro' => self::TOOLS_ARCH,
            default => null,
        };
        if ($required === null) {
            // fallback to family-based detection
            $family = explode(' ', strtolower($distro['family']));
            $required = match (true) {
                in_array('alpine', $family) => self::TOOLS_ALPINE,
                in_array('rhel', $family) || in_array('fedora', $family) => self::TOOLS_RHEL,
                in_array('arch', $family) => self::TOOLS_ARCH,
                default => self::TOOLS_DEBIAN,
            };
        }
        $missing = [];
        foreach ($required as $package) {
            if (LinuxUtil::findCommand(self::PROVIDED_COMMAND[$package] ?? $package) === null) {
                $missing[] = $package;
            }
        }
        if (!empty($missing)) {
            return CheckResult::fail(implode(', ', $missing) . ' not installed on your system', 'install-linux-tools', ['distro' => $distro, 'missing' => $missing]);
        }
        return CheckResult::ok();
    }

    #[CheckItem('if cmake version >= 3.22', limit_os: 'Linux')]
    public function checkCMakeVersion(): ?CheckResult
    {
        $ver = get_cmake_version();
        if ($ver === null) {
            return CheckResult::fail('Failed to get cmake version');
        }
        if (version_compare($ver, '3.22.0') < 0) {
            return CheckResult::fail('cmake version is too low (' . $ver . '), please update it manually!');
        }
        return CheckResult::ok($ver);
    }

    /** @noinspection PhpUnused */
    #[CheckItem('if necessary linux headers are installed', limit_os: 'Linux')]
    public function checkSystemOSPackages(): ?CheckResult
    {
        if (LinuxUtil::isMuslDist()) {
            // check linux-headers installation
            if (!file_exists('/usr/include/linux/mman.h')) {
                return CheckResult::fail('linux-headers not installed on your system', 'install-linux-tools', ['distro' => LinuxUtil::getOSRelease(), 'missing' => ['linux-headers']]);
            }
        }
        return CheckResult::ok();
    }

    #[FixItem('install-linux-tools')]
    public function fixBuildTools(array $distro, array $missing): bool
    {
        $install_cmd = match ($distro['dist']) {
            'ubuntu', 'debian', 'Deepin', 'neon' => 'apt-get install -y',
            'alpine' => 'apk add',
            'redhat' => 'dnf install -y',
            'centos' => 'yum install -y',
            'arch' => 'pacman -S --noconfirm',
            default => null,
        };
        if ($install_cmd === null) {
            // try family
            $family = explode(' ', strtolower($distro['family']));
            if (in_array('debian', $family) || in_array('ubuntu', $family)) {
                $install_cmd = 'apt-get install -y';
            } elseif (in_array('rhel', $family) || in_array('fedora', $family)) {
                $install_cmd = 'dnf install -y';
            } elseif (in_array('arch', $family)) {
                $install_cmd = 'pacman -S --noconfirm';
            } else {
                throw new EnvironmentException(
                    "Current linux distro [{$distro['dist']}] does not have an auto-install script for packages yet.",
                    'You can submit an issue to request support: https://github.com/crazywhalecc/static-php-cli/issues'
                );
            }
        }
        $prefix = '';
        if (($user = exec('whoami')) !== 'root') {
            $prefix = 'sudo ';
            logger()->warning("Current user ({$user}) is not root, using sudo for running command (may require password input)");
        }

        $is_debian = LinuxUtil::isDebianDist();
        $to_install = $is_debian ? str_replace('xz', 'xz-utils', $missing) : $missing;
        // debian, alpine libtool -> libtoolize
        $to_install = str_replace('libtoolize', 'libtool', $to_install);
        f_passthru($prefix . $install_cmd . ' ' . implode(' ', $to_install));

        return true;
    }
}
