<?php

declare(strict_types=1);

namespace App\Tests\State;

use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Post;
use App\Entity\Absence;
use App\Entity\AbsenceType;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\User;
use App\Enum\HalfDay;
use App\Repository\Contract\AbsenceReadRepositoryInterface;
use App\Repository\Contract\WorkHourReadRepositoryInterface;
use App\Service\AuditLogger;
use App\State\AbsenceWriteProcessor;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
 * @internal
 */
final class AbsenceWriteProcessorTest extends TestCase
{
    private AbsenceWriteProcessor $processor;

    public function testPostSplitsRangeIntoDailyEntries(): void
    {
        $entityManager      = $this->createMock(EntityManagerInterface::class);
        $absenceRepository  = $this->createMock(AbsenceReadRepositoryInterface::class);
        $workHourRepository = $this->createMock(WorkHourReadRepositoryInterface::class);
        $security           = $this->createAdminSecurityStub();
        $this->processor    = new AbsenceWriteProcessor($entityManager, $absenceRepository, $workHourRepository, $security, $this->createStub(AuditLogger::class));

        $absence = $this->buildAbsence('2026-02-16', '2026-02-18', HalfDay::AM, HalfDay::PM);

        $workHourRepository->expects(self::once())
            ->method('hasValidatedInRange')
            ->willReturn(false)
        ;
        $absenceRepository->expects(self::once())
            ->method('findByEmployeeAndDateRange')
            ->willReturn([])
        ;
        $entityManager->expects(self::exactly(3))->method('persist');
        $entityManager->expects(self::once())->method('flush');

        $result = $this->processor->process($absence, new Post());

        self::assertSame($absence, $result);
        self::assertSame('2026-02-16', $absence->getStartDate()->format('Y-m-d'));
        self::assertSame('2026-02-16', $absence->getEndDate()->format('Y-m-d'));
    }

    public function testDeleteThrowsWhenValidated(): void
    {
        $entityManager      = $this->createMock(EntityManagerInterface::class);
        $absenceRepository  = $this->createStub(AbsenceReadRepositoryInterface::class);
        $workHourRepository = $this->createMock(WorkHourReadRepositoryInterface::class);
        $security           = $this->createAdminSecurityStub();
        $this->processor    = new AbsenceWriteProcessor($entityManager, $absenceRepository, $workHourRepository, $security, $this->createStub(AuditLogger::class));

        $absence = $this->buildAbsence('2026-02-16', '2026-02-16', HalfDay::AM, HalfDay::PM);

        $workHourRepository->expects(self::once())
            ->method('hasValidatedInRange')
            ->willReturn(true)
        ;
        $entityManager->expects(self::never())->method('remove');
        $entityManager->expects(self::never())->method('flush');

        $this->expectException(ConflictHttpException::class);
        $this->processor->process($absence, new Delete());
    }

    public function testDeleteRemovesWhenNotValidated(): void
    {
        $entityManager      = $this->createMock(EntityManagerInterface::class);
        $absenceRepository  = $this->createStub(AbsenceReadRepositoryInterface::class);
        $workHourRepository = $this->createMock(WorkHourReadRepositoryInterface::class);
        $security           = $this->createAdminSecurityStub();
        $this->processor    = new AbsenceWriteProcessor($entityManager, $absenceRepository, $workHourRepository, $security, $this->createStub(AuditLogger::class));

        $absence = $this->buildAbsence('2026-02-16', '2026-02-16', HalfDay::AM, HalfDay::PM);

        $workHourRepository->expects(self::once())
            ->method('hasValidatedInRange')
            ->willReturn(false)
        ;
        $entityManager->expects(self::once())->method('remove')->with($absence);
        $entityManager->expects(self::once())->method('flush');

        $result = $this->processor->process($absence, new Delete());

        self::assertNull($result);
    }

    public function testPostThrowsOnInvalidHalfDayOrder(): void
    {
        $entityManager      = $this->createStub(EntityManagerInterface::class);
        $absenceRepository  = $this->createStub(AbsenceReadRepositoryInterface::class);
        $workHourRepository = $this->createStub(WorkHourReadRepositoryInterface::class);
        $security           = $this->createAdminSecurityStub();
        $this->processor    = new AbsenceWriteProcessor($entityManager, $absenceRepository, $workHourRepository, $security, $this->createStub(AuditLogger::class));

        $absence = $this->buildAbsence('2026-02-16', '2026-02-16', HalfDay::PM, HalfDay::AM);

        $this->expectException(UnprocessableEntityHttpException::class);
        $this->processor->process($absence, new Post());
    }

    private function buildAbsence(string $startDate, string $endDate, HalfDay $startHalf, HalfDay $endHalf): Absence
    {
        $contract = new Contract()->setName('35h')->setTrackingMode(Contract::TRACKING_TIME)->setWeeklyHours(35);
        $employee = new Employee()->setFirstName('Test')->setLastName('User')->setContract($contract);
        $type     = new AbsenceType()->setCode('CP')->setLabel('Congé')->setColor('#000')->setCountAsWorkedHours(true);

        return new Absence()
            ->setEmployee($employee)
            ->setType($type)
            ->setComment('x')
            ->setStartDate(new DateTime($startDate))
            ->setEndDate(new DateTime($endDate))
            ->setStartHalf($startHalf)
            ->setEndHalf($endHalf)
        ;
    }

    private function createAdminSecurityStub(): Security
    {
        $security = $this->createStub(Security::class);
        $security->method('getUser')->willReturn(
            new User()->setUsername('admin')->setRoles(['ROLE_ADMIN'])
        );

        return $security;
    }
}
