<?php

namespace PortalPeople\indicator\services;

use Yii;
use yii\base\BaseObject;
use phpseclib3\Net\SFTP;

class FtpDownloadService extends BaseObject
{
    public string $protocol = 'sftp';
    public string $host = '';
    public int $port = 22;
    public string $username = '';
    public string $password = '';
    public string $remotePath = '/';
    public bool $deleteAfterDownload = false;
    public bool $moveAfterDownload = false;
    public string $moveToPath = '';
    public bool $passiveMode = true;
    public string $inboxPath = '';
    /** Maximum files to download per run. 0 = unlimited. */
    public int $limit = 0;

    public function download(): array
    {
        $result = [
            'success' => false,
            'downloaded' => [],
            'errors' => [],
        ];

        try {
            $protocol = strtolower(trim($this->protocol));

            if ($protocol === 'ftp' || $protocol === 'ftps') {
                return $this->downloadFtp();
            } elseif ($protocol === 'sftp') {
                return $this->downloadSftp();
            } else {
                throw new \RuntimeException("Unsupported protocol: {$this->protocol}");
            }
        } catch (\Throwable $e) {
            Yii::error('FTP download failed: ' . $e->getMessage(), 'indicator.download');
            $result['errors'][] = $e->getMessage();

            return $result;
        }
    }

    private function downloadFtp(): array
    {
        $result = [
            'success' => false,
            'downloaded' => [],
            'errors' => [],
        ];

        $useSsl = strtolower(trim($this->protocol)) === 'ftps';
        $conn = $useSsl
            ? @ftp_ssl_connect($this->host, $this->port, 10)
            : @ftp_connect($this->host, $this->port, 10);
        if (!$conn) {
            $result['errors'][] = "Failed to connect to {$this->host}:{$this->port} (" . ($useSsl ? 'FTPS' : 'FTP') . ')' ;

            return $result;
        }

        if (!@ftp_login($conn, $this->username, $this->password)) {
            ftp_close($conn);
            $result['errors'][] = 'FTP authentication failed';

            return $result;
        }

        if ($this->passiveMode) {
            ftp_pasv($conn, true);
        }

        if (!@ftp_chdir($conn, $this->remotePath)) {
            ftp_close($conn);
            $result['errors'][] = "Cannot change to remote path: {$this->remotePath}";

            return $result;
        }

        $files = @ftp_nlist($conn, '.');
        if (!is_array($files)) {
            $files = [];
        }
        $xmlFiles = array_values(array_filter(
            array_map('basename', $files),
            static fn ($f) => (bool) preg_match('/\.xml$/i', $f)
        ));
        if ($this->limit > 0) {
            $xmlFiles = array_slice($xmlFiles, 0, $this->limit);
        }

        foreach ($xmlFiles as $file) {
            $localFile = $this->inboxPath . DIRECTORY_SEPARATOR . $file;

            if (@ftp_get($conn, $localFile, $file, FTP_BINARY)) {
                $result['downloaded'][] = $file;

                if ($this->moveAfterDownload && $this->moveToPath !== '') {
                    $dest = rtrim($this->moveToPath, '/') . '/' . $file;
                    if (!@ftp_rename($conn, $file, $dest)) {
                        $result['errors'][] = "Downloaded but could not move: {$file} to {$dest}";
                    }
                } elseif ($this->deleteAfterDownload) {
                    @ftp_delete($conn, $file);
                }
            } else {
                $result['errors'][] = "Failed to download: {$file}";
            }
        }

        ftp_close($conn);
        $result['success'] = empty($result['errors']);

        return $result;
    }

    private function downloadSftp(): array
    {
        $result = [
            'success' => false,
            'downloaded' => [],
            'errors' => [],
        ];

        // phpseclib uses undefined array key accesses internally which Yii's error
        // handler converts to exceptions. Restore the previous handler around SFTP ops.
        $yiiHandler = set_error_handler(null);

        try {
            $sftp = new SFTP($this->host, $this->port, 10);

            if (!$sftp->login($this->username, $this->password)) {
                $result['errors'][] = 'SFTP authentication failed';

                return $result;
            }

            if (!$sftp->chdir($this->remotePath)) {
                $result['errors'][] = "Cannot change to remote path: {$this->remotePath}";

                return $result;
            }

            $files = $sftp->nlist('.');
            if (!is_array($files)) {
                $files = [];
            }
            $xmlFiles = array_values(array_filter(
                $files,
                static fn ($f) => (bool) preg_match('/\.xml$/i', $f)
            ));
            if ($this->limit > 0) {
                $xmlFiles = array_slice($xmlFiles, 0, $this->limit);
            }

            foreach ($xmlFiles as $file) {
                $localFile = $this->inboxPath . DIRECTORY_SEPARATOR . $file;
                $remoteFile = rtrim($this->remotePath, '/') . '/' . $file;

                if ($sftp->get($file, $localFile)) {
                    $result['downloaded'][] = $file;

                    if ($this->moveAfterDownload && $this->moveToPath !== '') {
                        $dest = rtrim($this->moveToPath, '/') . '/' . $file;
                        if (!$sftp->rename($file, $dest)) {
                            $result['errors'][] = "Downloaded but could not move: {$file} to {$dest}";
                        }
                    } elseif ($this->deleteAfterDownload) {
                        $sftp->delete($file);
                    }
                } else {
                    $result['errors'][] = "Failed to download: {$file}";
                }
            }
        } catch (\Throwable $e) {
            $result['errors'][] = 'SFTP error: ' . $e->getMessage();
        } finally {
            // Restore Yii's error handler
            if ($yiiHandler !== null) {
                set_error_handler($yiiHandler);
            }
        }

        $result['success'] = empty($result['errors']);

        return $result;
    }
}
