<?php

declare(strict_types=1);

/**
 * This file is part of CodeIgniter 4 framework.
 *
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace CodeIgniter\HTTP\Files;

use CodeIgniter\Test\CIUnitTestCase;
use Config\Mimes;
use PHPUnit\Framework\Attributes\Group;

/**
 * @internal
 */
#[Group('Others')]
final class FileCollectionTest extends CIUnitTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        $_FILES = [];
    }

    public function testAllReturnsArrayWithNoFiles(): void
    {
        $files = new FileCollection();

        $this->assertSame([], $files->all());
    }

    public function testAllReturnsValidSingleFile(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();
        $files      = $collection->all();
        $this->assertCount(1, $files);

        $file = array_shift($files);
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame('someFile.txt', $file->getName());
        $this->assertSame(124, $file->getSize());
    }

    public function testAllReturnsValidMultipleFilesSameName(): void
    {
        $_FILES = [
            'userfile' => [
                'name' => [
                    'fileA.txt',
                    'fileB.txt',
                ],
                'type' => [
                    'text/plain',
                    'text/csv',
                ],
                'size' => [
                    '124',
                    '248',
                ],
                'tmp_name' => [
                    '/tmp/fileA.txt',
                    '/tmp/fileB.txt',
                ],
                'error' => 0,
            ],
        ];

        $collection = new FileCollection();
        $files      = $collection->all();
        $this->assertCount(1, $files);
        $this->assertSame('userfile', key($files));

        $files = array_shift($files);
        $this->assertCount(2, $files);

        $file = $files[0];
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame('fileA.txt', $file->getName());
        $this->assertSame('/tmp/fileA.txt', $file->getTempName());
        $this->assertSame('txt', $file->getClientExtension());
        $this->assertSame('text/plain', $file->getClientMimeType());
        $this->assertSame(124, $file->getSize());
    }

    public function testAllReturnsValidMultipleFilesDifferentName(): void
    {
        $_FILES = [
            'userfile1' => [
                'name'     => 'fileA.txt',
                'type'     => 'text/plain',
                'size'     => 124,
                'tmp_name' => '/tmp/fileA.txt',
                'error'    => 0,
            ],
            'userfile2' => [
                'name'     => 'fileB.txt',
                'type'     => 'text/csv',
                'size'     => 248,
                'tmp_name' => '/tmp/fileB.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();
        $files      = $collection->all();
        $this->assertCount(2, $files);
        $this->assertSame('userfile1', key($files));

        $file = array_shift($files);
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame('fileA.txt', $file->getName());
        $this->assertSame('fileA.txt', $file->getClientName());
        $this->assertSame('/tmp/fileA.txt', $file->getTempName());
        $this->assertSame('txt', $file->getClientExtension());
        $this->assertSame('text/plain', $file->getClientMimeType());
        $this->assertSame(124, $file->getSize());

        $file = array_pop($files);
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame('fileB.txt', $file->getName());
        $this->assertSame('fileB.txt', $file->getClientName());
        $this->assertSame('/tmp/fileB.txt', $file->getTempName());
        $this->assertSame('txt', $file->getClientExtension());
        $this->assertSame('text/csv', $file->getClientMimeType());
        $this->assertSame(248, $file->getSize());
    }

    public function testExtensionGuessing(): void
    {
        $_FILES = [
            'userfile1' => [
                'name'     => 'fileA.txt',
                'type'     => 'text/plain',
                'size'     => 4,
                'tmp_name' => SUPPORTPATH . 'HTTP/Files/tmp/fileA.txt',
                'error'    => 0,
            ],
            'userfile2' => [
                'name'     => 'fileB.txt',
                'type'     => 'text/csv',
                'size'     => 9,
                'tmp_name' => SUPPORTPATH . 'HTTP/Files/tmp/fileB.txt',
                'error'    => 0,
            ],
            'userfile3' => [
                'name'     => 'fileC.csv',
                'type'     => 'text/csv',
                'size'     => 16,
                'tmp_name' => SUPPORTPATH . 'HTTP/Files/tmp/fileC.csv',
                'error'    => 0,
            ],
            'userfile4' => [
                'name'     => 'fileD.zip',
                'type'     => 'application/zip',
                'size'     => 441,
                'tmp_name' => SUPPORTPATH . 'HTTP/Files/tmp/fileD.zip',
                'error'    => 0,
            ],
            'userfile5' => [
                'name'     => 'fileE.zip.rar',
                'type'     => 'application/rar',
                'size'     => 441,
                'tmp_name' => SUPPORTPATH . 'HTTP/Files/tmp/fileE.zip.rar',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();

        // proposed extension matches finfo_open mime type (text/plain)
        $file = $collection->getFile('userfile1');
        $this->assertInstanceOf(UploadedFile::class, $file);
        $this->assertSame('txt', $file->getExtension());

        // proposed extension matches finfo_open mime type (text/plain)
        $file = $collection->getFile('userfile2');
        $this->assertInstanceOf(UploadedFile::class, $file);
        $this->assertSame('txt', $file->getExtension());
        // but not client mime type
        $this->assertSame('csv', Mimes::guessExtensionFromType($file->getClientMimeType(), $file->getClientExtension()));

        // proposed extension does not match finfo_open mime type (text/plain)
        // but can be resolved by reverse searching
        $file = $collection->getFile('userfile3');
        $this->assertInstanceOf(UploadedFile::class, $file);
        $this->assertSame('csv', $file->getExtension());

        // proposed extension matches finfo_open mime type (application/zip)
        $file = $collection->getFile('userfile4');
        $this->assertInstanceOf(UploadedFile::class, $file);
        $this->assertSame('zip', $file->getExtension());

        // proposed extension matches client mime type, but not finfo_open mime type (application/zip)
        // this is a zip file (userFile4) but has been renamed to 'rar'
        $file = $collection->getFile('userfile5');
        $this->assertInstanceOf(UploadedFile::class, $file);
        $this->assertSame('zip', $file->getExtension());
        $this->assertSame('rar', Mimes::guessExtensionFromType($file->getClientMimeType(), $file->getClientExtension()));
        $this->assertSame('zip', $file->guessExtension());
    }

    public function testAllReturnsValidSingleFileNestedName(): void
    {
        $_FILES = [
            'userfile' => [
                'name' => [
                    'foo' => [
                        'bar' => 'fileA.txt',
                    ],
                ],
                'type' => [
                    'foo' => [
                        'bar' => 'text/plain',
                    ],
                ],
                'size' => [
                    'foo' => [
                        'bar' => 124,
                    ],
                ],
                'tmp_name' => [
                    'foo' => [
                        'bar' => '/tmp/fileA.txt',
                    ],
                ],
                'error' => 0,
            ],
        ];

        $collection = new FileCollection();
        $files      = $collection->all();
        $this->assertCount(1, $files);
        $this->assertSame('userfile', key($files));

        $this->assertArrayHasKey('bar', $files['userfile']['foo']);

        $file = $files['userfile']['foo']['bar'];
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame('fileA.txt', $file->getName());
        $this->assertSame('/tmp/fileA.txt', $file->getTempName());
        $this->assertSame('txt', $file->getClientExtension());
        $this->assertSame('text/plain', $file->getClientMimeType());
        $this->assertSame(124, $file->getSize());
    }

    public function testHasFileWithSingleFile(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();

        $this->assertTrue($collection->hasFile('userfile'));
        $this->assertFalse($collection->hasFile('foo'));
    }

    public function testHasFileWithMultipleFilesWithDifferentNames(): void
    {
        $_FILES = [
            'userfile1' => [
                'name'     => 'fileA.txt',
                'type'     => 'text/plain',
                'size'     => 124,
                'tmp_name' => '/tmp/fileA.txt',
                'error'    => 0,
            ],
            'userfile2' => [
                'name'     => 'fileB.txt',
                'type'     => 'text/csv',
                'size'     => 248,
                'tmp_name' => '/tmp/fileB.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();

        $this->assertTrue($collection->hasFile('userfile1'));
        $this->assertTrue($collection->hasFile('userfile2'));
    }

    public function testHasFileWithSingleFileNestedName(): void
    {
        $_FILES = [
            'userfile' => [
                'name' => [
                    'foo' => [
                        'bar' => 'fileA.txt',
                    ],
                ],
                'type' => [
                    'foo' => [
                        'bar' => 'text/plain',
                    ],
                ],
                'size' => [
                    'foo' => [
                        'bar' => 124,
                    ],
                ],
                'tmp_name' => [
                    'foo' => [
                        'bar' => '/tmp/fileA.txt',
                    ],
                ],
                'error' => 0,
            ],
        ];

        $collection = new FileCollection();

        $this->assertTrue($collection->hasFile('userfile'));
        $this->assertTrue($collection->hasFile('userfile.foo'));
        $this->assertTrue($collection->hasFile('userfile.foo.bar'));
    }

    public function testErrorString(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => UPLOAD_ERR_INI_SIZE,
            ],
        ];

        $expected = 'The file "someFile.txt" exceeds your upload_max_filesize ini directive.';

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame($expected, $file->getErrorString());
    }

    public function testErrorStringWithUnknownError(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => 123,
            ],
        ];

        $expected = 'The file "someFile.txt" was not uploaded due to an unknown error.';

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame($expected, $file->getErrorString());
    }

    public function testErrorStringWithNoError(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
            ],
        ];

        $expected = 'The file uploaded with success.';

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame($expected, $file->getErrorString());
    }

    public function testError(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => UPLOAD_ERR_INI_SIZE,
            ],
        ];

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame(UPLOAD_ERR_INI_SIZE, $file->getError());
    }

    public function testErrorWithUnknownError(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
            ],
        ];

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame(0, $file->getError());
    }

    public function testErrorWithNoError(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame(UPLOAD_ERR_OK, $file->getError());
    }

    public function testClientPathReturnsValidFullPath(): void
    {
        $_FILES = [
            'userfile' => [
                'name'      => 'someFile.txt',
                'type'      => 'text/plain',
                'size'      => '124',
                'tmp_name'  => '/tmp/myTempFile.txt',
                'full_path' => 'someDir/someFile.txt',
            ],
        ];

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame('someDir/someFile.txt', $file->getClientPath());
    }

    public function testClientPathReturnsNullWhenFullPathIsNull(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
            ],
        ];

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertNull($file->getClientPath());
    }

    public function testFileReturnsValidSingleFile(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();
        $file       = $collection->getFile('userfile');
        $this->assertInstanceOf(UploadedFile::class, $file);

        $this->assertSame('someFile.txt', $file->getName());
        $this->assertSame(124, $file->getSize());
    }

    public function testFileNoExistSingleFile(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'someFile.txt',
                'type'     => 'text/plain',
                'size'     => '124',
                'tmp_name' => '/tmp/myTempFile.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();
        $file       = $collection->getFile('fileuser');
        $this->assertNotInstanceOf(UploadedFile::class, $file);
    }

    public function testFileReturnValidMultipleFiles(): void
    {
        $_FILES = [
            'userfile' => [
                'name' => [
                    'fileA.txt',
                    'fileB.txt',
                ],
                'type' => [
                    'text/plain',
                    'text/csv',
                ],
                'size' => [
                    '124',
                    '248',
                ],
                'tmp_name' => [
                    '/tmp/fileA.txt',
                    '/tmp/fileB.txt',
                ],
                'error' => 0,
            ],
        ];

        $collection = new FileCollection();

        $file1 = $collection->getFile('userfile.0');
        $this->assertInstanceOf(UploadedFile::class, $file1);
        $this->assertSame('fileA.txt', $file1->getName());
        $this->assertSame('/tmp/fileA.txt', $file1->getTempName());
        $this->assertSame('txt', $file1->getClientExtension());
        $this->assertSame('text/plain', $file1->getClientMimeType());
        $this->assertSame(124, $file1->getSize());

        $file2 = $collection->getFile('userfile.1');
        $this->assertInstanceOf(UploadedFile::class, $file2);
        $this->assertSame('fileB.txt', $file2->getName());
        $this->assertSame('/tmp/fileB.txt', $file2->getTempName());
        $this->assertSame('txt', $file2->getClientExtension());
        $this->assertSame('text/csv', $file2->getClientMimeType());
        $this->assertSame(248, $file2->getSize());
    }

    public function testFileWithMultipleFilesNestedName(): void
    {
        $_FILES = [
            'my-form' => [
                'name' => [
                    'details' => [
                        'avatars' => [
                            'fileA.txt',
                            'fileB.txt',
                        ],
                    ],
                ],
                'type' => [
                    'details' => [
                        'avatars' => [
                            'text/plain',
                            'text/plain',
                        ],
                    ],
                ],
                'size' => [
                    'details' => [
                        'avatars' => [
                            125,
                            243,
                        ],
                    ],
                ],
                'tmp_name' => [
                    'details' => [
                        'avatars' => [
                            '/tmp/fileA.txt',
                            '/tmp/fileB.txt',
                        ],
                    ],
                ],
                'error' => [
                    'details' => [
                        'avatars' => [
                            0,
                            0,
                        ],
                    ],
                ],
            ],
        ];

        $collection = new FileCollection();

        $file1 = $collection->getFile('my-form.details.avatars.0');
        $this->assertInstanceOf(UploadedFile::class, $file1);
        $this->assertSame('fileA.txt', $file1->getName());
        $this->assertSame('/tmp/fileA.txt', $file1->getTempName());
        $this->assertSame('txt', $file1->getClientExtension());
        $this->assertSame('text/plain', $file1->getClientMimeType());
        $this->assertSame(125, $file1->getSize());

        $file2 = $collection->getFile('my-form.details.avatars.1');
        $this->assertInstanceOf(UploadedFile::class, $file2);
        $this->assertSame('fileB.txt', $file2->getName());
        $this->assertSame('/tmp/fileB.txt', $file2->getTempName());
        $this->assertSame('txt', $file2->getClientExtension());
        $this->assertSame('text/plain', $file2->getClientMimeType());
        $this->assertSame(243, $file2->getSize());
    }

    public function testDoesntHaveFile(): void
    {
        $_FILES = [
            'my-form' => [
                'name' => [
                    'details' => [
                        'avatars' => [
                            'fileA.txt',
                            'fileB.txt',
                        ],
                    ],
                ],
                'type' => [
                    'details' => [
                        'avatars' => [
                            'text/plain',
                            'text/plain',
                        ],
                    ],
                ],
                'size' => [
                    'details' => [
                        'avatars' => [
                            125,
                            243,
                        ],
                    ],
                ],
                'tmp_name' => [
                    'details' => [
                        'avatars' => [
                            '/tmp/fileA.txt',
                            '/tmp/fileB.txt',
                        ],
                    ],
                ],
                'error' => [
                    'details' => [
                        'avatars' => [
                            0,
                            0,
                        ],
                    ],
                ],
            ],
        ];

        $collection = new FileCollection();

        $this->assertFalse($collection->hasFile('my-form.detailz.avatars.0'));
        $this->assertNotInstanceOf(UploadedFile::class, $collection->getFile('my-form.detailz.avatars.0'));
    }

    public function testGetFileMultipleHasNoFile(): void
    {
        $_FILES = [
            'userfile' => [
                'name' => [
                    'fileA.txt',
                    'fileB.txt',
                ],
                'type' => [
                    'text/plain',
                    'text/csv',
                ],
                'size' => [
                    '124',
                    '248',
                ],
                'tmp_name' => [
                    '/tmp/fileA.txt',
                    '/tmp/fileB.txt',
                ],
                'error' => 0,
            ],
        ];

        $collection = new FileCollection();

        $files = $collection->getFileMultiple('userfiletest');

        $this->assertNull($files);
    }

    public function testGetFileMultipleReturnValidDotNotationSyntax(): void
    {
        $_FILES = [
            'my-form' => [
                'name' => [
                    'details' => [
                        'avatars' => [
                            'fileA.txt',
                            'fileB.txt',
                        ],
                    ],
                ],
                'type' => [
                    'details' => [
                        'avatars' => [
                            'text/plain',
                            'text/plain',
                        ],
                    ],
                ],
                'size' => [
                    'details' => [
                        'avatars' => [
                            125,
                            243,
                        ],
                    ],
                ],
                'tmp_name' => [
                    'details' => [
                        'avatars' => [
                            '/tmp/fileA.txt',
                            '/tmp/fileB.txt',
                        ],
                    ],
                ],
                'error' => [
                    'details' => [
                        'avatars' => [
                            0,
                            0,
                        ],
                    ],
                ],
            ],
        ];

        $collection = new FileCollection();

        $files = $collection->getFileMultiple('my-form.details.avatars');
        $this->assertIsArray($files);
        $this->assertCount(2, $files);

        $this->assertInstanceOf(UploadedFile::class, $files[0]);
        $this->assertSame('fileA.txt', $files[0]->getName());
        $this->assertSame('/tmp/fileA.txt', $files[0]->getTempName());
        $this->assertSame('txt', $files[0]->getClientExtension());
        $this->assertSame('text/plain', $files[0]->getClientMimeType());
        $this->assertSame(125, $files[0]->getSize());

        $this->assertInstanceOf(UploadedFile::class, $files[1]);
        $this->assertSame('fileB.txt', $files[1]->getName());
        $this->assertSame('/tmp/fileB.txt', $files[1]->getTempName());
        $this->assertSame('txt', $files[1]->getClientExtension());
        $this->assertSame('text/plain', $files[1]->getClientMimeType());
        $this->assertSame(243, $files[1]->getSize());
    }

    public function testGetFileMultipleReturnInvalidDotNotationSyntax(): void
    {
        $_FILES = [
            'my-form' => [
                'name' => [
                    'details' => [
                        'avatars' => 'fileA.txt',
                    ],
                ],
                'type' => [
                    'details' => [
                        'avatars' => 'text/plain',
                    ],
                ],
                'size' => [
                    'details' => [
                        'avatars' => 243,
                    ],
                ],
                'tmp_name' => [
                    'details' => [
                        'avatars' => '/tmp/fileA.txt',
                    ],
                ],
                'error' => [
                    'details' => [
                        'avatars' => 0,
                    ],
                ],
            ],
        ];

        $collection = new FileCollection();

        $files = $collection->getFileMultiple('my-form.details.avatars');
        $this->assertNull($files);
    }

    public function testGetFileMultipleReturnValidMultipleFiles(): void
    {
        $_FILES = [
            'userfile' => [
                'name' => [
                    'fileA.txt',
                    'fileB.txt',
                ],
                'type' => [
                    'text/plain',
                    'text/csv',
                ],
                'size' => [
                    '124',
                    '248',
                ],
                'tmp_name' => [
                    '/tmp/fileA.txt',
                    '/tmp/fileB.txt',
                ],
                'error' => 0,
            ],
        ];

        $collection = new FileCollection();

        $files = $collection->getFileMultiple('userfile');
        $this->assertCount(2, $files);
        $this->assertIsArray($files);

        $this->assertInstanceOf(UploadedFile::class, $files[0]);
        $this->assertSame(124, $files[0]->getSize());
        $this->assertSame('fileA.txt', $files[0]->getName());
        $this->assertSame('/tmp/fileA.txt', $files[0]->getTempName());
        $this->assertSame('txt', $files[0]->getClientExtension());
        $this->assertSame('text/plain', $files[0]->getClientMimeType());

        $this->assertInstanceOf(UploadedFile::class, $files[1]);
        $this->assertSame(248, $files[1]->getSize());
        $this->assertSame('fileB.txt', $files[1]->getName());
        $this->assertSame('/tmp/fileB.txt', $files[1]->getTempName());
        $this->assertSame('txt', $files[1]->getClientExtension());
        $this->assertSame('text/csv', $files[1]->getClientMimeType());
    }

    public function testGetFileMultipleReturnInvalidSingleFile(): void
    {
        $_FILES = [
            'userfile' => [
                'name'     => 'fileA.txt',
                'type'     => 'text/csv',
                'size'     => '248',
                'tmp_name' => '/tmp/fileA.txt',
                'error'    => 0,
            ],
        ];

        $collection = new FileCollection();

        $files = $collection->getFileMultiple('userfile');
        $this->assertNull($files);
    }
}
