<?php
declare(strict_types=1);

require_once __DIR__ . '/config.php';

/** Tamanho máximo por arquivo (5 MB). */
const GPC_UPLOAD_MAX_BYTES = 5242880;

const GPC_UPLOAD_ALLOWED_EXT = ['jpg', 'jpeg', 'png', 'gif', 'webp'];

const GPC_UPLOAD_ALLOWED_MIME = [
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/webp',
];

const GPC_UPLOAD_BLOCKED_EXT = [
    'php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phar',
    'cgi', 'pl', 'py', 'asp', 'aspx', 'jsp', 'sh', 'bash', 'exe', 'htaccess',
];

/**
 * @return array{ok: bool, path: ?string, error: ?string}
 */
function gpc_upload_image(array $file, string $subdir): array
{
    $fail = static function (string $msg): array {
        return ['ok' => false, 'path' => null, 'error' => $msg];
    };

    if (!isset($file['error']) || (int) $file['error'] === UPLOAD_ERR_NO_FILE) {
        return $fail('Nenhum arquivo');
    }

    if ((int) $file['error'] !== UPLOAD_ERR_OK) {
        return $fail(gpc_upload_error_message((int) $file['error']));
    }

    if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
        return $fail('Upload inválido');
    }

    if ((int) ($file['size'] ?? 0) > GPC_UPLOAD_MAX_BYTES) {
        return $fail('Arquivo muito grande (máx. 5 MB)');
    }

    $original = (string) ($file['name'] ?? '');

    if (gpc_upload_has_blocked_extension($original)) {
        return $fail('Tipo de arquivo não permitido');
    }

    $clientExt = strtolower((string) pathinfo($original, PATHINFO_EXTENSION));
    if ($clientExt === '' || !in_array($clientExt, GPC_UPLOAD_ALLOWED_EXT, true)) {
        return $fail('Extensão não permitida');
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);
    if ($mime === false || !in_array($mime, GPC_UPLOAD_ALLOWED_MIME, true)) {
        return $fail('MIME não permitido');
    }

    $info = @getimagesize($file['tmp_name']);
    if ($info === false || empty($info[0]) || empty($info[1])) {
        return $fail('Imagem inválida');
    }

    if ((int) $info[0] > 8000 || (int) $info[1] > 8000) {
        return $fail('Dimensões excedem o limite');
    }

    $ext = gpc_upload_ext_from_mime($mime);
    if ($ext === null) {
        return $fail('Formato não suportado');
    }

    $subdir = gpc_upload_safe_subdir($subdir);
    if ($subdir === null) {
        return $fail('Destino inválido');
    }

    $destDir = GPC_UPLOADS . '/' . $subdir;
    if (!is_dir($destDir) && !mkdir($destDir, 0755, true) && !is_dir($destDir)) {
        return $fail('Não foi possível criar pasta');
    }

    gpc_upload_write_htaccess($destDir);

    $filename = bin2hex(random_bytes(16)) . '.' . $ext;
    $destAbs = $destDir . '/' . $filename;

    if (!gpc_upload_sanitize_to_disk($file['tmp_name'], $destAbs, $mime)) {
        @unlink($destAbs);
        return $fail('Falha ao processar imagem');
    }

    @chmod($destAbs, 0644);

    return [
        'ok'    => true,
        'path'  => 'uploads/' . $subdir . '/' . $filename,
        'error' => null,
    ];
}

function gpc_upload_error_message(int $code): string
{
    switch ($code) {
        case UPLOAD_ERR_INI_SIZE:
        case UPLOAD_ERR_FORM_SIZE:
            return 'Arquivo muito grande';

        case UPLOAD_ERR_PARTIAL:
            return 'Upload incompleto';

        case UPLOAD_ERR_NO_TMP_DIR:
            return 'Pasta temporária indisponível';

        case UPLOAD_ERR_CANT_WRITE:
            return 'Falha ao gravar';

        case UPLOAD_ERR_EXTENSION:
            return 'Extensão bloqueada pelo servidor';

        default:
            return 'Erro no upload';
    }
}

function gpc_upload_has_blocked_extension(string $filename): bool
{
    $lower = strtolower($filename);
    $parts = explode('.', $lower);
    array_shift($parts);

    foreach ($parts as $part) {
        if (in_array($part, GPC_UPLOAD_BLOCKED_EXT, true)) {
            return true;
        }
    }

    return false;
}

function gpc_upload_ext_from_mime(string $mime): ?string
{
    switch ($mime) {
        case 'image/jpeg':
            return 'jpg';

        case 'image/png':
            return 'png';

        case 'image/gif':
            return 'gif';

        case 'image/webp':
            return 'webp';

        default:
            return null;
    }
}

function gpc_upload_safe_subdir(string $subdir): ?string
{
    $subdir = str_replace('\\', '/', trim($subdir, '/'));

    if (
        $subdir === '' ||
        strpos($subdir, '..') !== false ||
        preg_match('#^[a-z0-9_-]+$#i', $subdir) !== 1
    ) {
        return null;
    }

    return $subdir;
}

/** Regrava a imagem via GD para remover conteúdo malicioso embutido. */
function gpc_upload_sanitize_to_disk(string $tmp, string $dest, string $mime): bool
{
    if (!extension_loaded('gd')) {
        return move_uploaded_file($tmp, $dest);
    }

    switch ($mime) {
        case 'image/jpeg':
            $img = @imagecreatefromjpeg($tmp);
            break;

        case 'image/png':
            $img = @imagecreatefrompng($tmp);
            break;

        case 'image/gif':
            $img = @imagecreatefromgif($tmp);
            break;

        case 'image/webp':
            $img = function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($tmp) : false;
            break;

        default:
            $img = false;
            break;
    }

    if ($img === false) {
        return false;
    }

    if ($mime === 'image/png' || $mime === 'image/webp') {
        imagealphablending($img, false);
        imagesavealpha($img, true);
    }

    switch ($mime) {
        case 'image/jpeg':
            $ok = imagejpeg($img, $dest, 90);
            break;

        case 'image/png':
            $ok = imagepng($img, $dest, 6);
            break;

        case 'image/gif':
            $ok = imagegif($img, $dest);
            break;

        case 'image/webp':
            $ok = function_exists('imagewebp') ? imagewebp($img, $dest, 90) : false;
            break;

        default:
            $ok = false;
            break;
    }

    imagedestroy($img);

    return (bool) $ok;
}

function gpc_upload_write_htaccess(string $dir): void
{
    $file = rtrim($dir, '/\\') . '/.htaccess';

    if (is_file($file)) {
        return;
    }

    $rules = <<<'HTACCESS'
Options -Indexes
RemoveHandler .php .phtml .php3 .php4 .php5 .php7 .php8 .phar
RemoveType .php .phtml .php3 .php4 .php5 .php7 .php8 .phar
<FilesMatch "\.(php|phtml|phar|cgi|pl|py|asp|aspx|jsp|sh|exe|htaccess)$">
    Require all denied
</FilesMatch>
HTACCESS;

    @file_put_contents($file, $rules);
}

/** Remove arquivo somente se estiver dentro de uploads/. */
function gpc_upload_delete(?string $relativePath): bool
{
    if ($relativePath === null || $relativePath === '') {
        return false;
    }

    $relativePath = str_replace('\\', '/', $relativePath);

    if (strpos($relativePath, '..') !== false || strpos($relativePath, 'uploads/') !== 0) {
        return false;
    }

    $full = GPC_ROOT . '/' . $relativePath;
    $realRoot = realpath(GPC_UPLOADS);
    $realFile = realpath($full);

    if (
        $realRoot === false ||
        $realFile === false ||
        strpos($realFile, $realRoot . DIRECTORY_SEPARATOR) !== 0
    ) {
        return false;
    }

    if (!is_file($realFile)) {
        return false;
    }

    return unlink($realFile);
}

function gpc_upload_public_path(?string $relativePath): ?string
{
    if ($relativePath === null || $relativePath === '') {
        return null;
    }

    $relativePath = str_replace('\\', '/', $relativePath);

    if (strpos($relativePath, '..') !== false || strpos($relativePath, 'uploads/') !== 0) {
        return null;
    }

    $full = GPC_ROOT . '/' . $relativePath;

    return is_file($full) ? $relativePath : null;
}

/** Serve arquivo de upload com headers seguros (API logo/bg). */
function gpc_upload_serve(?string $relativePath): void
{
    $safe = gpc_upload_public_path($relativePath);

    if ($safe === null) {
        http_response_code(404);
        exit;
    }

    $full = GPC_ROOT . '/' . $safe;
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($full);

    if ($mime === false || !in_array($mime, GPC_UPLOAD_ALLOWED_MIME, true)) {
        http_response_code(403);
        exit;
    }

    header('Content-Type: ' . $mime);
    header('Content-Length: ' . (string) filesize($full));
    header('X-Content-Type-Options: nosniff');
    header('Cache-Control: public, max-age=86400');

    readfile($full);
    exit;
}