<?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\CustomFieldsBundle\Command;

use Chill\CustomFieldsBundle\Entity\CustomField;
use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup;
use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;

/**
 * Class for the command 'chill:custom_fields:populate_group' that
 * Create custom fields from a yml file.
 */
class CreateFieldsOnGroupCommand extends Command
{
    protected static $defaultDescription = 'Create custom fields from a yml file';
    final public const ARG_DELETE = 'delete';

    final public const ARG_PATH = 'path';

    /**
     * CreateFieldsOnGroupCommand constructor.
     */
    public function __construct(
        private readonly CustomFieldProvider $customFieldProvider,
        private readonly EntityManager $entityManager,
        private readonly ValidatorInterface $validator,
        private $availableLanguages,
        private $customizablesEntities,
    ) {
        parent::__construct();
    }

    protected function configure()
    {
        $this->setName('chill:custom_fields:populate_group')
            ->addArgument(
                self::ARG_PATH,
                InputOption::VALUE_REQUIRED,
                'Path to description file'
            )
            ->addOption(
                self::ARG_DELETE,
                null,
                InputOption::VALUE_NONE,
                'If set, delete existing fields'
            );
    }

    /**
     * Delete the existing custom fields for a given customFieldGroup.
     *
     * @param CustomFieldsGroup $customFieldsGroup : The custom field group
     */
    protected function deleteFieldsForCFGroup($customFieldsGroup)
    {
        $em = $this->entityManager;

        foreach ($customFieldsGroup->getCustomFields() as $field) {
            $em->remove($field);
        }
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $helper = $this->getHelperSet()->get('question');

        $em = $this->entityManager;

        $customFieldsGroups = $em
            ->getRepository(CustomFieldsGroup::class)
            ->findAll();

        if (0 === \count($customFieldsGroups)) {
            $output->writeln('<error>There aren\'t any CustomFieldsGroup recorded'
                .' Please create at least one.</error>');
        }

        $table = new Table($output);
        $table
            ->setHeaders(array_merge(
                ['id', 'entity'],
                $this->availableLanguages
            ))
            ->setRows($this->_prepareRows($customFieldsGroups))
            ->render();

        $question = new Question(
            "Enter the customfieldGroup's id on which the custom fields should be added: "
        );
        $question->setNormalizer(
            static function ($answer) use ($customFieldsGroups) {
                foreach ($customFieldsGroups as $customFieldsGroup) {
                    if ($customFieldsGroup->getId() === $answer) {
                        return $customFieldsGroup;
                    }
                }

                throw new \RuntimeException('The id does not match an existing CustomFieldsGroup');
            }
        );
        $customFieldsGroup = $helper->ask($input, $output, $question);

        if ($input->getOption(self::ARG_DELETE)) {
            $this->deleteFieldsForCFGroup($customFieldsGroup);
        }

        $fieldsInput = $this->_parse(
            $input->getArgument(self::ARG_PATH),
            $output
        );

        $fields = $this->_addFields($customFieldsGroup, $fieldsInput, $output);

        return Command::SUCCESS;
    }

    private function _addFields(CustomFieldsGroup $group, $values, OutputInterface $output)
    {
        $em = $this->entityManager;

        $languages = $this->availableLanguages;

        foreach ($values['fields'] as $slug => $field) {
            // check the cf type exists
            $cfType = $this->customFieldProvider->getCustomFieldByType($field['type']);

            if (null === $cfType) {
                throw new \RuntimeException('the type '.$field['type'].' does not exists');
            }

            $cf = new CustomField();
            $cf->setSlug($slug)
                ->setName($field['name'])
                ->setOptions($field['options'] ?? [])
                ->setOrdering($field['ordering'])
                ->setType($field['type'])
                ->setCustomFieldsGroup($group);

            // add to table
            $names = [];

            foreach ($languages as $lang) {
                // todo replace with service to find lang when available
                $names[] = $cf->getName()[$lang] ?? 'Not available in this language';
            }

            if ($this->validator->validate($cf)) {
                $em->persist($cf);
                $output->writeln('<info>Adding Custom Field of type '
                    .$cf->getType()."\t with slug ".$cf->getSlug().
                    "\t and names : ".implode(', ', $names).'</info>');
            } else {
                throw new \RuntimeException('Error in field '.$slug);
            }
        }

        $em->flush();
    }

    private function _parse($path, OutputInterface $output)
    {
        $parser = new Parser();

        if (!file_exists($path)) {
            throw new \RuntimeException('file does not exist');
        }

        try {
            $values = $parser->parse(file_get_contents($path));
        } catch (ParseException $ex) {
            throw new \RuntimeException('The yaml file is not valid', 0, $ex);
        }

        return $values;
    }

    private function _prepareRows($customFieldsGroups)
    {
        $rows = [];
        $languages = $this->availableLanguages;
        // gather entitites and create an array to access them easily
        $customizableEntities = [];

        foreach ($this->customizablesEntities as $entry) {
            $customizableEntities[$entry['class']] = $entry['name'];
        }

        array_walk(
            $customFieldsGroups,
            static function (CustomFieldsGroup $customFieldGroup, $key) use ($languages, &$rows, $customizableEntities) {
                // set id and entity
                $row = [
                    $customFieldGroup->getId(),
                    $customizableEntities[$customFieldGroup->getEntity()],
                ];

                foreach ($languages as $lang) {
                    // todo replace with service to find lang when available
                    $row[] = $customFieldGroup->getName()[$lang] ?? 'Not available in this language';
                }
                $rows[] = $row;
            }
        );

        return $rows;
    }
}
