<?php

namespace NoahVet\Reef\Contract\Phpunit;

use NoahVet\Reef\Domain\Http\Multipart\ContentDecoder;
use NoahVet\Reef\Domain\Http\Multipart\HeaderParser;
use NoahVet\Reef\Domain\Http\Multipart\HeaderValueParser;
use NoahVet\Reef\Domain\Http\Multipart\MultipartParser;
use NoahVet\Reef\Domain\Http\Multipart\PartExtractor;

class MultipartBody implements RawBodyInterface
{
    /** @var array<string, string>|null */
    private ?array $parameters = null;

    /** @var list<array{name: string, filename: string, mimeType: string, content: string}>|null */
    private ?array $fileParts = null;

    public function __construct(
        public readonly string $boundary,
        public readonly string $rawBody,
        public readonly string $subtype = 'related',
    ) {
    }

    #[\Override]
    public function contentType(): string
    {
        return 'multipart/'.$this->subtype.'; boundary='.$this->boundary;
    }

    #[\Override]
    public function getRawBody(): string
    {
        return $this->rawBody;
    }

    /** @return array<string, string> */
    public function getParameters(): array
    {
        $this->parseParts();

        \assert(null !== $this->parameters);

        return $this->parameters;
    }

    /** @return list<array{name: string, filename: string, mimeType: string, content: string}> */
    public function getFileParts(): array
    {
        $this->parseParts();

        \assert(null !== $this->fileParts);

        return $this->fileParts;
    }

    private function normalizeForParsing(string $raw): string
    {
        // Normalize existing CRLF to LF for uniform processing
        $raw = \str_replace("\r\n", "\n", $raw);
        // Whitespace-only lines (e.g. from YAML block scalars) must be empty for the HeaderParser
        // to detect the end-of-headers blank line (it checks for an empty buffer after rtrim)
        $raw = (string) \preg_replace('/^[ \t]+$/m', '', $raw);

        // Normalize to CRLF as required by the domain MultipartParser
        return \str_replace("\n", "\r\n", $raw);
    }

    private function parseParts(): void
    {
        if (null !== $this->parameters) {
            return;
        }

        $this->parameters = [];
        $this->fileParts = [];

        $stream = \fopen('php://temp', 'r+');
        if (!\is_resource($stream)) {
            throw new \RuntimeException('Failed to open temporary stream for multipart parsing.');
        }
        \fwrite($stream, 'Content-Type: '.$this->contentType()."\r\n\r\n".$this->normalizeForParsing($this->rawBody));

        $hvp = new HeaderValueParser();
        $parser = new MultipartParser(new HeaderParser(), new PartExtractor(), new ContentDecoder($hvp), $hvp);
        $root = $parser->parse($stream);

        foreach ($root->getParts() as $part) {
            $name = $part->getName();
            if (null === $name) {
                continue;
            }

            if ($part->isFile()) {
                $this->fileParts[] = [
                    'name' => $name,
                    'filename' => $part->getFileName() ?? $name,
                    'mimeType' => $part->getMimeType(),
                    'content' => $part->getBody(),
                ];
            } else {
                $this->parameters[$name] = $part->getBody();
            }
        }
    }
}
