<?php

declare(strict_types=1);

namespace App\State;

use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Enum\ContractNature;
use App\Repository\Contract\EmployeeContractPeriodReadRepositoryInterface;
use App\Service\AuditLogger;
use App\Service\Contracts\EmployeeContractChangeRequestFactory;
use App\Service\Contracts\EmployeeContractPeriodManagerInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

final readonly class EmployeeWriteProcessor implements ProcessorInterface
{
    public function __construct(
        #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
        private ProcessorInterface $persistProcessor,
        #[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')]
        private ProcessorInterface $removeProcessor,
        private EntityManagerInterface $entityManager,
        private EmployeeContractPeriodReadRepositoryInterface $periodRepository,
        private EmployeeContractChangeRequestFactory $changeRequestFactory,
        private EmployeeContractPeriodManagerInterface $periodManager,
        private AuditLogger $auditLogger,
    ) {}

    public function process(
        mixed $data,
        Operation $operation,
        array $uriVariables = [],
        array $context = []
    ): mixed {
        if ($operation instanceof DeleteOperationInterface) {
            return $this->removeProcessor->process($data, $operation, $uriVariables, $context);
        }

        if (!$data instanceof Employee) {
            return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
        }

        $isNew            = null === $data->getId();
        $previousContract = $this->resolvePreviousContract($data);
        $result           = $this->persistProcessor->process($data, $operation, $uriVariables, $context);

        $currentContract = $data->getContract();
        if (!$currentContract instanceof Contract) {
            return $result;
        }

        $today         = new DateTimeImmutable('today');
        $changeRequest = $this->changeRequestFactory->fromEmployee($data);

        if ($isNew) {
            $startDate = $changeRequest->contractStartDate ?? new DateTimeImmutable('1970-01-01');
            $nature    = $changeRequest->contractNature ?? ContractNature::CDI;
            $this->periodManager->ensureContractPeriodExists(
                employee: $data,
                contract: $currentContract,
                startDate: $startDate,
                endDate: $changeRequest->contractEndDate,
                nature: $nature,
                isDriver: $changeRequest->isDriver ?? false,
                workDaysHours: $changeRequest->workDaysHours,
                interimAgencyId: $changeRequest->interimAgencyId,
            );

            $data->setEntryDate($startDate);
            $this->entityManager->flush();

            $empName = trim(($data->getLastName() ?? '').' '.($data->getFirstName() ?? ''));
            $this->auditLogger->log(
                $data,
                'create',
                'employee',
                $data->getId(),
                sprintf('Employé %s créé (contrat: %s)', $empName, $currentContract->getName() ?? ''),
                ['new' => ['name' => $empName, 'contract' => $currentContract->getName(), 'nature' => $nature->value, 'startDate' => $startDate->format('d/m/Y')]],
            );
            $this->entityManager->flush();

            return $result;
        }

        if ($this->isSameContract($previousContract, $currentContract) && !$changeRequest->hasPeriodChangeRequest()) {
            return $result;
        }

        $empName = trim(($data->getLastName() ?? '').' '.($data->getFirstName() ?? ''));
        $this->auditLogger->log(
            $data,
            'update',
            'employee',
            $data->getId(),
            sprintf('Contrat modifié pour %s : %s → %s', $empName, $previousContract?->getName() ?? 'aucun', $currentContract->getName() ?? ''),
            ['old' => ['contract' => $previousContract?->getName()], 'new' => ['contract' => $currentContract->getName()]],
        );
        $this->entityManager->flush();

        $todayPeriod           = $this->periodRepository->findOneCoveringDate($data, $today);
        $effectivePeriod       = $todayPeriod ?? $this->periodRepository->findLatestPeriod($data);
        $currentPeriodContract = $effectivePeriod?->getContract();
        $contractChanged       = $currentPeriodContract instanceof Contract
            ? $currentPeriodContract->getId() !== $currentContract->getId()
            : true;
        $isCloseOnlyRequest = $changeRequest->isCloseOnlyRequest($contractChanged);

        if ($isCloseOnlyRequest) {
            $requestedEndDate = $changeRequest->contractEndDate;
            if (null === $requestedEndDate) {
                throw new UnprocessableEntityHttpException('contractEndDate is required for close-only request.');
            }
            $isAlreadyEnded = null === $todayPeriod;
            $this->periodManager->closeCurrentPeriod(
                $effectivePeriod,
                $requestedEndDate,
                $changeRequest->contractPaidLeaveSettled ?? false,
                $changeRequest->contractComment,
                $isAlreadyEnded
            );

            return $result;
        }

        $startDate = $changeRequest->contractStartDate ?? $today;
        $nature    = $changeRequest->contractNature ?? $effectivePeriod?->getContractNatureEnum() ?? ContractNature::CDI;
        $this->periodManager->createNextPeriod(
            employee: $data,
            contract: $currentContract,
            startDate: $startDate,
            endDate: $changeRequest->contractEndDate,
            nature: $nature,
            todayPeriod: $effectivePeriod,
            isDriver: $changeRequest->isDriver ?? false,
            workDaysHours: $changeRequest->workDaysHours,
            interimAgencyId: $changeRequest->interimAgencyId,
        );

        return $result;
    }

    private function resolvePreviousContract(Employee $employee): ?Contract
    {
        if (null === $employee->getId()) {
            return null;
        }

        $originalData = $this->entityManager->getUnitOfWork()->getOriginalEntityData($employee);
        $original     = $originalData['contract'] ?? null;

        return $original instanceof Contract ? $original : null;
    }

    private function isSameContract(?Contract $first, ?Contract $second): bool
    {
        if (null === $first || null === $second) {
            return $first === $second;
        }

        return $first->getId() === $second->getId();
    }
}
