<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Command;

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\Mailer;
use Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper;
use Doctrine\ORM\EntityManagerInterface;
use League\Csv\Reader;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Class ChillUserSendRenewPasswordCodeCommand.
 */
class ChillUserSendRenewPasswordCodeCommand extends Command
{
    protected static $defaultDescription = 'Send a message with code to recover password';
    /**
     * @var EntityManagerInterface
     */
    protected $em;

    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher;

    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var Mailer
     */
    protected $mailer;

    /**
     * @var RecoverPasswordHelper
     */
    protected $recoverPasswordHelper;

    /**
     * The current input interface.
     */
    private ?InputInterface $input = null;

    /**
     * The current output interface.
     */
    private ?OutputInterface $output = null;

    public function __construct(
        LoggerInterface $logger,
        EntityManagerInterface $em,
        RecoverPasswordHelper $recoverPasswordHelper,
        EventDispatcherInterface $eventDispatcher,
    ) {
        $this->logger = $logger;
        $this->em = $em;
        $this->recoverPasswordHelper = $recoverPasswordHelper;
        $this->eventDispatcher = $eventDispatcher;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName('chill:user:send-password-recover-code')
            ->addArgument('csvfile', InputArgument::REQUIRED, 'CSV file with the list of users')
            ->addOption('template', null, InputOption::VALUE_REQUIRED, 'Template for email')
            ->addOption('expiration', null, InputOption::VALUE_REQUIRED, 'Expiration of the link, as an unix timestamp')
            ->addOption('subject', null, InputOption::VALUE_REQUIRED, 'Subject of the email', 'Recover your password');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->input = $input;
        $this->output = $output;

        $reader = $this->getReader();

        foreach ($reader->getRecords() as $offset => $r) {
            $user = $this->getUser($r);

            if (null === $user) {
                $this->onUserNotFound($r, $offset);

                continue;
            }

            $this->sendRecoverCode($user);
        }

        return Command::SUCCESS;
    }

    /**
     * @return Reader
     *
     * @throws \Exception
     */
    protected function getReader()
    {
        try {
            $reader = Reader::from($this->input->getArgument('csvfile'));
        } catch (\Exception $e) {
            $this->logger->error('The csv file could not be read', [
                'path' => $this->input->getArgument('csvfile'),
            ]);

            throw $e;
        }

        $reader->setHeaderOffset(0);

        $headers = $reader->getHeader();

        if (
            false === \in_array('username', $headers, true)
            && false === \in_array('email', $headers, true)
        ) {
            throw new \InvalidArgumentException('The csv file does not have an username or email header');
        }

        return $reader;
    }

    protected function getUser($row)
    {
        /** @var \Chill\MainBundle\Repository\UserRepository $userRepository */
        $userRepository = $this->em->getRepository(User::class);

        try {
            if (\array_key_exists('email', $row)) {
                return $userRepository->findOneByUsernameOrEmail(\trim((string) $row['email']));
            }
        } catch (\Doctrine\ORM\NoResultException) {
            // continue, we will try username
        }

        try {
            if (\array_key_exists('username', $row)) {
                return $userRepository->findOneByUsernameOrEmail(\trim((string) $row['username']));
            }
        } catch (\Doctrine\ORM\NoResultException) {
            return null;
        }
    }

    protected function onUserNotFound($row, $offset)
    {
        $this->logger->alert('User not found', \array_merge([
            'offset' => $offset,
        ], $row));
    }

    protected function sendRecoverCode(User $user)
    {
        if (empty($user->getEmail())) {
            $this->logger->alert('User without email', [
                'user_id' => $user->getId(),
                'username' => $user->getUsername(),
            ]);

            return;
        }

        $template = $this->input->getOption('template');
        $expiration = \DateTime::createFromFormat(
            'U',
            $this->input->getOption('expiration')
        );

        $this->recoverPasswordHelper
            ->sendRecoverEmail(
                $user,
                $expiration,
                $template,
                ['expiration' => $expiration],
                false,
                ['_locale' => 'fr'],
                $this->input->getOption('subject')
            );
    }
}
