<?php

declare(strict_types=1);

namespace App\Tests\State;

use ApiPlatform\Metadata\Operation;
use App\Entity\AuditLog;
use App\Repository\Contract\AuditLogReadRepositoryInterface;
use App\State\AuditLogProvider;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * @internal
 */
final class AuditLogProviderTest extends TestCase
{
    public function testExposesForensicFields(): void
    {
        $log = new AuditLog()
            ->setUsername('usine')
            ->setAction('create')
            ->setEntityType('work_hour')
            ->setDescription('desc')
            ->setIpAddress('203.0.113.7')
            ->setUserAgent('UA-string')
            ->setDeviceLabel('Mobile · Android · Chrome')
            ->setDeviceId('device-abc')
        ;

        $response = $this->provideWith($this->spyRepository([$log], 1), []);
        $item     = json_decode((string) $response->getContent(), true)['items'][0];

        self::assertSame('203.0.113.7', $item['ipAddress']);
        self::assertSame('UA-string', $item['userAgent']);
        self::assertSame('Mobile · Android · Chrome', $item['deviceLabel']);
        self::assertSame('device-abc', $item['deviceId']);
    }

    public function testPassesNewFiltersToRepository(): void
    {
        $repo = $this->spyRepository();
        $this->provideWith($repo, [
            'employeeId' => '5',
            'employee'   => 'dupont',
            'username'   => 'usine',
            'ip'         => '10.0.',
            'device'     => 'android',
            'entityType' => ['work_hour', 'absence'],
            'action'     => ['create'],
            'perPage'    => '25',
            'page'       => '2',
        ]);

        self::assertSame(5, $repo->findArgs['employeeId']);
        self::assertSame('dupont', $repo->findArgs['employeeName']);
        self::assertSame('usine', $repo->findArgs['username']);
        self::assertSame('10.0.', $repo->findArgs['ip']);
        self::assertSame('android', $repo->findArgs['device']);
        self::assertSame(['work_hour', 'absence'], $repo->findArgs['entityTypes']);
        self::assertSame(['create'], $repo->findArgs['actions']);
        self::assertSame(25, $repo->findArgs['limit']);
        self::assertSame(25, $repo->findArgs['offset']); // page 2, perPage 25 -> offset 25
    }

    public function testBlankFiltersBecomeNull(): void
    {
        $repo = $this->spyRepository();
        $this->provideWith($repo, ['username' => '  ', 'ip' => '', 'device' => '']);

        self::assertNull($repo->findArgs['username']);
        self::assertNull($repo->findArgs['ip']);
        self::assertNull($repo->findArgs['device']);
        self::assertNull($repo->findArgs['entityTypes']);
        self::assertNull($repo->findArgs['actions']);
    }

    public function testPerPageOutOfRangeFallsBackToDefault(): void
    {
        $repo     = $this->spyRepository();
        $response = $this->provideWith($repo, ['perPage' => '999']);

        self::assertSame(10, $repo->findArgs['limit']);
        self::assertSame(10, json_decode((string) $response->getContent(), true)['perPage']);
    }

    public function testDefaultPerPageIs10(): void
    {
        $repo     = $this->spyRepository();
        $response = $this->provideWith($repo, []);

        self::assertSame(10, $repo->findArgs['limit']);
        self::assertSame(10, json_decode((string) $response->getContent(), true)['perPage']);
    }

    private function spyRepository(array $items = [], int $count = 0): AuditLogReadRepositoryInterface
    {
        return new class($items, $count) implements AuditLogReadRepositoryInterface {
            public array $findArgs  = [];
            public array $countArgs = [];

            public function __construct(private array $items, private int $count) {}

            public function findByFilters(?int $employeeId = null, ?DateTimeImmutable $from = null, ?DateTimeImmutable $to = null, ?array $entityTypes = null, ?array $actions = null, ?string $username = null, ?string $ip = null, ?string $device = null, ?string $employeeName = null, int $limit = 50, int $offset = 0): array
            {
                $this->findArgs = compact('employeeId', 'from', 'to', 'entityTypes', 'actions', 'username', 'ip', 'device', 'employeeName', 'limit', 'offset');

                return $this->items;
            }

            public function countByFilters(?int $employeeId = null, ?DateTimeImmutable $from = null, ?DateTimeImmutable $to = null, ?array $entityTypes = null, ?array $actions = null, ?string $username = null, ?string $ip = null, ?string $device = null, ?string $employeeName = null): int
            {
                $this->countArgs = compact('employeeId', 'from', 'to', 'entityTypes', 'actions', 'username', 'ip', 'device', 'employeeName');

                return $this->count;
            }
        };
    }

    private function provideWith(AuditLogReadRepositoryInterface $repo, array $query): JsonResponse
    {
        $stack = new RequestStack();
        $stack->push(Request::create('/api/audit-logs', 'GET', $query));
        $provider = new AuditLogProvider($stack, $repo);

        return $provider->provide($this->createStub(Operation::class));
    }
}
