<?php

namespace NoahVet\Reef\Test\A_Unit\Domain\Http\Multipart;

use NoahVet\Reef\Domain\Http\Multipart\ContentDecoder;
use PHPUnit\Framework\TestCase;

class ContentDecoderTest extends TestCase
{
    private ContentDecoder $decoder;

    protected function setUp(): void
    {
        $this->decoder = new ContentDecoder();
    }

    // --- decode() ---

    public function testDecodeBase64(): void
    {
        $raw = 'aGVsbG8gd29ybGQ=';
        self::assertEquals('hello world', $this->decoder->decode($raw, 'base64', 'text/plain'));
    }

    public function testDecodeQuotedPrintable(): void
    {
        $raw = 'This is the c=C3=B4nt=C3=A9nt';
        self::assertEquals('This is the côntént', $this->decoder->decode($raw, 'quoted-printable', 'text/html; charset=utf-8'));
    }

    public function testDecode7bit(): void
    {
        self::assertEquals('plain text', $this->decoder->decode('plain text', '7bit', 'text/plain'));
    }

    public function testDecodeBinary(): void
    {
        $raw = "\xff\xfe\xab";
        self::assertEquals($raw, $this->decoder->decode($raw, 'binary', null));
    }

    public function testDecodeCharsetConversionForTextType(): void
    {
        // iso-8859-1 + base64 encoded "côntént"
        $raw = 'Y/RudOludA==';

        $decoded = $this->decoder->decode($raw, 'base64', 'text/html; charset=iso-8859-1');

        self::assertEquals('côntént', $decoded);
    }

    public function testDecodeNoBinaryCorruptionForPdf(): void
    {
        $binary = "\xff\xfe\xab\xcd\xef\x00\x01";
        $raw = \base64_encode($binary);

        $decoded = $this->decoder->decode($raw, 'base64', 'application/pdf');

        self::assertEquals($binary, $decoded, 'Binary PDF bytes must not be charset-converted');
    }

    public function testDecodeNoBinaryCorruptionForOctetStream(): void
    {
        $binary = "\xff\xfe\xab";
        $raw = \base64_encode($binary);

        self::assertEquals($binary, $this->decoder->decode($raw, 'base64', 'application/octet-stream'));
    }

    public function testDecodeNoBinaryCorruptionForImageType(): void
    {
        $binary = "\x89PNG\r\n\x1a\n";
        $raw = \base64_encode($binary);

        self::assertEquals($binary, $this->decoder->decode($raw, 'base64', 'image/png'));
    }

    public function testDecodeNo7bitCharsetConversion(): void
    {
        $raw = 'plain ascii';
        self::assertEquals($raw, $this->decoder->decode($raw, '7bit', 'text/plain; charset=iso-8859-1'));
    }

    public function testDecodeNoBinaryEncodingCharsetConversion(): void
    {
        $binary = "\xff\xfe";
        self::assertEquals($binary, $this->decoder->decode($binary, 'binary', 'text/plain'));
    }

    public function testDecodeToStreamBase64(): void
    {
        $content = \base64_encode('hello world');
        $stream = $this->streamFromString($content);

        $result = $this->decoder->decodeToStream($stream, 0, 'base64', 'text/plain');

        self::assertIsResource($result);
        self::assertEquals('hello world', \stream_get_contents($result));
    }

    public function testDecodeToStreamQuotedPrintable(): void
    {
        $content = 'c=C3=B4nt';
        $stream = $this->streamFromString($content);

        $result = $this->decoder->decodeToStream($stream, 0, 'quoted-printable', 'text/plain; charset=utf-8');

        self::assertEquals('cônt', \stream_get_contents($result));
    }

    public function testDecodeToStreamBinaryPdfNotCorrupted(): void
    {
        $binary = "\xff\xfe\xab\xcd\xef\x00\x01";
        $content = \base64_encode($binary);
        $stream = $this->streamFromString($content);

        $result = $this->decoder->decodeToStream($stream, 0, 'base64', 'application/pdf');

        self::assertEquals($binary, \stream_get_contents($result));
    }

    public function testDecodeToStreamWithBodyOffset(): void
    {
        $prefix = "HEADER\n";
        $content = $prefix.\base64_encode('data');
        $stream = $this->streamFromString($content);

        $result = $this->decoder->decodeToStream($stream, \strlen($prefix), 'base64', 'text/plain');

        self::assertEquals('data', \stream_get_contents($result));
    }

    public function testDecodeToStreamBase64IsSeekable(): void
    {
        $stream = $this->streamFromString(\base64_encode('hello'));

        $result = $this->decoder->decodeToStream($stream, 0, 'base64', 'application/pdf');

        self::assertEquals('hello', \stream_get_contents($result));
        \rewind($result);
        self::assertEquals('hello', \stream_get_contents($result), 'Stream must be re-readable after rewind');
    }

    public function testDecodeToStreamQuotedPrintableIsSeekable(): void
    {
        $stream = $this->streamFromString('caf=C3=A9');

        $result = $this->decoder->decodeToStream($stream, 0, 'quoted-printable', 'text/plain; charset=utf-8');

        self::assertEquals('café', \stream_get_contents($result));
        \rewind($result);
        self::assertEquals('café', \stream_get_contents($result), 'Stream must be re-readable after rewind');
    }

    // --- decodeToStream() ---

    private function streamFromString(string $content, int $offset = 0): mixed
    {
        $stream = \fopen('php://temp', 'rw');
        \fwrite($stream, $content);

        return $stream;
    }
}
