<?php

namespace Tests\Feature;

use App\Filters\UploadLimitFilter;
use App\Services\AdOps\UploadLimit;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Test\CIUnitTestCase;
use Config\Filters as FiltersConfig;

/**
 * A body over `post_max_size` is discarded by PHP in full — $_POST and $_FILES both
 * come back empty — and the request is then served as if the form had been submitted
 * with nothing in it. The CSRF filter finds no token and answers with the
 * "Resubmission" page QA reported on a large upload, which never mentions size.
 *
 * Needs the framework (redirect(), baseURL()), so it lives in the feature suite.
 */
final class UploadLimitFilterTest extends CIUnitTestCase
{
    /** @var array<string, mixed> */
    private array $post = [];
    /** @var array<string, mixed> */
    private array $files = [];
    private $contentLength;

    protected function setUp(): void
    {
        parent::setUp();
        $this->post          = $_POST;
        $this->files         = $_FILES;
        $this->contentLength = $_SERVER['CONTENT_LENGTH'] ?? null;
    }

    protected function tearDown(): void
    {
        $_POST  = $this->post;
        $_FILES = $this->files;
        if ($this->contentLength === null) {
            unset($_SERVER['CONTENT_LENGTH']);
        } else {
            $_SERVER['CONTENT_LENGTH'] = $this->contentLength;
        }
        parent::tearDown();
    }

    public function testItRunsBeforeCsrfForTheUploadRoutes(): void
    {
        // The whole point: csrf must not get to answer a size problem first.
        // Config\Filters::$filters is applied in declaration order.
        $order = array_keys((new FiltersConfig())->filters);

        $this->assertContains('uploadlimit', $order);
        $this->assertLessThan(
            array_search('csrf', $order, true),
            array_search('uploadlimit', $order, true),
            'uploadlimit must be declared before csrf'
        );
    }

    public function testItCoversEveryUploadRoute(): void
    {
        $paths = (new FiltersConfig())->filters['uploadlimit']['before'];

        $this->assertContains('import-export/upload/*', $paths);
    }

    public function testADiscardedPostBodyIsAnsweredWithTheSizeLimit(): void
    {
        $_POST                     = [];
        $_FILES                    = [];
        $_SERVER['CONTENT_LENGTH'] = (string) (30 * 1024 * 1024);

        $result = (new UploadLimitFilter())->before($this->postRequest());

        $this->assertInstanceOf(ResponseInterface::class, $result);
        $this->assertStringContainsString('import-export/import', (string) $result->getHeaderLine('Location'));
        $this->assertStringContainsString('30 MB', (string) session()->getFlashdata('errorMsg'));
        $this->assertStringContainsString(UploadLimit::maxLabel(), (string) session()->getFlashdata('errorMsg'));
    }

    public function testAnOrdinaryUploadIsLeftAlone(): void
    {
        $_POST                     = ['csrf_test_name' => 'x'];
        $_FILES                    = ['campaigns' => ['name' => 'c.csv']];
        $_SERVER['CONTENT_LENGTH'] = '4096';

        $this->assertNull((new UploadLimitFilter())->before($this->postRequest()));
    }

    public function testAnEmptyPostWithNoBodyIsLeftToTheOtherFilters(): void
    {
        // Nothing was sent at all, so this is not a size problem and claiming it was
        // would hide whatever the real one is.
        $_POST                     = [];
        $_FILES                    = [];
        $_SERVER['CONTENT_LENGTH'] = '0';

        $this->assertNull((new UploadLimitFilter())->before($this->postRequest()));
    }

    public function testAGetRequestIsLeftAlone(): void
    {
        $_POST                     = [];
        $_FILES                    = [];
        $_SERVER['CONTENT_LENGTH'] = (string) (30 * 1024 * 1024);

        $request = service('request');
        $request->setMethod('GET');

        $this->assertNull((new UploadLimitFilter())->before($request));
    }

    private function postRequest()
    {
        $request = service('request');
        $request->setMethod('POST');

        return $request;
    }
}
