<?php

namespace PHPIAC;

use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Net\SFTP;
use phpseclib3\Net\SSH2;

/**
 * Class Connection
 *
 * @method static string exec(string $command, callback $callback = null)
 * @method static string|bool|null read(string $expect = '', int $read = 1)
 * @method static bool write(string $cmd)
 * @method static enablePty()
 * @method static disablePty()
 */
class Connection
{
    private static SSH2 $ssh;
    private static SFTP $sftp;

    private static string $host;
    private static string $user;
    private static mixed $key;

    /**
     * Connection constructor.
     *
     * @param string $host
     * @param string $user
     * @param string $keyFile
     *
     * @throws \Exception
     */
    public static function initialize(string $host, string $user, string $keyFile)
    {
        self::$host = $host;
        self::$user = $user;
        self::$key = PublicKeyLoader::load(file_get_contents($keyFile));

        self::connect();
    }

    /**
     * @throws \Exception
     */
    private static function connect()
    {
        self::$ssh = new SSH2(self::$host);
        if (! self::$ssh->login(self::$user, self::$key)) {
            throw new \Exception('SSH Login failed');
        }

        self::$sftp = new SFTP(self::$host);
        if (! self::$sftp->login(self::$user, self::$key)) {
            throw new \Exception('SFTP Login failed');
        }
    }

    /**
     * @throws \Exception
     */
    private static function ensureConnection()
    {
        if (! self::$ssh->isConnected() ||
            ! self::$sftp->isConnected()) {
            self::connect();
        }
    }

    /**
     * Calls SSH2 methods statically
     *
     * @param string $name
     * @param array $arguments
     *
     * @return mixed
     */
    public static function __callStatic(string $name, array $arguments): mixed
    {
        self::ensureConnection();

        if (! method_exists(self::$ssh, $name)) {
            return self::$sftp->$name(...$arguments);
        }

        return self::$ssh->$name(...$arguments);
    }

    /**
     * @see SFTP::put
     */
    public static function put($remote_file, $data, $mode = SFTP::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null): bool
    {
        self::ensureConnection();

        $tmp = bin2hex(random_bytes(10)); # work around sftp sudo put restrictions

        return
            self::$sftp->put("/tmp/$tmp", $data, $mode, $start, $local_start, $progressCallback) &&
            self::$ssh->exec("sudo mv /tmp/$tmp $remote_file");
    }
}