<?php

declare(strict_types=1);

namespace App\Services;

use App\Helpers\Logger;

class FtpService
{
    private $connection = null;
    private string $host;
    private string $user;
    private string $pass;
    private int $port;
    private bool $ssl;

    private int $uploadedFiles = 0;
    private int $totalFiles = 0;
    /** @var callable|null */
    private $progressCallback = null;

    public function __construct(string $host, string $user, string $pass, int $port = 21, bool $ssl = false)
    {
        $this->host = $host;
        $this->user = $user;
        $this->pass = $pass;
        $this->port = $port;
        $this->ssl = $ssl;
    }

    public function setProgressCallback(callable $callback): void
    {
        $this->progressCallback = $callback;
    }

    public function connect(): void
    {
        if ($this->ssl) {
            $this->connection = @ftp_ssl_connect($this->host, $this->port, 30);
        } else {
            $this->connection = @ftp_connect($this->host, $this->port, 30);
        }

        if (!$this->connection) {
            throw new \RuntimeException("Nie można połączyć z FTP: {$this->host}:{$this->port}");
        }

        if (!@ftp_login($this->connection, $this->user, $this->pass)) {
            throw new \RuntimeException("Logowanie FTP nieudane dla użytkownika: {$this->user}");
        }

        ftp_pasv($this->connection, true);

        Logger::info("FTP connected to {$this->host}", 'installer');
    }

    public function uploadDirectory(string $localDir, string $remoteDir): void
    {
        // Count total files before starting (only on first/top-level call)
        if ($this->totalFiles === 0) {
            $this->totalFiles = $this->countFiles($localDir);
            $this->uploadedFiles = 0;
        }

        $this->ensureDirectory($remoteDir);

        $items = scandir($localDir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $localPath = $localDir . '/' . $item;
            $remotePath = $remoteDir . '/' . $item;

            if (is_dir($localPath)) {
                $this->uploadDirectory($localPath, $remotePath);
            } else {
                $this->uploadFile($localPath, $remotePath);
                $this->uploadedFiles++;

                // Report progress every 50 files to avoid excessive writes
                if ($this->progressCallback && $this->uploadedFiles % 50 === 0) {
                    ($this->progressCallback)($this->uploadedFiles, $this->totalFiles);
                }
            }
        }
    }

    public function uploadFile(string $localPath, string $remotePath): void
    {
        if (!@ftp_put($this->connection, $remotePath, $localPath, FTP_BINARY)) {
            throw new \RuntimeException("FTP upload failed: {$remotePath}");
        }
    }

    public function downloadFile(string $remotePath, string $localPath): bool
    {
        return (bool) @ftp_get($this->connection, $localPath, $remotePath, FTP_BINARY);
    }

    public function ensureDirectory(string $path): void
    {
        $parts = explode('/', trim($path, '/'));
        $current = '';

        foreach ($parts as $part) {
            $current .= '/' . $part;
            @ftp_mkdir($this->connection, $current);
        }
    }

    public function deleteDirectoryContents(string $remoteDir): void
    {
        $remoteDir = $this->normalizePath($remoteDir);
        $items = $this->listDirectory($remoteDir);

        foreach ($items as $item) {
            $this->deletePathRecursive($item['path'], $item['is_dir']);
        }
    }

    public function disconnect(): void
    {
        if ($this->connection) {
            @ftp_close($this->connection);
            $this->connection = null;
            Logger::info("FTP disconnected", 'installer');
        }
    }

    public function __destruct()
    {
        $this->disconnect();
    }

    private function countFiles(string $dir): int
    {
        $count = 0;
        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            $path = $dir . '/' . $item;
            if (is_dir($path)) {
                $count += $this->countFiles($path);
            } else {
                $count++;
            }
        }
        return $count;
    }

    /**
     * @return array<int, array{path: string, is_dir: bool}>
     */
    private function listDirectory(string $remoteDir): array
    {
        $result = [];

        if (function_exists('ftp_mlsd')) {
            $entries = @ftp_mlsd($this->connection, $remoteDir);
            if (is_array($entries)) {
                foreach ($entries as $entry) {
                    $name = (string) ($entry['name'] ?? '');
                    if ($name === '' || $name === '.' || $name === '..') {
                        continue;
                    }

                    $path = $this->normalizePath($remoteDir . '/' . $name);
                    $result[] = [
                        'path' => $path,
                        'is_dir' => (($entry['type'] ?? '') === 'dir'),
                    ];
                }

                return $result;
            }
        }

        $entries = @ftp_nlist($this->connection, $remoteDir);
        if (!is_array($entries)) {
            return [];
        }

        foreach ($entries as $entry) {
            $normalizedEntry = str_replace('\\', '/', (string) $entry);
            $name = basename($normalizedEntry);
            if ($name === '' || $name === '.' || $name === '..') {
                continue;
            }

            $path = str_starts_with($normalizedEntry, '/')
                ? $this->normalizePath($normalizedEntry)
                : $this->normalizePath($remoteDir . '/' . $normalizedEntry);

            $result[] = [
                'path' => $path,
                'is_dir' => $this->isDirectory($path),
            ];
        }

        return $result;
    }

    private function deletePathRecursive(string $path, bool $isDir): void
    {
        if ($isDir) {
            foreach ($this->listDirectory($path) as $child) {
                $this->deletePathRecursive($child['path'], $child['is_dir']);
            }

            if (!@ftp_rmdir($this->connection, $path)) {
                throw new \RuntimeException("FTP remove directory failed: {$path}");
            }
            return;
        }

        if (!@ftp_delete($this->connection, $path)) {
            throw new \RuntimeException("FTP delete file failed: {$path}");
        }
    }

    private function isDirectory(string $path): bool
    {
        $current = @ftp_pwd($this->connection);
        if ($current === false) {
            return false;
        }

        if (@ftp_chdir($this->connection, $path)) {
            @ftp_chdir($this->connection, $current);
            return true;
        }

        return false;
    }

    private function normalizePath(string $path): string
    {
        $path = str_replace('\\', '/', $path);
        $path = preg_replace('#/+#', '/', $path) ?? $path;
        return '/' . ltrim($path, '/');
    }
}
