<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Tests\Controllers;

use App\Controllers\AuditController;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

/**
 * R01-N12: pin AuditController::validateDateFilters. The repo concatenates
 * "T00:00:00Z" / "T23:59:59Z" onto whatever string it gets and binds the
 * result against the ISO-8601 `occurred_at` column — garbage in silently
 * hides rows. The controller is the gate; this test pins which inputs
 * pass through and which get dropped + flagged.
 */
final class AuditControllerTest extends TestCase
{
    /**
     * @return list<array{string,string,string,string,list<string>,string}>
     */
    public static function dateCases(): array
    {
        // [from, to, expectedFrom, expectedTo, expectedErrorKeys, label]
        return [
            ['',           '',           '',           '',           [],                          'no filters → no errors'],
            ['2026-01-31', '',           '2026-01-31', '',           [],                          'from-only valid'],
            ['',           '2026-12-31', '',           '2026-12-31', [],                          'to-only valid'],
            ['2026-01-01', '2026-12-31', '2026-01-01', '2026-12-31', [],                          'both valid'],
            ['2024/01/01', '',           '',           '',           ['from_date'],               'slashes → from dropped, error reported'],
            ['',           '2024/01/01', '',           '',           ['to_date'],                 'slashes → to dropped, error reported'],
            ['2024/01/01', '2024/12/31', '',           '',           ['from_date', 'to_date'],    'both garbage → both dropped, both errors'],
            ['2026-01-31', 'tomorrow',   '2026-01-31', '',           ['to_date'],                 'one-side invalid → only that side errors, the other passes'],
            ['2026-13-01', '',           '',           '',           ['from_date'],               'impossible month → rejected'],
            ['2026-02-30', '',           '',           '',           ['from_date'],               'impossible day for month → rejected (strict round-trip)'],
            ['2026-1-1',   '',           '',           '',           ['from_date'],               'unpadded → rejected (strict round-trip)'],
            [' 2026-01-01', '',          '',           '',           ['from_date'],               'leading space → rejected'],
            ['2026-01-01 ', '',          '',           '',           ['from_date'],               'trailing space → rejected'],
            ["'; DROP TABLE audit_log; --", '', '',    '',           ['from_date'],               'injection-shaped string → rejected'],
        ];
    }

    /** @param list<string> $expectedErrorKeys */
    #[DataProvider('dateCases')]
    public function testValidateDateFilters(
        string $from,
        string $to,
        string $expectedFrom,
        string $expectedTo,
        array  $expectedErrorKeys,
        string $label,
    ): void {
        $out = AuditController::validateDateFilters($from, $to);

        $this->assertSame($expectedFrom, $out['from'], "{$label}: from");
        $this->assertSame($expectedTo,   $out['to'],   "{$label}: to");
        $this->assertSame(
            $expectedErrorKeys,
            array_keys($out['errors']),
            "{$label}: error keys",
        );

        // Error messages are non-empty when present (keeps the contract for
        // the view, which renders {{ dateErrors.from_date }} verbatim).
        foreach ($out['errors'] as $key => $msg) {
            $this->assertNotSame('', $msg, "{$label}: error message for {$key} is non-empty");
        }
    }

    public function testValidatorPreservesValidValuesVerbatim(): void
    {
        // Boundary that the repo concatenates onto: a leap day. Confirms
        // we don't normalise or reformat — the repo gets exactly what the
        // user typed (after gating).
        $out = AuditController::validateDateFilters('2024-02-29', '2024-02-29');
        $this->assertSame('2024-02-29', $out['from']);
        $this->assertSame('2024-02-29', $out['to']);
        $this->assertSame([], $out['errors']);
    }

    public function testValidatorRejectsNonLeapFebTwentyNine(): void
    {
        // 2025 is not a leap year. createFromFormat is lenient and silently
        // rolls over to 2025-03-01; the round-trip equality check we use
        // is what catches it.
        $out = AuditController::validateDateFilters('2025-02-29', '');
        $this->assertSame('', $out['from']);
        $this->assertArrayHasKey('from_date', $out['errors']);
    }
}
