<?php

namespace Tests\Unit\AdOps;

use App\Services\AdOps\UploadLimit;
use PHPUnit\Framework\TestCase;

/**
 * The upload limit had three disagreeing answers — a hardcoded 10 MB in the
 * controller, `upload_max_filesize`, and `post_max_size` — which is why a large
 * upload sometimes said "10MB" and sometimes landed on a Resubmission page. These
 * pin the one that is derived from the server.
 */
final class UploadLimitTest extends TestCase
{
    public function testTheLimitIsTheSmallestCeilingIncludingTheProxy(): void
    {
        // Three ceilings, and the user meets whichever is lowest: nginx's
        // client_max_body_size, post_max_size for the request, upload_max_filesize
        // for the file. On the Ad Center hosts nginx is 100m while php80/php82 allow
        // 500M, so leaving nginx out promised five times what actually gets through.
        $ceilings = array_values(array_filter([
            100 * 1024 * 1024,
            self::iniBytes('upload_max_filesize'),
            self::iniBytes('post_max_size'),
        ], static fn ($b) => $b > 0));

        $this->assertSame(min($ceilings), UploadLimit::maxBytes());
    }

    public function testTheLimitNeverExceedsTheProxyCeiling(): void
    {
        // The regression that matters: whatever PHP is configured to allow, a body
        // over nginx's limit dies at nginx with a 413 this app never sees, so the
        // number quoted to the user must not be larger than that.
        $this->assertLessThanOrEqual(100 * 1024 * 1024, UploadLimit::maxBytes());
    }

    public function testAnExactSizeIsNotPrintedWithASpuriousDecimal(): void
    {
        // PHP's `/` returns an int when two ints divide evenly, so the first version
        // of this compared int(1) to float(1.0) and printed "2.0 MB" for the limit.
        $this->assertSame('2 MB', UploadLimit::humanBytes(2 * 1024 * 1024));
        $this->assertSame('1 KB', UploadLimit::humanBytes(1024));
        $this->assertSame('64 MB', UploadLimit::humanBytes(64 * 1024 * 1024));
    }

    public function testAnInexactSizeKeepsOneDecimal(): void
    {
        $this->assertSame('2.5 MB', UploadLimit::humanBytes((int) (2.5 * 1024 * 1024)));
        $this->assertSame('1.5 KB', UploadLimit::humanBytes(1536));
    }

    public function testASmallSizeIsGivenInBytes(): void
    {
        $this->assertSame('0 bytes', UploadLimit::humanBytes(0));
        $this->assertSame('512 bytes', UploadLimit::humanBytes(512));
    }

    public function testTheTooLargeMessageNamesBothSizes(): void
    {
        $message = UploadLimit::tooLarge(25 * 1024 * 1024);

        $this->assertStringContainsString('25 MB', $message);
        $this->assertStringContainsString(UploadLimit::maxLabel(), $message);
    }

    public function testTheDiscardedBodyMessageSaysTheServerDroppedIt(): void
    {
        // The Resubmission page QA saw. The message has to explain that the request
        // never reached the page, because the CSRF answer certainly did not.
        $message = UploadLimit::tooLargeForPostBody(30 * 1024 * 1024);

        $this->assertStringContainsString('30 MB', $message);
        $this->assertStringContainsString('discarded', $message);
    }

    public function testTheFormHintNamesTheLimit(): void
    {
        $this->assertStringContainsString(UploadLimit::maxLabel(), UploadLimit::hint());
    }

    private static function iniBytes(string $key): int
    {
        $raw = trim((string) ini_get($key));
        if ($raw === '' || $raw === '-1' || $raw === '0') {
            return 0;
        }
        $value = (int) $raw;

        return match (strtolower(substr($raw, -1))) {
            'g'     => $value * 1024 * 1024 * 1024,
            'm'     => $value * 1024 * 1024,
            'k'     => $value * 1024,
            default => $value,
        };
    }
}
