<?php

namespace MintyPHP\Service\Import;

class ImportTempFileService
{
    public const MAX_UPLOAD_BYTES = 10485760; // 10 MB
    private const TMP_SUBDIR = '/imports/tmp';
    private const MAX_FILE_AGE_SECONDS = 86400;

    /**
     * @param array<string, mixed> $upload
     * @return array{ok:bool, path?:string, code?:string}
     */
    public function storeUploadedFile(array $upload): array
    {
        $tmpName = (string) ($upload['tmp_name'] ?? '');
        $name = (string) ($upload['name'] ?? '');
        $size = (int) ($upload['size'] ?? 0);
        $error = (int) ($upload['error'] ?? UPLOAD_ERR_NO_FILE);

        if ($error === UPLOAD_ERR_NO_FILE || $tmpName === '') {
            return ['ok' => false, 'code' => 'upload_missing'];
        }
        if ($error !== UPLOAD_ERR_OK) {
            return ['ok' => false, 'code' => 'invalid_file_type'];
        }
        if ($size <= 0) {
            return ['ok' => false, 'code' => 'empty_csv'];
        }
        if ($size > self::MAX_UPLOAD_BYTES) {
            return ['ok' => false, 'code' => 'upload_too_large'];
        }
        if (!$this->isAllowedExtension($name)) {
            return ['ok' => false, 'code' => 'invalid_file_type'];
        }

        $dir = $this->tmpDir();
        if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) {
            return ['ok' => false, 'code' => 'unexpected_error'];
        }

        // Lazy cleanup on each upload — removes abandoned files without needing a separate cron job.
        $this->cleanupExpired();

        try {
            $target = $dir . '/import_' . bin2hex(random_bytes(16)) . '.csv';
        } catch (\Throwable $exception) {
            $target = $dir . '/import_' . str_replace('.', '', uniqid('', true)) . '.csv';
        }
        // Real uploads use move_uploaded_file (validates PHP upload context).
        // Fallback rename/copy path handles test uploads without a real HTTP context.
        $moved = false;
        if (is_uploaded_file($tmpName)) {
            $moved = @move_uploaded_file($tmpName, $target);
        } elseif (is_file($tmpName) && is_readable($tmpName)) {
            $moved = @rename($tmpName, $target);
            if (!$moved) {
                $moved = @copy($tmpName, $target);
            }
        }

        if (!$moved) {
            return ['ok' => false, 'code' => 'invalid_file_type'];
        }

        @chmod($target, 0640);
        return ['ok' => true, 'path' => $target];
    }

    public function delete(string $path): void
    {
        $path = trim($path);
        if ($path === '') {
            return;
        }
        if (!$this->isInTmpDir($path)) {
            return;
        }
        if (is_file($path)) {
            @unlink($path);
        }
    }

    public function cleanupExpired(): void
    {
        $dir = $this->tmpDir();
        if (!is_dir($dir)) {
            return;
        }

        $files = glob($dir . '/*');
        if (!is_array($files)) {
            return;
        }

        $threshold = time() - self::MAX_FILE_AGE_SECONDS;
        foreach ($files as $file) {
            if (!is_file($file)) {
                continue;
            }
            $modifiedAt = @filemtime($file);
            if ($modifiedAt !== false && $modifiedAt < $threshold) {
                @unlink($file);
            }
        }
    }

    public function storageBase(): string
    {
        if (defined('APP_STORAGE_PATH') && APP_STORAGE_PATH) {
            return rtrim((string) APP_STORAGE_PATH, '/');
        }
        return rtrim(dirname(__DIR__, 3) . '/storage', '/');
    }

    public function tmpDir(): string
    {
        return $this->storageBase() . self::TMP_SUBDIR;
    }

    private function isAllowedExtension(string $name): bool
    {
        $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
        return in_array($extension, ['csv', 'txt'], true);
    }

    // Resolves both paths via realpath to prevent path traversal attacks (e.g. "../../../etc/passwd").
    private function isInTmpDir(string $path): bool
    {
        $tmpDir = $this->tmpDir();
        $realTmpDir = realpath($tmpDir);
        $realPath = realpath($path);
        if ($realTmpDir === false || $realPath === false) {
            return false;
        }
        return str_starts_with($realPath, $realTmpDir . DIRECTORY_SEPARATOR);
    }
}
