<?php

namespace App\Support;

use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;

class MultipartFormDataParser
{
    /**
     * Parse multipart/form-data payloads for PUT/PATCH requests.
     *
     * PHP only natively populates $_POST/$_FILES for POST multipart requests,
     * so mobile clients and API tools that send true PUT multipart bodies can
     * otherwise arrive as an empty Laravel Request.
     *
     * @return array<string, string|UploadedFile>
     */
    public static function fromRequest(Request $request): array
    {
        if (!self::shouldParse($request)) {
            return [];
        }

        return self::parseRaw(
            (string) $request->header('Content-Type', ''),
            (string) $request->getContent()
        );
    }

    public static function shouldParse(Request $request): bool
    {
        $method = strtoupper((string) $request->getMethod());
        $contentType = strtolower((string) $request->header('Content-Type', ''));

        if (!in_array($method, ['PUT', 'PATCH'], true)) {
            return false;
        }

        if (!str_contains($contentType, 'multipart/form-data')) {
            return false;
        }

        return $request->all() === [] && $request->allFiles() === [];
    }

    /**
     * @return array<string, string|UploadedFile>
     */
    public static function parseRaw(string $contentType, string $body): array
    {
        if ($body === '') {
            return [];
        }

        if (!preg_match('/boundary="?([^";]+)"?/i', $contentType, $matches)) {
            return [];
        }

        $boundary = '--' . $matches[1];
        $parts = explode($boundary, $body);
        $payload = [];

        foreach ($parts as $part) {
            $part = ltrim($part, "\r\n");

            if ($part === '' || $part === '--' || $part === "--\r\n") {
                continue;
            }

            if (str_ends_with($part, '--')) {
                $part = substr($part, 0, -2);
            }

            $segments = preg_split("/\r\n\r\n|\n\n/", $part, 2);
            if (!is_array($segments) || count($segments) < 2) {
                continue;
            }

            [$rawHeaders, $content] = $segments;
            if (str_ends_with($content, "\r\n")) {
                $content = substr($content, 0, -2);
            } elseif (str_ends_with($content, "\n")) {
                $content = substr($content, 0, -1);
            }

            $headers = self::parseHeaders($rawHeaders);
            $disposition = $headers['content-disposition'] ?? '';

            if (!preg_match('/name="([^"]+)"/', $disposition, $nameMatch)) {
                continue;
            }

            $name = $nameMatch[1];

            if (preg_match('/filename="([^"]*)"/', $disposition, $fileMatch) && $fileMatch[1] !== '') {
                $tmpPath = tempnam(sys_get_temp_dir(), 'mtx_profile_');
                if ($tmpPath === false) {
                    continue;
                }

                file_put_contents($tmpPath, $content);

                $payload[$name] = new UploadedFile(
                    $tmpPath,
                    basename($fileMatch[1]),
                    $headers['content-type'] ?? null,
                    null,
                    true
                );

                continue;
            }

            $payload[$name] = $content;
        }

        return $payload;
    }

    /**
     * @return array<string, string>
     */
    private static function parseHeaders(string $rawHeaders): array
    {
        $headers = [];

        foreach (preg_split("/\r\n|\n/", trim($rawHeaders)) ?: [] as $line) {
            if (!str_contains($line, ':')) {
                continue;
            }

            [$key, $value] = explode(':', $line, 2);
            $headers[strtolower(trim($key))] = trim($value);
        }

        return $headers;
    }
}
