<?php

declare(strict_types=1);

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\ApiResource\EmployeeRttPaymentInput;
use App\Entity\Employee;
use App\Entity\EmployeeRttBalance;
use App\Entity\EmployeeRttPayment;
use App\Repository\EmployeeRepository;
use App\Repository\EmployeeRttBalanceRepository;
use App\Repository\EmployeeRttPaymentRepository;
use App\Service\AuditLogger;
use App\Service\Contracts\EmployeeContractPhaseResolver;
use App\Service\Exercise\ExerciseYearResolver;
use App\Service\Rtt\RttClosingBalanceService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Clock\ClockInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

final readonly class EmployeeRttPaymentProcessor implements ProcessorInterface
{
    public function __construct(
        private EmployeeRepository $employeeRepository,
        private EmployeeRttPaymentRepository $rttPaymentRepository,
        private EmployeeRttBalanceRepository $rttBalanceRepository,
        private EntityManagerInterface $entityManager,
        private AuditLogger $auditLogger,
        private EmployeeContractPhaseResolver $phaseResolver,
        private ClockInterface $clock,
        private ExerciseYearResolver $exerciseYearResolver,
        private RttClosingBalanceService $rttClosingService,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): EmployeeRttPaymentInput
    {
        if (!$data instanceof EmployeeRttPaymentInput) {
            throw new UnprocessableEntityHttpException('Invalid payload.');
        }

        $employeeId = (int) ($uriVariables['id'] ?? 0);
        if ($employeeId <= 0) {
            throw new UnprocessableEntityHttpException('id must be a positive integer.');
        }

        $employee = $this->employeeRepository->find($employeeId);
        if (!$employee instanceof Employee) {
            throw new NotFoundHttpException('Employee not found.');
        }

        if ($data->month < 1 || $data->month > 12) {
            throw new UnprocessableEntityHttpException('month must be between 1 and 12.');
        }

        $year                = $data->year ?? $this->resolveCurrentExerciseYear();
        $currentExerciseYear = $this->resolveCurrentExerciseYear();

        $this->assertYearAllowedForPayment($employee, $year);

        // Option B — retroactive payment on the previous exercise: the next exercise's
        // opening report (a frozen snapshot) must be recomputed so the carry stays correct.
        // Refuse upfront if that report has been locked (validated) by RH.
        $downstreamBalance = null;
        if ($year === $currentExerciseYear - 1) {
            $downstreamBalance = $this->rttBalanceRepository->findOneByEmployeeAndYear($employee, $currentExerciseYear);
            $this->assertReportNotLocked($downstreamBalance);
        }

        $payment = $this->rttPaymentRepository->findOneByEmployeeYearMonth($employee, $year, $data->month);

        if (null === $payment) {
            $payment = new EmployeeRttPayment();
            $payment->setEmployee($employee);
            $payment->setYear($year);
            $payment->setMonth($data->month);
            $this->entityManager->persist($payment);
        }

        $payment->setBase25Minutes($data->base25Minutes);
        $payment->setBonus25Minutes($data->bonus25Minutes);
        $payment->setBase50Minutes($data->base50Minutes);
        $payment->setBonus50Minutes($data->bonus50Minutes);
        $payment->touch();

        $empName = trim(($employee->getLastName() ?? '').' '.($employee->getFirstName() ?? ''));
        $this->auditLogger->log(
            $employee,
            'update',
            'rtt_payment',
            $payment->getId(),
            sprintf('Paiement RTT modifié pour %s (%02d/%d)', $empName, $data->month, $year),
            ['new' => ['month' => $data->month, 'year' => $year, 'base25' => $data->base25Minutes, 'bonus25' => $data->bonus25Minutes, 'base50' => $data->base50Minutes, 'bonus50' => $data->bonus50Minutes]],
        );

        // Persist the payment and, atomically, refresh the next exercise's opening report.
        // The flush inside the transaction makes the new payment visible to the closing
        // recomputation (same DB connection), so the carry reflects it.
        $this->entityManager->wrapInTransaction(function () use ($employee, $year, $downstreamBalance): void {
            $this->entityManager->flush();

            if (null !== $downstreamBalance) {
                $closing = $this->rttClosingService->computeClosingBalance($employee, $year);
                $downstreamBalance
                    ->setOpeningBase25Minutes($closing->base25Minutes)
                    ->setOpeningBonus25Minutes($closing->bonus25Minutes)
                    ->setOpeningBase50Minutes($closing->base50Minutes)
                    ->setOpeningBonus50Minutes($closing->bonus50Minutes)
                    ->touch()
                ;
                $this->entityManager->flush();
            }
        });

        $data->year = $year;

        return $data;
    }

    private function resolveCurrentExerciseYear(): int
    {
        return $this->exerciseYearResolver->forDate($this->clock->now());
    }

    /**
     * Allow payment when the requested exercise is the current one, the
     * immediately previous one (retroactive payment — Option B), or the last
     * exercise of a closed contract phase (the one containing the phase end
     * date). Reject any other exercise (older past or future).
     */
    private function assertYearAllowedForPayment(Employee $employee, int $year): void
    {
        $currentExerciseYear = $this->resolveCurrentExerciseYear();
        if ($year === $currentExerciseYear || $year === $currentExerciseYear - 1) {
            return;
        }

        $phases = $this->phaseResolver->resolvePhases($employee);
        foreach ($phases as $phase) {
            if ($phase->isCurrent || null === $phase->endDate) {
                continue;
            }
            if ($year === $this->exerciseYearResolver->forDate($phase->endDate)) {
                return;
            }
        }

        throw new UnprocessableEntityHttpException(
            'RTT payment is only allowed on the current exercise, the previous one, or the last exercise of a closed contract phase.'
        );
    }

    /**
     * Refuse a retroactive payment when the next exercise's opening report has
     * been locked (validated) by RH: recomputing it would either be impossible
     * or silently desync the carry. A missing report (null) never blocks.
     */
    private function assertReportNotLocked(?EmployeeRttBalance $downstreamBalance): void
    {
        if (null !== $downstreamBalance && $downstreamBalance->isLocked()) {
            throw new UnprocessableEntityHttpException(
                'Impossible : le report RTT de l\'exercice suivant est verrouillé. Déverrouillez-le pour saisir un paiement rétroactif.'
            );
        }
    }
}
