<?php

namespace FiloBlu\Flow\Model\Connector;

use Exception;
use FiloBlu\Flow\Helper\LoggerProvider;
use Monolog\Logger;
use RuntimeException;
use phpseclib\Crypt\RSA;
use phpseclib\Net\SFTP;

/**
 * Created by PhpStorm.
 * User: raffaele pugliese
 * Date: 12/01/24
 */
class SFtpConnector extends AbstractConnector
{
    /**
     * @var string
     */
    public $type = 'SFTP';
    /**
     * @var string
     */
    public $ftpDir;
    /**
     * @var string
     */
    public $incomingDir;
    /**
     * @var string
     */
    public $localDir;
    /**
     * @var string
     */
    public $localDirOriginal;
    /**
     * @var string
     */
    public $destinationDir;
    /**
     * @var string
     */
    public $dirRoot;
    /**
     * @var string
     */
    protected $host;
    /**
     * @var string
     */
    protected $username;
    /**
     * @var string
     */
    protected $password;
    /**
     * @var int
     */
    protected $passive = 0;
    /**
     * @var \FiloBlu\Flow\Helper\Monolog\Logger|Logger
     */
    protected $_logger;
    /**
     * @var string
     */
    protected $auth_type;
    /**
     * @var bool|null|string
     */
    protected $private_key;

    /**
     * Init FTP Class retrieving configuration.
     *
     * @param LoggerProvider $_loggerProvider
     */
    public function __construct(
        LoggerProvider $_loggerProvider
    ) {
        parent::__construct($_loggerProvider);
        $this->_logger = $_loggerProvider->getLogger();
    }

    /**
     * @param $config
     */
    public function setConfig($config)
    {
        $this->_config = $config;

        $this->host = $this->_config['host'];
        $this->username = $this->_config['username'];
        $this->auth_type = $this->_config['auth_type'];
        $this->password = (isset($this->_config['password'])) ? $this->_config['password'] : null;
        $this->private_key = (isset($this->_config['private_key'])) ? $this->_config['private_key'] : null;
        $this->passive = $this->_config['passive'];
        $this->ftpDir = $this->_config['ftp_dir'];

        if (isset($this->_config['incomingDir'])) {
            $this->incomingDir = $this->_config['incomingDir'];
        }

        //TODO: Create a provider
        $this->localDir = BP . '/var/' . $this->_config['local_dir'];
        $this->localDirOriginal = $this->_config['local_dir'];

        $this->destinationDir = '';
        if (isset($this->_config['destination_dir'])) {
            $this->destinationDir = $this->_config['destination_dir'];
        }

        $this->dirRoot = BP;
        $this->connection = null;

        $this->_mask = (string)$this->_config['regex'];

        if (!file_exists($this->localDir) && !mkdir($concurrentDirectory = $this->localDir, 0755, true) && !is_dir(
                $concurrentDirectory
            )) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
        }
    }

    /**
     * @return mixed|void
     */
    public function fetchResources()
    {
    }

    /**
     * @return mixed
     */
    public function getLocalDir()
    {
        return $this->localDir;
    }

    /**
     * @param $dir
     * @return $this
     */
    public function setLocalDir($dir)
    {
        $this->localDir = $dir;
        return $this;
    }

    /**
     * List, Download and Delete resources from ftp.
     *
     * @return array|mixed
     * @throws Exception
     */
    public function getResources()
    {
        $this->_logger->info(__METHOD__);

        $this->connection = $this->getConnection();
        $this->login($this->username, $this->password);

        $resources = $this->listDir($this->ftpDir);
        $downloadedFiles = [];

        foreach ($resources as $resource) {
            $baseFile = basename($resource);

            // la regex deve avere caratteri delimiter! nel campo backend
            if (preg_match($this->_mask, $baseFile)) {
                $downloadedFiles[] = $baseFile;
            }
        }

        $this->close();
        return $downloadedFiles;
    }

    /**
     * @return mixed
     * @throws Exception
     */
    public function getConnection()
    {
        $this->openConnection();
        return $this->connection;
    }

    /**
     * @return SFTP
     * @throws Exception
     */
    public function openConnection()
    {
        if (!$this->connection) {
            if (!$this->host) {
                $this->_logger->info(__METHOD__ . ' -> Missing HOST');
                throw new Exception('Missing HOST');
            }

            try {
                $this->connection = new SFTP($this->host);
                if (!$this->connection) {
                    $this->_logger->info(__METHOD__ . ' -> SFTP connection failed');
                    throw new Exception('SFTP connection failed');
                }

                $this->login();
            }
            catch (Exception $e) {
                $this->_logger->error(__METHOD__ . $e->getMessage());
                throw new \Exception('SFTP login failed: '.$e->getMessage());
            }
        }
        return $this->connection;
    }


    /**
     * @param $user
     * @param $pass
     * @param $key
     * @return void
     * @throws Exception
     */
    public function login($user = null, $pass = null, $private_key = null)
    {
        if($this->connection && $this->connection->isAuthenticated()) {
            return;
        }

        if(!$user) {
            $user = $this->username;
        }
        if(!$pass) {
            $pass = $this->password;
        }
        if(!$private_key) {
            $private_key = $this->private_key;
        }
        if(!$this->connection) {
            $this->getConnection();
        }

        try {
            if($this->auth_type == 'key') {
                if($private_key) {
                    $key = new RSA();
                    $key->loadKey($private_key);
                    if (!$this->connection->login($user, $key)) {
                        throw new \Exception('SFTP login failed over public key');
                    }
                }
                else {
                    throw new \Exception('SFTP login failed over public key: missing private key');
                }
            }
            elseif($this->auth_type == 'password') {
                if($pass) {
                    if (!$this->connection->login($user, $pass)) {
                        $this->_logger->error(__METHOD__ . 'SFTP login failed over username and paswword');
                        throw new \Exception('SFTP login failed: wrong username or password');
                    }
                }
                else {
                    $this->_logger->error(__METHOD__ . 'SFTP login failed: missing password');
                    throw new \Exception('SFTP login failed: missing password');
                }
            }
        }
        catch (Exception $e) {
            $this->_logger->error(__METHOD__ . $e->getMessage());
            throw new \Exception('SFTP login failed: '.$e->getMessage());
        }
    }

    /**
     * @param $dir
     * @return array
     */
    public function listDir($dir)
    {
        $list = [];
        $this->_logger->info(__METHOD__);
        if ($this->connection->chdir($dir)) {
            $list = $this->getOnlyFiles('.');
        }

        return $list;
    }

    /**
     * @param $path
     * @return array
     */
    public function getOnlyFiles($path)
    {
        $files = [];
        $contents = $this->connection->rawList();
        foreach ($contents as $filename => $info) {
            if($info['type'] != NET_SFTP_TYPE_REGULAR) {
                continue;
            }

            $files[] = $filename;
        }
        return $files;
    }

    /**
     * @return bool
     */
    public function close()
    {
        if($this->connection) {
            $this->connection->disconnect();
            $this->connection = null;
        }
    }

    /**
     * @param $resource
     * @param null $copy
     * @param int $ftp_mode
     * @param bool $keep_conn_open
     * @param bool $unique_filename_pattern
     * @return array|string|string[]|null
     * @throws Exception
     */
    public function receiveResource(
        $resource,
        $copy = null,
        $ftp_mode = FTP_ASCII,
        $keep_conn_open = false,
        $unique_filename_pattern = false,
        $overwrite = false
    ) {
        $this->_logger->info(__METHOD__);

        $this->connection = $this->getConnection();

        $downloadedFile = [];

        if (!is_dir($this->localDir) && trim($this->localDir) !== '' && !mkdir(
                $concurrentDirectory = $this->localDir,
                0777,
                true
            ) && !is_dir($concurrentDirectory)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
        }

        $basefile = basename($resource);
        if ($unique_filename_pattern) {
            $basefile = $this->fixFilename($basefile, $unique_filename_pattern);
        }

        $localFile = $this->localDir . '/' . $basefile;

        if (file_exists($localFile) && !$overwrite) {
            $this->_logger->info('FTP - file ' . $basefile . ' already exists');
            throw new RuntimeException('FTP - file ' . $basefile . ' already exists');
        }

        try {
            if ($this->downloadFile($localFile, $resource, $ftp_mode)) {
                $downloadedFile = $basefile;
                $this->deleteFile($resource);
            }
        } catch (Exception $e) {
            $this->_logger->info($e->getMessage());
        }

        $this->close();

        return $downloadedFile;
    }

    /**
     * This will change the filename to an unique filename appending the unique pattern before extension
     * @param $fileName
     * @param null $unique_filename_pattern
     * @return string|string[]|null
     */
    public function fixFilename($fileName, $unique_filename_pattern = null)
    {
        $fileName = preg_replace('/[^a-z0-9_ \\-\\.]+/i', '', $fileName);

        // Add unique identifier to the filename
        if (!$unique_filename_pattern) {
            return $fileName;
        }

        // No extension? then return it with the unique identifier
        $parts = explode('.', $fileName);
        if (count($parts) === 1) {
            return $fileName . $unique_filename_pattern;
        }

        // Have extension, then add unique identifier before extension
        $last = array_pop($parts);
        $parts = [implode('.', $parts), $last];

        return sprintf('%s%s.%s', $parts[0], $unique_filename_pattern, $parts[1]);
    }

    /**
     * Scarica un file dall'FTP
     *
     * @param $local_file
     * @param $server_file
     * @param int $mode
     * @return bool
     * @throws Exception
     */
    public function downloadFile($local_file, $server_file, $mode = FTP_ASCII)
    {
        $this->_logger->info(__METHOD__);

        if (!$this->connection->chdir($this->ftpDir)) {
            $this->_logger->info(
                __METHOD__ . "FTP HELPER: function sftp chdir failed - downloading {$server_file} > {$local_file}"
            );
            throw new Exception("FTP HELPER: function sftp chdir failed - downloading {$server_file} > {$local_file}");
        }

        $res = $this->connection->get($server_file, $local_file);
        if (!$res) {
            $this->_logger->info(
                __METHOD__ . "FTP HELPER: function sftp get failed - downloading {$server_file} > {$local_file}"
            );
            throw new Exception("FTP HELPER: function sftp get failed - downloading {$server_file} > {$local_file}");
        }

        chmod($local_file, 0777);
        return true;
    }

    /**
     *
     * @param string $serverFilename
     * @return bool
     */
    public function deleteFile($serverFilename)
    {
        $this->_logger->info(__METHOD__);

        return $this->connection->delete($serverFilename, false);
    }

    /**
     *
     *
     */
    public function isReceived()
    {
    }

    /**
     * @param $resource
     * @return bool
     * @throws Exception
     */
    public function sendResource($resource)
    {
        $this->_logger->info(__METHOD__);

        $this->connection = $this->getConnection($this->host);
        $this->login($this->username, $this->password);

        if (!is_dir($this->localDir) && trim($this->localDir) !== '' && !mkdir(
                $concurrentDirectory = $this->localDir,
                0777,
                true
            ) && !is_dir($concurrentDirectory)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
        }

        $baseFile = basename($resource);
        $localFile = $this->localDir . DIRECTORY_SEPARATOR . $baseFile;
        $remoteFile = $this->ftpDir . DIRECTORY_SEPARATOR . $baseFile;

        if (file_exists($localFile) && $this->uploadFile($remoteFile, $localFile)) {
            unlink($localFile);
            return true;
        }
        return false;
    }

    /**
     * @param $remote_file
     * @param $xmlPath
     * @param int $mode
     * @return bool
     * @throws Exception
     */
    public function uploadFile($remote_file, $local_file, $mode = FTP_ASCII)
    {
        $this->_logger->info("Upload del file {$local_file} in {$remote_file}");

        $res = $this->connection->put($remote_file, file_get_contents($local_file));

        if (!$res) {
            $this->_logger->info("FTP HELPER: upload failed {$local_file} > {$remote_file}");
            throw new Exception("FTP HELPER: upload failed {$local_file} > {$remote_file}");
        }

        $this->_logger->info("FTP HELPER: Uploaded {$local_file} > {$remote_file}");
        return true;
    }

    /**
     * @param $dir
     * @return $this
     */
    public function setFtpDir($dir)
    {
        $this->ftpDir = $dir;
        return $this;
    }
}
