<?php

declare(strict_types=1);

namespace App\Command;

use App\Entity\Employee;
use App\Entity\EmployeeRttBalance;
use App\Enum\ContractType;
use App\Enum\TrackingMode;
use App\Repository\EmployeeRepository;
use App\Repository\EmployeeRttBalanceRepository;
use App\Service\Rtt\RttClosingBalanceService;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Throwable;

#[AsCommand(
    name: 'app:rtt:rollover',
    description: 'Create yearly RTT opening balances (idempotent).'
)]
final class RttRolloverCommand extends Command
{
    public function __construct(
        private readonly EmployeeRepository $employeeRepository,
        private readonly EmployeeRttBalanceRepository $rttBalanceRepository,
        private readonly RttClosingBalanceService $rttClosingService,
        private readonly EntityManagerInterface $entityManager,
        #[Autowire(service: 'monolog.logger.cron')]
        private readonly LoggerInterface $logger,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this->addOption(
            'force',
            null,
            InputOption::VALUE_NONE,
            'Run rollover regardless of business date (manual recovery mode).'
        );
        $this->addOption(
            'recompute',
            null,
            InputOption::VALUE_NONE,
            'Recompute and overwrite existing (non-locked) balances instead of skipping them.'
        );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io        = new SymfonyStyle($input, $output);
        $today     = new DateTimeImmutable('today');
        $force     = (bool) $input->getOption('force');
        $recompute = (bool) $input->getOption('recompute');

        $this->logger->info('app:rtt:rollover started.', ['date' => $today->format('Y-m-d'), 'force' => $force, 'recompute' => $recompute]);

        if (!$force && '06-01' !== $today->format('m-d')) {
            $message = 'No RTT rollover today: business date is not 01/06.';
            $this->logger->info($message, ['date' => $today->format('Y-m-d')]);
            $io->success($message);

            return Command::SUCCESS;
        }

        $targetYear = $this->resolveTargetYear($today);
        $created    = 0;
        $updated    = 0;
        $skipped    = 0;

        foreach ($this->employeeRepository->findAll() as $employee) {
            if (!$employee instanceof Employee) {
                continue;
            }

            if (!$this->isEligible($employee)) {
                $this->logger->info('Employee skipped: not eligible.', ['employeeId' => $employee->getId()]);
                ++$skipped;

                continue;
            }

            $existing = $this->rttBalanceRepository->findOneByEmployeeAndYear($employee, $targetYear);
            if (null !== $existing && !$recompute) {
                $this->logger->info('Employee skipped: balance already exists.', ['employeeId' => $employee->getId(), 'year' => $targetYear]);
                ++$skipped;

                continue;
            }
            if (null !== $existing && $existing->isLocked()) {
                // Never overwrite a balance an RH user has validated/frozen.
                $this->logger->info('Employee skipped: balance is locked.', ['employeeId' => $employee->getId(), 'year' => $targetYear]);
                ++$skipped;

                continue;
            }

            try {
                $previousYear = $targetYear - 1;
                // Closing of the previous exercise = opening report + earned − paid.
                $closing = $this->rttClosingService->computeClosingBalance($employee, $previousYear);
            } catch (Throwable $e) {
                $this->logger->error('Error computing closing balance for employee.', ['employeeId' => $employee->getId(), 'error' => $e->getMessage()]);
                ++$skipped;

                continue;
            }

            $balance = $existing ?? new EmployeeRttBalance()
                ->setEmployee($employee)
                ->setYear($targetYear)
                ->setIsLocked(false)
            ;

            $balance
                ->setOpeningBase25Minutes($closing->base25Minutes)
                ->setOpeningBonus25Minutes($closing->bonus25Minutes)
                ->setOpeningBase50Minutes($closing->base50Minutes)
                ->setOpeningBonus50Minutes($closing->bonus50Minutes)
            ;

            if (null === $existing) {
                $this->entityManager->persist($balance);
                $this->logger->info('Balance created.', ['employeeId' => $employee->getId(), 'year' => $targetYear, 'carryMinutes' => $closing->totalMinutes]);
                ++$created;
            } else {
                $balance->touch();
                $this->logger->info('Balance recomputed.', ['employeeId' => $employee->getId(), 'year' => $targetYear, 'carryMinutes' => $closing->totalMinutes]);
                ++$updated;
            }
        }

        try {
            $this->entityManager->flush();
        } catch (Throwable $e) {
            $this->logger->error('Error flushing RTT balances.', ['error' => $e->getMessage()]);
            $io->error('RTT rollover failed: '.$e->getMessage());

            return Command::FAILURE;
        }

        $message = sprintf('RTT rollover done: %d created, %d recomputed, %d skipped.', $created, $updated, $skipped);
        $this->logger->info($message);
        $io->success($message);

        return Command::SUCCESS;
    }

    private function resolveTargetYear(DateTimeImmutable $today): int
    {
        $year  = (int) $today->format('Y');
        $month = (int) $today->format('n');

        return $month >= 6 ? $year + 1 : $year;
    }

    private function isEligible(Employee $employee): bool
    {
        $contract = $employee->getContract();
        if (null === $contract) {
            return false;
        }

        if (TrackingMode::PRESENCE->value === $contract->getTrackingMode()) {
            return false;
        }

        $type = ContractType::resolve(
            $contract->getName(),
            $contract->getTrackingMode(),
            $contract->getWeeklyHours()
        );

        return ContractType::INTERIM !== $type;
    }
}
