<?php

declare(strict_types=1);

namespace Core;

final class Upload
{
    public static function store(array $file, string $directory, array $allowedMimeTypes, int $maxSizeBytes): array
    {
        $error = Validator::file($file, $allowedMimeTypes, $maxSizeBytes);
        if ($error) {
            throw new \RuntimeException($error);
        }

        $tmpName = (string) ($file['tmp_name'] ?? '');
        if ($tmpName === '' || !is_file($tmpName)) {
            throw new \RuntimeException('Arquivo temporário inválido para upload.');
        }

        if (!is_uploaded_file($tmpName)) {
            throw new \RuntimeException('Upload inválido ou expirado. Reenvie o arquivo e tente novamente.');
        }

        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mime = (string) $finfo->file($tmpName);
        $clientName = self::sanitizeClientFilename((string) ($file['name'] ?? 'arquivo'));
        $extension = match ($mime) {
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'application/pdf' => 'pdf',
            default => pathinfo($clientName, PATHINFO_EXTENSION) ?: 'bin',
        };

        $relativeDir = trim($directory, '/') . '/' . date('Y/m');
        $absoluteDir = self::storageRoot() . '/' . $relativeDir;

        if (!is_dir($absoluteDir) && !mkdir($absoluteDir, 0775, true) && !is_dir($absoluteDir)) {
            throw new \RuntimeException('Não foi possível preparar a pasta de uploads.');
        }

        $storedName = bin2hex(random_bytes(20)) . '.' . $extension;
        $absolutePath = $absoluteDir . '/' . $storedName;

        if (!move_uploaded_file($tmpName, $absolutePath)) {
            throw new \RuntimeException('Não foi possível salvar o arquivo.');
        }

        if (!is_file($absolutePath)) {
            throw new \RuntimeException('O arquivo foi enviado, mas não ficou disponível no servidor.');
        }

        $storedSize = (int) @filesize($absolutePath);
        if ($storedSize <= 0) {
            @unlink($absolutePath);
            throw new \RuntimeException('O arquivo enviado está vazio ou corrompido. Gere o documento novamente e tente enviar.');
        }

        @chmod($absolutePath, 0640);

        return [
            'original_name' => $clientName,
            'stored_name' => $storedName,
            'relative_path' => $relativeDir . '/' . $storedName,
            'mime_type' => $mime,
            'size_bytes' => $storedSize,
        ];
    }

    private static function sanitizeClientFilename(string $name): string
    {
        $name = basename(str_replace(["\r", "\n", "\0"], '', $name));
        $name = preg_replace('/[\x00-\x1F\x7F]+/u', '-', $name) ?? 'arquivo';
        $name = trim($name);

        return $name !== '' ? $name : 'arquivo';
    }

    public static function storageRoot(): string
    {
        return dirname(__DIR__) . '/storage';
    }

    public static function absolutePath(string $relativePath): ?string
    {
        $relativePath = trim(str_replace('\\', '/', $relativePath), '/');
        if ($relativePath === '') {
            return null;
        }

        $segments = array_values(array_filter(explode('/', $relativePath), static fn (string $segment): bool => $segment !== ''));
        if ($segments === []) {
            return null;
        }

        foreach ($segments as $segment) {
            if ($segment === '.' || $segment === '..' || str_contains($segment, "\0")) {
                return null;
            }
        }

        $relativeSafePath = implode('/', $segments);
        return rtrim(self::storageRoot(), '/') . '/' . $relativeSafePath;
    }

    public static function deleteRelativePath(?string $relativePath): void
    {
        if ($relativePath === null || $relativePath === '') {
            return;
        }

        $absolutePath = self::absolutePath($relativePath);
        if ($absolutePath !== null && is_file($absolutePath)) {
            @unlink($absolutePath);
        }
    }
}
