<?php

declare(strict_types=1);

namespace App\Tests\State;

use ApiPlatform\Metadata\Get;
use App\Entity\Employee;
use App\Entity\User;
use App\Entity\WorkHour;
use App\Repository\Contract\EmployeeScopedRepositoryInterface;
use App\Repository\Contract\WorkHourReadRepositoryInterface;
use App\Service\Contracts\EmployeeContractResolver;
use App\State\WorkHourValidationStatusProvider;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use ReflectionObject;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
 * @internal
 */
final class WorkHourValidationStatusProviderTest extends TestCase
{
    private Security $security;
    private EmployeeScopedRepositoryInterface $employeeRepository;
    private WorkHourReadRepositoryInterface $workHourRepository;
    private RequestStack $requestStack;

    protected function setUp(): void
    {
        $this->security           = $this->createStub(Security::class);
        $this->employeeRepository = $this->createStub(EmployeeScopedRepositoryInterface::class);
        $this->workHourRepository = $this->createStub(WorkHourReadRepositoryInterface::class);
        $this->requestStack       = new RequestStack();
    }

    public function testThrowsWhenAnonymous(): void
    {
        $this->security->method('getUser')->willReturn(null);

        $this->expectException(AccessDeniedHttpException::class);
        $this->buildProvider()->provide(new Get());
    }

    public function testThrowsWhenDateFormatInvalid(): void
    {
        $this->security->method('getUser')->willReturn(new User());
        $this->requestStack->push(new Request(query: ['from' => '01-06-2026', 'to' => '2026-06-30']));

        $this->expectException(UnprocessableEntityHttpException::class);
        $this->buildProvider()->provide(new Get());
    }

    public function testThrowsWhenFromAfterTo(): void
    {
        $this->security->method('getUser')->willReturn(new User());
        $this->requestStack->push(new Request(query: ['from' => '2026-06-30', 'to' => '2026-06-01']));

        $this->expectException(UnprocessableEntityHttpException::class);
        $this->buildProvider()->provide(new Get());
    }

    public function testThrowsWhenRangeTooLarge(): void
    {
        $this->security->method('getUser')->willReturn(new User());
        $this->requestStack->push(new Request(query: ['from' => '2024-01-01', 'to' => '2026-01-01']));

        $this->expectException(UnprocessableEntityHttpException::class);
        $this->buildProvider()->provide(new Get());
    }

    public function testComputesValidatedDays(): void
    {
        $user   = new User();
        $alice  = $this->buildEmployee(1);
        $bob    = $this->buildEmployee(2);
        $driver = $this->buildEmployee(3);

        // 2026-06-01 : Alice + Bob validés → vert.
        // 2026-06-02 : Alice validée, Bob en attente → pas vert.
        // 2026-06-03 : seul un conducteur (validé) → exclu → total non-conducteur 0 → pas vert.
        $workHours = [
            $this->buildWorkHour($alice, '2026-06-01', true),
            $this->buildWorkHour($bob, '2026-06-01', true),
            $this->buildWorkHour($alice, '2026-06-02', true),
            $this->buildWorkHour($bob, '2026-06-02', false),
            $this->buildWorkHour($driver, '2026-06-03', true),
        ];

        $this->security->method('getUser')->willReturn($user);
        $this->requestStack->push(new Request(query: ['from' => '2026-06-01', 'to' => '2026-06-30']));
        $this->employeeRepository->method('findScoped')->with($user)->willReturn([$alice, $bob, $driver]);
        $this->workHourRepository->method('findByDateRangeAndEmployees')->willReturn($workHours);

        $resolver = $this->createStub(EmployeeContractResolver::class);
        $resolver->method('resolveIsDriverForEmployeeAndDate')
            ->willReturnCallback(static fn (Employee $e): bool => 3 === $e->getId())
        ;

        $result = $this->buildProvider($resolver)->provide(new Get());

        self::assertSame('2026-06-01', $result->from);
        self::assertSame('2026-06-30', $result->to);
        self::assertSame(['2026-06-01'], $result->validatedDays);
    }

    public function testComputesValidatedDaysForDriverScope(): void
    {
        $user   = new User();
        $alice  = $this->buildEmployee(1); // non-conducteur
        $driver = $this->buildEmployee(3); // conducteur

        // ?driver=1 : 01/06 conducteur validé → vert ; 02/06 conducteur en attente → non ;
        // 03/06 seule Alice (non-conducteur) validée → ignorée → non.
        $workHours = [
            $this->buildWorkHour($driver, '2026-06-01', true),
            $this->buildWorkHour($driver, '2026-06-02', false),
            $this->buildWorkHour($alice, '2026-06-03', true),
        ];

        $this->security->method('getUser')->willReturn($user);
        $this->requestStack->push(new Request(query: ['from' => '2026-06-01', 'to' => '2026-06-30', 'driver' => '1']));
        $this->employeeRepository->method('findScoped')->willReturn([$alice, $driver]);
        $this->workHourRepository->method('findByDateRangeAndEmployees')->willReturn($workHours);

        $resolver = $this->createStub(EmployeeContractResolver::class);
        $resolver->method('resolveIsDriverForEmployeeAndDate')
            ->willReturnCallback(static fn (Employee $e): bool => 3 === $e->getId())
        ;

        $result = $this->buildProvider($resolver)->provide(new Get());

        self::assertSame(['2026-06-01'], $result->validatedDays);
    }

    public function testEmptyWhenNoWorkHours(): void
    {
        $user = new User();
        $this->security->method('getUser')->willReturn($user);
        $this->requestStack->push(new Request(query: ['from' => '2026-06-01', 'to' => '2026-06-30']));
        $this->employeeRepository->method('findScoped')->willReturn([]);
        $this->workHourRepository->method('findByDateRangeAndEmployees')->willReturn([]);

        $result = $this->buildProvider()->provide(new Get());

        self::assertSame([], $result->validatedDays);
    }

    private function buildProvider(?EmployeeContractResolver $resolver = null): WorkHourValidationStatusProvider
    {
        $resolver ??= $this->createStub(EmployeeContractResolver::class);

        return new WorkHourValidationStatusProvider(
            $this->security,
            $this->requestStack,
            $this->employeeRepository,
            $this->workHourRepository,
            $resolver,
        );
    }

    private function buildEmployee(int $id): Employee
    {
        $employee = new Employee()
            ->setFirstName('Test')
            ->setLastName('Employee')
        ;
        $reflection = new ReflectionObject($employee);
        $property   = $reflection->getProperty('id');
        $property->setAccessible(true);
        $property->setValue($employee, $id);

        return $employee;
    }

    private function buildWorkHour(Employee $employee, string $date, bool $isValid): WorkHour
    {
        return new WorkHour()
            ->setEmployee($employee)
            ->setWorkDate(new DateTimeImmutable($date))
            ->setIsValid($isValid)
        ;
    }
}
