<?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\Doctrine\Model\Point;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\PostalCode;
use Doctrine\ORM\EntityManagerInterface;
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\Filesystem\Filesystem;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class LoadPostalCodesCommand extends Command
{
    protected static $defaultDescription = 'Add the postal code from a csv file.';

    public function __construct(private readonly EntityManagerInterface $entityManager, private readonly ValidatorInterface $validator)
    {
        parent::__construct();
    }

    protected function configure()
    {
        $this->setName('chill:main:postal-code:populate')
            ->setHelp('This script will try to avoid existing postal code '
                        ."using the postal code and name. \n"
                        .'The CSV file must have the following columns: '
                        .'postal code, label, country code.'
                        .'Optionally, the csv file can have the following '
                        .'columns after the country code: reference code, latitude, longitude, source. '
                        .'The latitude and longitude columns are supposed to be in WGS84 and expressed in decimal degrees. '
                        .'The CSV file should not have any header row.')
            ->addArgument('csv_file', InputArgument::REQUIRED, 'the path to '
                        .'the csv file. See the help for specifications.')
            ->addOption(
                'delimiter',
                'd',
                InputOption::VALUE_OPTIONAL,
                'The delimiter character of the csv file',
                ','
            )
            ->addOption(
                'enclosure',
                null,
                InputOption::VALUE_OPTIONAL,
                'The enclosure character of the csv file',
                '"'
            )
            ->addOption(
                'escape',
                null,
                InputOption::VALUE_OPTIONAL,
                'The escape character of the csv file',
                '\\'
            );
    }

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

        if (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
            $output->writeln('The content of the file is ...');
            $output->write(file_get_contents($input->getArgument('csv_file')));
        }

        $num = 0;
        $line = 0;

        while (
            false !== ($row = fgetcsv(
                $csv,
                0,
                $input->getOption('delimiter'),
                $input->getOption('enclosure'),
                (string) $input->getOption('escape')
            ))
        ) {
            try {
                $this->addPostalCode($row, $output);
                ++$num;
            } catch (CountryCodeNotFoundException|ExistingPostalCodeException|PostalCodeNotValidException $ex) {
                $output->writeln('<warning> on line '.$line.' : '.$ex->getMessage().'</warning>');
            }
            ++$line;
        }

        $this->entityManager->flush();

        $output->writeln('<info>'.$num.' were added !</info>');

        return Command::SUCCESS;
    }

    private function addPostalCode($row, OutputInterface $output)
    {
        if ('FR' === $row[2] && 4 === \strlen((string) $row[0])) {
            // CP in FRANCE are on 5 digit
            // For CP starting with a zero, the starting zero can be remove if stored as number in a csv
            // add a zero if CP from FR and on 4 digit
            $row[0] = '0'.$row[0];
        }

        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
            $output->writeln('handling row: '.$row[0].' | '.$row[1].' | '.$row[2]);
        }
        $em = $this->entityManager;
        $country = $em
            ->getRepository(Country::class)
            ->findOneBy(['countryCode' => $row[2]]);

        if (null === $country) {
            throw new CountryCodeNotFoundException(sprintf('The country with code %s is not found. Aborting to insert postal code with %s - %s', $row[2], $row[0], $row[1]));
        }

        // try to find an existing postal code
        $existingPC = $em
            ->getRepository(PostalCode::class)
            ->findBy(['code' => $row[0], 'name' => $row[1]]);

        if (\count($existingPC) > 0) {
            throw new ExistingPostalCodeException(sprintf('A postal code with code : %s and name : %s already exists, skipping', $row[0], $row[1]));
        }

        $postalCode = (new PostalCode())
            ->setCode($row[0])
            ->setName($row[1])
            ->setCountry($country);

        if (null !== $row[3]) {
            $postalCode->setRefPostalCodeId($row[3]);
        }

        if (null !== $row[4] & null !== $row[5]) {
            $postalCode->setCenter(Point::fromLonLat((float) $row[5], (float) $row[4]));
        }

        if (null !== $row[6]) {
            $postalCode->setPostalCodeSource($row[6]);
        }

        $errors = $this->validator->validate($postalCode);

        if (0 === $errors->count()) {
            $em->persist($postalCode);
        } else {
            $msg = '';

            foreach ($errors as $error) {
                $msg .= ' '.$error->getMessage();
            }

            throw new PostalCodeNotValidException($msg);
        }

        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
            $output->writeln(sprintf(
                'Creating postal code with code: %s, name: %s, countryCode: %s',
                $postalCode->getCode(),
                $postalCode->getName(),
                $postalCode->getCountry()->getCountryCode()
            ));
        }
    }

    private function getCSVResource(InputInterface $input)
    {
        $fs = new Filesystem();
        $filename = $input->getArgument('csv_file');

        if (!$fs->exists($filename)) {
            throw new \RuntimeException('The file does not exists or you do not have the right to read it.');
        }

        $resource = fopen($filename, 'rb');

        if (false === $resource) {
            throw new \RuntimeException("The file '{$filename}' could not be opened.");
        }

        return $resource;
    }
}

class ExistingPostalCodeException extends \Exception {}

class CountryCodeNotFoundException extends \Exception {}

class PostalCodeNotValidException extends \Exception {}
