<?php
// HttpFormContentTest.php
// Created: 2025-03-12
// Updated: 2025-03-15

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\{CoversClass,DataProvider,UsesClass};
use Index\Http\Content\{MultipartFormContent,UrlEncodedFormContent};
use Index\Http\Content\Multipart\{FileMultipartFormData,ValueMultipartFormData};
use Index\Http\Streams\Stream;

#[CoversClass(MultipartFormContent::class)]
#[CoversClass(UrlEncodedFormContent::class)]
#[CoversClass(FileMultipartFormData::class)]
#[CoversClass(ValueMultipartFormData::class)]
#[UsesClass(Stream::class)]
final class HttpFormContentTest extends TestCase {
    /** @return array<array{0: string, 1: array<string, list<?string>>}> */
    public static function urlEncodedStringProvider(): array {
        return [
            [
                'username=meow&password=beans',
                ['username' => ['meow'], 'password' => ['beans']]
            ],
            [
                'username=meow&password=beans&password=soap',
                ['username' => ['meow'], 'password' => ['beans', 'soap']]
            ],
            [
                'arg&arg&arg=maybe&arg&the=ok',
                ['arg' => [null, null, 'maybe', null], 'the' => ['ok']]
            ],
            [
                'array[]=old&array%5B%5D=syntax&array[meow]=soup',
                ['array[]' => ['old', 'syntax'], 'array[meow]' => ['soup']]
            ],
            [
                'plus=this+one+uses+plus+as+space&twenty=this%20uses%20percent%20encoding',
                ['plus' => ['this one uses plus as space'], 'twenty' => ['this uses percent encoding']]
            ],
            [
                '&&=&&=&', // there's no reason why this shouldn't be valid but it is quirky!
                ['' => [null, null, '', null, '', null]]
            ],
            [
                '',
                []
            ],
            [
                ' ',
                [' ' => [null]]
            ],
        ];
    }

    /** @param array<string, string[]> $expected */
    #[DataProvider('urlEncodedStringProvider')]
    public function testUrlEncodedForm(string $formString, array $expected): void {
        $form = UrlEncodedFormContent::parseStream(Stream::createStream($formString));

        $this->assertFalse($form->hasParam('never_has_this'));
        $this->assertEquals(0, $form->getParamCount('never_has_this_either'));
        $this->assertNull($form->getParam('this_is_not_there_either'));
        $this->assertEquals(0401, $form->getFilteredParamAt('abusing_octal_notation_for_teto_reference', 3510, default: 0401));

        foreach($expected as $key => $values) {
            $this->assertTrue($form->hasParam($key));
            $this->assertEquals($form->getParam($key), $values[0] ?? null);

            $count = $form->getParamCount($key);
            $this->assertEquals(count($values), $count);

            for($i = 0; $i < $count; ++$i)
                $this->assertEquals($form->getParamAt($key, $i), $values[$i]);
        }

        $extracted = iterator_to_array($form);
        $this->assertEquals($expected, $extracted);
    }

    public function testMultipartFormEmpty(): void {
        $form = MultipartFormContent::parseStream(
            Stream::createStream("------geckoformboundaryf31072d915603e48fe9e0e5393a89f20--\r\n"),
            '----geckoformboundaryf31072d915603e48fe9e0e5393a89f20'
        );
        $this->assertEquals(0, count($form->params));
    }

    public function testMultipartForm(): void {
        $form = MultipartFormContent::parseStream(
            Stream::createStreamFromFile(__DIR__ . '/HttpFormContentTest-multipart.bin', 'rb'),
            '----geckoformboundaryc23c64d876d81ed7258ada21a9eb0b06'
        );

        $this->assertFalse($form->hasParam('never_has_this'));
        $this->assertEquals(0, $form->getParamCount('never_has_this_either'));
        $this->assertNull($form->getParam('this_is_not_there_either'));
        $this->assertNull($form->getParamData('this_is_not_there_either'));
        $this->assertEquals(0401, $form->getFilteredParamAt('abusing_octal_notation_for_teto_reference', 3510, default: 0401));
        $this->assertEquals(null, $form->getParamDataAt('abusing_octal_notation_for_teto_reference', 3510));

        $this->assertTrue($form->hasParam('meow'));
        $this->assertEquals(1, $form->getParamCount('meow'));
        $this->assertEquals('sfdsfs', $form->getParam('meow'));
        $meow = $form->getParamData('meow');
        $this->assertInstanceOf(ValueMultipartFormData::class, $meow);
        $this->assertEquals('meow', $meow->name);
        $this->assertEquals('sfdsfs', (string)$meow->stream);
        $this->assertEquals('form-data; name="meow"', $meow->getHeaderLine('Content-Disposition'));
        $this->assertEquals('sfdsfs', $meow->value);

        $this->assertTrue($form->hasParam('mewow'));
        $this->assertEquals(1, $form->getParamCount('mewow'));
        $this->assertEquals('https://railgun.sh/sockchat', $form->getParam('mewow'));
        $mewow = $form->getParamData('mewow');
        $this->assertInstanceOf(ValueMultipartFormData::class, $mewow);
        $this->assertEquals('mewow', $mewow->name);
        $this->assertEquals('https://railgun.sh/sockchat', (string)$mewow->stream);
        $this->assertEquals('form-data; name="mewow"', $mewow->getHeaderLine('Content-Disposition'));
        $this->assertEquals('https://railgun.sh/sockchat', $mewow->value);

        $this->assertTrue($form->hasParam('"the'));
        $this->assertEquals(2, $form->getParamCount('"the'));
        $this->assertEquals('value!', $form->getParam('"the'));
        $the2Value = $form->getParamAt('"the', 1);
        $this->assertIsString($the2Value);
        $the2Hash = hash('sha256', $the2Value);
        $this->assertEquals('8e3b3df1ab6be7498566e795cc9fe3b8f55e084d0e1fcf3e5323269cfc1bc884', $the2Hash);
        $the1 = $form->getParamData('"the');
        $this->assertInstanceOf(ValueMultipartFormData::class, $the1);
        $this->assertEquals('"the', $the1->name);
        $this->assertEquals('value!', (string)$the1->stream);
        $this->assertEquals('form-data; name="%22the"', $the1->getHeaderLine('Content-Disposition'));
        $this->assertEquals('value!', $the1->value);
        $the2 = $form->getParamDataAt('"the', 1);
        $this->assertInstanceOf(FileMultipartFormData::class, $the2);
        $this->assertEquals('"the', $the2->name);
        $this->assertEquals('kagaglue.png', $the2->fileName);
        $this->assertEquals('kagaglue.png', $the2->getClientFilename());
        $this->assertEquals(766, $the2->getSize());
        $this->assertEquals('form-data; name="%22the"; filename="kagaglue.png"', $the2->getHeaderLine('Content-Disposition'));
        $this->assertEquals('image/png', $the2->getHeaderLine('Content-Type'));
        $this->assertEquals('image/png', $the2->getClientMediaType());
        $the2Path = tempnam(sys_get_temp_dir(), 'ndx-test-');
        $this->assertIsString($the2Path);
        $the2->moveTo($the2Path);
        $this->assertEquals($the2Hash, hash_file('sha256', $the2Path));

        $expected = [
            'meow' => ['sfdsfs'],
            'mewow' => ['https://railgun.sh/sockchat'],
            '"the' => [
                'value!',
                base64_decode(
                    'iVBORw0KGgoAAAANSUhEUgAAABkAAAAdCAYAAABfeMd1AAAABHNCSVQICAgIfAhkiAAAArVJREFUSEu1'
                  . 'Vj9o00EUfr8gdNFBKtVEsUvjUgfpFBQKKrR1tRlUEEwFB8U4OHXroHSqaAanSgRRI0ZRpCUGFAwKpUJF'
                  . 'cEqqECiJLRgHC6XT2e/0Xe/3ctekBg9+9N37833vvb67C1GLFd9JqoVLZ2YQrNQXFf7+NzIAT6bTaqFU'
                  . '6ogs8NVqZz42lqaL42njeize5wyrrJITz6kEwYfKohOIlUuVmhZf5/M0pfJa3p39o5NkTSQgeDJbMgQH'
                  . '4jEjS8FFtD6kaN/ZOtlEIRIQoDVy2a2StnaInJW0apWPaOjpGW2S1UTsgHb+F5IAe27p9SCpzV3FgL4/'
                  . 'jhIPjyHxEaAd3BIXga0bTiaJiWx9qBLbwOCYHs703mTGuEC2yV0DwtVEUIF9JlyZuypB1kjAZev7eFC3'
                  . 'jJeuZO7BafpcukF2ptADhBeDcRLInImMkyWAaHn/FY0dwTwnzj+nteoCvZyfN8AguHR8icYzGRoYHNSf'
                  . 'JIYO9p5o8w3QaDQ0JrBNTWhZ9+EEwZgaGaFsoUDlcpmUUnRib1YTzBRqdOf+ism3mDui5bfLKQqCgDbu'
                  . 'OR3Hqz71Sx/MHazQJ/TLnApih7Rjbnqaen/cNgEsXLvQY3TF3KZZEjSGV40xNF0gUrUyvX832wTeSoHW'
                  . 'Ig7JoRP2MpX4QNY+3dxok9uK9mE1Xoy6Hf5qtyT59nWG+nsHDMCb0bshsJPPLnvBcbXwajqMaBneCx5f'
                  . 'TAg+37Jt1Yf9oavfS2KDnZtYJ1SDD2BHbyWMGbJNAJ9T1W5tt6vAvukWZhSMNKri2+DRRJfNH5J94HDC'
                  . 'CHtJJKJ99Ujbz9TmwyarCJ0TGSj38km17fFsTdlEMrbtSmSg3KNSJuJq5DMsY/5pD6I9V2Nq16to6Gbf'
                  . '8pxsl0kPClpH/h8f28X0+ss3yuvYiUFO4m81NF/DgbLLlAAAAABJRU5ErkJggg=='
                ),
            ],
        ];
        $extracted = iterator_to_array($form);
        $this->assertEquals($expected, $extracted);
    }
}